fix: 平衡报工归属拆分并约束唯一性
This commit is contained in:
@@ -49,11 +49,21 @@ REQUIRED_COLUMNS = {
|
||||
},
|
||||
}
|
||||
REQUIRED_INDEXES = {
|
||||
"idx_report_allocations_report": ("report_id",),
|
||||
"idx_report_allocations_item": ("report_item_id",),
|
||||
"idx_report_allocations_date": ("allocation_date",),
|
||||
"idx_report_allocations_point_date": ("attendance_point_name", "allocation_date"),
|
||||
"idx_report_allocations_employee_date": ("employee_phone", "allocation_date"),
|
||||
"uq_report_allocations_report_item_date": {
|
||||
"columns": ("report_id", "report_item_id", "allocation_date"),
|
||||
"non_unique": 0,
|
||||
},
|
||||
"idx_report_allocations_report": {"columns": ("report_id",), "non_unique": 1},
|
||||
"idx_report_allocations_item": {"columns": ("report_item_id",), "non_unique": 1},
|
||||
"idx_report_allocations_date": {"columns": ("allocation_date",), "non_unique": 1},
|
||||
"idx_report_allocations_point_date": {
|
||||
"columns": ("attendance_point_name", "allocation_date"),
|
||||
"non_unique": 1,
|
||||
},
|
||||
"idx_report_allocations_employee_date": {
|
||||
"columns": ("employee_phone", "allocation_date"),
|
||||
"non_unique": 1,
|
||||
},
|
||||
}
|
||||
REQUIRED_FOREIGN_KEYS = {
|
||||
"report_id": ("production_reports", "id", "CASCADE"),
|
||||
@@ -153,22 +163,51 @@ def existing_foreign_keys(conn) -> dict[str, tuple[str, str, str]]:
|
||||
return {row[0]: (row[1], row[2], row[3]) for row in rows}
|
||||
|
||||
|
||||
def find_equivalent_index(indexes: dict[str, IndexMetadata], columns: tuple[str, ...]) -> str | None:
|
||||
def required_index_columns(index_name: str) -> tuple[str, ...]:
|
||||
return REQUIRED_INDEXES[index_name]["columns"]
|
||||
|
||||
|
||||
def required_index_non_unique(index_name: str) -> int:
|
||||
return int(REQUIRED_INDEXES[index_name]["non_unique"])
|
||||
|
||||
|
||||
def find_equivalent_index(
|
||||
indexes: dict[str, IndexMetadata],
|
||||
columns: tuple[str, ...],
|
||||
non_unique: int,
|
||||
) -> str | None:
|
||||
for index_name, metadata in indexes.items():
|
||||
if index_name != "PRIMARY" and metadata["columns"] == columns and metadata["non_unique"] == 1:
|
||||
if (
|
||||
index_name != "PRIMARY"
|
||||
and metadata["columns"] == columns
|
||||
and metadata["non_unique"] == non_unique
|
||||
):
|
||||
return index_name
|
||||
return None
|
||||
|
||||
|
||||
def create_index(conn, index_name: str, columns: tuple[str, ...]) -> None:
|
||||
if index_name not in REQUIRED_INDEXES or REQUIRED_INDEXES[index_name] != columns:
|
||||
if index_name not in REQUIRED_INDEXES or required_index_columns(index_name) != columns:
|
||||
raise ValueError(f"unexpected index definition: {index_name} {columns}")
|
||||
|
||||
non_unique = required_index_non_unique(index_name)
|
||||
indexes = existing_indexes(conn)
|
||||
if index_name in indexes:
|
||||
if (
|
||||
index_name in indexes
|
||||
and indexes[index_name]["columns"] == columns
|
||||
and indexes[index_name]["non_unique"] == non_unique
|
||||
):
|
||||
return
|
||||
if index_name in indexes:
|
||||
conn.execute(
|
||||
text(
|
||||
f"ALTER TABLE {quote_identifier(TABLE_NAME)} "
|
||||
f"DROP INDEX {quote_identifier(index_name)}"
|
||||
)
|
||||
)
|
||||
indexes = existing_indexes(conn)
|
||||
|
||||
equivalent_index = find_equivalent_index(indexes, columns)
|
||||
equivalent_index = find_equivalent_index(indexes, columns, non_unique)
|
||||
if equivalent_index:
|
||||
conn.execute(
|
||||
text(
|
||||
@@ -179,14 +218,51 @@ def create_index(conn, index_name: str, columns: tuple[str, ...]) -> None:
|
||||
return
|
||||
|
||||
column_sql = ", ".join(quote_identifier(column) for column in columns)
|
||||
unique_sql = "UNIQUE " if non_unique == 0 else ""
|
||||
conn.execute(
|
||||
text(
|
||||
f"CREATE INDEX {quote_identifier(index_name)} "
|
||||
f"CREATE {unique_sql}INDEX {quote_identifier(index_name)} "
|
||||
f"ON {quote_identifier(TABLE_NAME)} ({column_sql})"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def cleanup_duplicate_allocation_keys(conn) -> None:
|
||||
if not table_exists(conn):
|
||||
return
|
||||
conn.execute(
|
||||
text(
|
||||
"""
|
||||
DELETE target
|
||||
FROM production_report_allocations target
|
||||
JOIN (
|
||||
SELECT id
|
||||
FROM (
|
||||
SELECT allocation.id
|
||||
FROM production_report_allocations allocation
|
||||
JOIN (
|
||||
SELECT
|
||||
report_id,
|
||||
report_item_id,
|
||||
allocation_date,
|
||||
MIN(id) AS keep_id
|
||||
FROM production_report_allocations
|
||||
WHERE report_item_id IS NOT NULL
|
||||
GROUP BY report_id, report_item_id, allocation_date
|
||||
HAVING COUNT(*) > 1
|
||||
) duplicate_key
|
||||
ON duplicate_key.report_id = allocation.report_id
|
||||
AND duplicate_key.report_item_id = allocation.report_item_id
|
||||
AND duplicate_key.allocation_date = allocation.allocation_date
|
||||
WHERE allocation.id <> duplicate_key.keep_id
|
||||
) duplicate_rows
|
||||
) doomed
|
||||
ON doomed.id = target.id
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def cleanup_duplicate_fk_indexes(conn) -> None:
|
||||
indexes = existing_indexes(conn)
|
||||
duplicates = [
|
||||
@@ -235,6 +311,8 @@ def ensure_schema(conn) -> None:
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uq_report_allocations_report_item_date
|
||||
(report_id, report_item_id, allocation_date),
|
||||
KEY idx_report_allocations_report (report_id),
|
||||
KEY idx_report_allocations_item (report_item_id),
|
||||
KEY idx_report_allocations_date (allocation_date),
|
||||
@@ -251,8 +329,9 @@ def ensure_schema(conn) -> None:
|
||||
)
|
||||
)
|
||||
|
||||
for index_name, columns in REQUIRED_INDEXES.items():
|
||||
create_index(conn, index_name, columns)
|
||||
cleanup_duplicate_allocation_keys(conn)
|
||||
for index_name in REQUIRED_INDEXES:
|
||||
create_index(conn, index_name, required_index_columns(index_name))
|
||||
cleanup_duplicate_fk_indexes(conn)
|
||||
|
||||
|
||||
@@ -319,19 +398,21 @@ def verify_schema(conn) -> None:
|
||||
|
||||
wrong_indexes = [
|
||||
f"{index_name}({', '.join(indexes[index_name]['columns'])})"
|
||||
for index_name, columns in REQUIRED_INDEXES.items()
|
||||
if indexes.get(index_name, {}).get("columns") != columns
|
||||
for index_name in REQUIRED_INDEXES
|
||||
if indexes.get(index_name, {}).get("columns") != required_index_columns(index_name)
|
||||
]
|
||||
if wrong_indexes:
|
||||
raise RuntimeError(f"{TABLE_NAME} incompatible indexes: {', '.join(wrong_indexes)}")
|
||||
|
||||
unique_required_indexes = [
|
||||
wrong_index_uniqueness = [
|
||||
index_name
|
||||
for index_name in REQUIRED_INDEXES
|
||||
if indexes[index_name]["non_unique"] != 1
|
||||
if indexes[index_name]["non_unique"] != required_index_non_unique(index_name)
|
||||
]
|
||||
if unique_required_indexes:
|
||||
raise RuntimeError(f"{TABLE_NAME} required indexes are unique: {', '.join(unique_required_indexes)}")
|
||||
if wrong_index_uniqueness:
|
||||
raise RuntimeError(
|
||||
f"{TABLE_NAME} incompatible index uniqueness: {', '.join(wrong_index_uniqueness)}"
|
||||
)
|
||||
|
||||
duplicate_single_column_indexes = [
|
||||
index_name
|
||||
|
||||
Reference in New Issue
Block a user