feat: add MaterialCode-based deduplication to material validation queries

Implement database-level deduplication using ROW_NUMBER() window function
to remove duplicate material records based on MaterialCode field.

Changes:
- Add query_all_distinct_by_material_code() to deduplicate full table results
- Add query_by_source_numbers_distinct() to deduplicate filtered results
- Update validator to use new deduplication methods with statistics logging
- Retention strategy: keep earliest record (CreateDate ASC, SequenceNumber ASC)

Benefits:
- Reduces data redundancy from 54,358 to 1,679 records (96.91% reduction)
- Improves validation result readability by removing duplicate materials
- Maintains backward compatibility with existing query methods

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-02-24 16:22:35 +08:00
parent 0644bdfb11
commit e5f6f4ee0c
2 changed files with 112 additions and 5 deletions

View File

@@ -357,6 +357,97 @@ class DiscreteMaterialPlanDAO(BaseDAO):
return all_results
def query_all_distinct_by_material_code(self) -> List[Dict]:
"""
查询所有记录,基于 MaterialCode 去重
保留策略:每个 MaterialCode 保留第一条记录
排序规则CreateDate ASC → SequenceNumber ASC
Returns:
List of dictionaries representing deduplicated records
"""
with get_connection() as db:
table_name = self._convert_sql('[dbo].[DiscreteMaterialPlanData]')
sql = f"""
WITH RankedRecords AS (
SELECT
*,
ROW_NUMBER() OVER (
PARTITION BY MaterialCode
ORDER BY CreateDate ASC, SequenceNumber ASC
) AS rn
FROM {table_name}
WHERE MaterialCode IS NOT NULL
)
SELECT
Factory, MaterialStatus, PlanNumber, SourceNumber, MaterialType,
ProductCode, ProductName, ProductUnit, ProductPlanQuantity,
UseDepartment, Remark, Creator, CreateDate, Approver, ApproveDate,
SequenceNumber, MaterialCode, MaterialName, Specification, Model,
DrawingNumber, MaterialQuality, PlanQuantity, Unit, RequiredDate,
Warehouse, UnitUsage, CumulativeOutputQuantity, BOMVersion
FROM RankedRecords
WHERE rn = 1
"""
return db.execute_query(sql)
def query_by_source_numbers_distinct(self, source_numbers: List[str]) -> List[Dict]:
"""
按 SourceNumber 过滤查询,基于 MaterialCode 去重
保留策略:每个 MaterialCode 保留第一条记录
排序规则CreateDate ASC → SequenceNumber ASC
Args:
source_numbers: SourceNumber 列表
Returns:
List of dictionaries representing deduplicated records
"""
if not source_numbers:
return []
batch_size = 2000
all_results = []
for i in range(0, len(source_numbers), batch_size):
batch = source_numbers[i:i + batch_size]
placeholder = self._get_placeholder()
placeholders = ','.join([placeholder for _ in batch])
table_name = self._convert_sql('[dbo].[DiscreteMaterialPlanData]')
sql = f"""
WITH RankedRecords AS (
SELECT
*,
ROW_NUMBER() OVER (
PARTITION BY MaterialCode
ORDER BY CreateDate ASC, SequenceNumber ASC
) AS rn
FROM {table_name}
WHERE SourceNumber IN ({placeholders})
AND MaterialCode IS NOT NULL
)
SELECT
Factory, MaterialStatus, PlanNumber, SourceNumber, MaterialType,
ProductCode, ProductName, ProductUnit, ProductPlanQuantity,
UseDepartment, Remark, Creator, CreateDate, Approver, ApproveDate,
SequenceNumber, MaterialCode, MaterialName, Specification, Model,
DrawingNumber, MaterialQuality, PlanQuantity, Unit, RequiredDate,
Warehouse, UnitUsage, CumulativeOutputQuantity, BOMVersion
FROM RankedRecords
WHERE rn = 1
"""
with get_connection() as db:
results = db.execute_query(sql, tuple(batch))
all_results.extend(results)
return all_results
def get_unique_material_names(self, source_numbers: List[str] = None) -> List[str]:
"""
Get unique material names, optionally filtered by SourceNumber.