Преглед на файлове

Speed up schedule itemDailyOut queries with joined aggregates.

Avoid per-item correlated scans over delivery_order_line on large demo copies.
debrand
kelvin.yau преди 1 седмица
родител
ревизия
08737736c1
променени са 2 файла, в които са добавени 70 реда и са изтрити 50 реда
  1. +40
    -22
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/PSService.kt
  2. +30
    -28
      src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt

+ 40
- 22
src/main/java/com/ffii/fpsms/modules/jobOrder/service/PSService.kt Целия файл

@@ -29,35 +29,24 @@ open class PSService(
"fromDate" to from.toString(), "fromDate" to from.toString(),
"toDate" to to.toString(), "toDate" to to.toString(),
) )
// Join once instead of per-FG correlated scans on delivery_order_line (~millions of rows on demo copy).
val sql = """ val sql = """
SELECT SELECT
(SELECT dailyQty FROM item_daily_out WHERE itemCode = items.code) AS dailyQty,
(SELECT
IFNULL(ROUND(AVG(d.dailyQty)), 0)
FROM
(SELECT
SUM(dol.qty) AS dailyQty
FROM
delivery_order_line dol
LEFT JOIN delivery_order do ON dol.deliveryOrderId = do.id
WHERE
do.deleted = 0
AND dol.itemId = items.id
AND do.estimatedArrivalDate >= :fromDate AND do.estimatedArrivalDate <= :toDate
GROUP BY do.estimatedArrivalDate) AS d) AS avgQtyLastMonth,
(SELECT SUM(reqQty) FROM job_order WHERE bomId = bom.id AND status != 'completed') AS pendingJobQty,
(SELECT COUNT(1) FROM coffee_or_tea WHERE systemType = 'coffee' AND itemCode = items.code AND deleted = 0) AS isCoffee,
(SELECT COUNT(1) FROM coffee_or_tea WHERE systemType = 'tea' AND itemCode = items.code AND deleted = 0) AS isTea,
(SELECT COUNT(1) FROM coffee_or_tea WHERE systemType = 'lemon' AND itemCode = items.code AND deleted = 0) AS isLemon,
ido.dailyQty AS dailyQty,
COALESCE(avg_out.avgQtyLastMonth, 0) AS avgQtyLastMonth,
pending.pendingJobQty AS pendingJobQty,
COALESCE(cot.isCoffee, 0) AS isCoffee,
COALESCE(cot.isTea, 0) AS isTea,
COALESCE(cot.isLemon, 0) AS isLemon,
CASE WHEN item_fake_onhand.onHandQty IS NOT NULL THEN item_fake_onhand.onHandQty CASE WHEN item_fake_onhand.onHandQty IS NOT NULL THEN item_fake_onhand.onHandQty
ELSE inventory.onHandQty - 500 END AS stockQty, ELSE inventory.onHandQty - 500 END AS stockQty,
bom.baseScore, bom.baseScore,
bom.outputQty, bom.outputQty,
bom.outputQtyUom, bom.outputQtyUom,
(SELECT udfudesc
FROM delivery_order_line
LEFT JOIN uom_conversion ON delivery_order_line.uomId = uom_conversion.id
WHERE delivery_order_line.itemId = bom.itemId
(SELECT uc.udfudesc
FROM delivery_order_line dol
LEFT JOIN uom_conversion uc ON dol.uomId = uc.id
WHERE dol.itemId = bom.itemId
LIMIT 1) AS doUom, LIMIT 1) AS doUom,
items.code AS itemCode, items.code AS itemCode,
items.name AS itemName, items.name AS itemName,
@@ -81,6 +70,35 @@ open class PSService(
LEFT JOIN item_fake_onhand ON items.code = item_fake_onhand.itemCode LEFT JOIN item_fake_onhand ON items.code = item_fake_onhand.itemCode
LEFT JOIN item_uom iu ON iu.itemId = items.id AND iu.stockUnit = 1 LEFT JOIN item_uom iu ON iu.itemId = items.id AND iu.stockUnit = 1
LEFT JOIN uom_conversion uc_stock ON uc_stock.id = iu.uomId LEFT JOIN uom_conversion uc_stock ON uc_stock.id = iu.uomId
LEFT JOIN item_daily_out ido ON ido.itemCode = items.code
LEFT JOIN (
SELECT itemId, IFNULL(ROUND(AVG(dailyQty)), 0) AS avgQtyLastMonth
FROM (
SELECT dol.itemId, do.estimatedArrivalDate, SUM(dol.qty) AS dailyQty
FROM delivery_order_line dol
INNER JOIN delivery_order do ON dol.deliveryOrderId = do.id
WHERE do.deleted = 0
AND do.estimatedArrivalDate >= :fromDate
AND do.estimatedArrivalDate <= :toDate
GROUP BY dol.itemId, do.estimatedArrivalDate
) d
GROUP BY itemId
) avg_out ON avg_out.itemId = items.id
LEFT JOIN (
SELECT bomId, SUM(reqQty) AS pendingJobQty
FROM job_order
WHERE status != 'completed'
GROUP BY bomId
) pending ON pending.bomId = bom.id
LEFT JOIN (
SELECT itemCode,
SUM(CASE WHEN systemType = 'coffee' THEN 1 ELSE 0 END) AS isCoffee,
SUM(CASE WHEN systemType = 'tea' THEN 1 ELSE 0 END) AS isTea,
SUM(CASE WHEN systemType = 'lemon' THEN 1 ELSE 0 END) AS isLemon
FROM coffee_or_tea
WHERE deleted = 0
GROUP BY itemCode
) cot ON cot.itemCode = items.code
WHERE bom.deleted = 0 and bom.description = 'FG' WHERE bom.deleted = 0 and bom.description = 'FG'
""".trimIndent() """.trimIndent()
return jdbcDao.queryForList(sql, args) return jdbcDao.queryForList(sql, args)


+ 30
- 28
src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt Целия файл

@@ -1656,7 +1656,8 @@ open class ProductionScheduleService(
val dateFormatterDayMonth = DateTimeFormatter.ofPattern("d MMM", Locale.ENGLISH) val dateFormatterDayMonth = DateTimeFormatter.ofPattern("d MMM", Locale.ENGLISH)
val dateFormatterDayName = DateTimeFormatter.ofPattern("EEEE", Locale.ENGLISH) val dateFormatterDayName = DateTimeFormatter.ofPattern("EEEE", Locale.ENGLISH)


val dateInfo = (1..9).map { offset ->
// Match searchExportDailyMaterial day1..day7 columns
val dateInfo = (1..7).map { offset ->
val date = fromDate.plusDays(offset.toLong()) val date = fromDate.plusDays(offset.toLong())
Pair(date.format(dateFormatterDayMonth), date.format(dateFormatterDayName)) Pair(date.format(dateFormatterDayMonth), date.format(dateFormatterDayName))
} }
@@ -1693,7 +1694,7 @@ open class ProductionScheduleService(
rowData["uom"]?.toString() ?: "" rowData["uom"]?.toString() ?: ""
) )


val dailyQty = (1..9).map { offset ->
val dailyQty = (1..7).map { offset ->
asDouble(rowData["day$offset"]) asDouble(rowData["day$offset"])
} }


@@ -1722,7 +1723,7 @@ open class ProductionScheduleService(
dailySheet.setColumnWidth(0, 20 * 256) // 物料編號 dailySheet.setColumnWidth(0, 20 * 256) // 物料編號
dailySheet.setColumnWidth(1, 55 * 256) // 物料名稱 dailySheet.setColumnWidth(1, 55 * 256) // 物料名稱
dailySheet.setColumnWidth(2, 12 * 256) // 採購單位 dailySheet.setColumnWidth(2, 12 * 256) // 採購單位
for (i in baseHeaders.size until baseHeaders.size + 9) {
for (i in baseHeaders.size until baseHeaders.size + 7) {
dailySheet.setColumnWidth(i, 16 * 256) // daily qty columns dailySheet.setColumnWidth(i, 16 * 256) // daily qty columns
} }


@@ -1792,31 +1793,32 @@ open class ProductionScheduleService(
"fromDate" to fromDate, "fromDate" to fromDate,
) )


// Aggregate-only SELECT: MySQL 8 ONLY_FULL_GROUP_BY rejects the old `group by matCode` + psl.*.
val sql = """ val sql = """
select select
group_concat(distinct it.code) as itemCode, group_concat(distinct it.code) as itemCode,
group_concat(distinct it.name) as itemName, group_concat(distinct it.name) as itemName,
itm.code as matCode, itm.code as matCode,
itm.name as matName,
MAX(itm.name) as matName,
sum(bm.qty * psl.batchNeed) as totalMatQtyNeed, sum(bm.qty * psl.batchNeed) as totalMatQtyNeed,
iv.onHandQty,
iv.unavailableQty,
MAX(iv.onHandQty) as onHandQty,
MAX(iv.unavailableQty) as unavailableQty,
(select sum(qty) from purchase_order_line (select sum(qty) from purchase_order_line
left join purchase_order on purchase_order_line.purchaseOrderId = purchase_order.id left join purchase_order on purchase_order_line.purchaseOrderId = purchase_order.id
where purchase_order_line.itemId = itm.id and date(purchase_order.estimatedArrivalDate) >= date(now()) and purchase_order.completeDate is null) as purchasedQty, where purchase_order_line.itemId = itm.id and date(purchase_order.estimatedArrivalDate) >= date(now()) and purchase_order.completeDate is null) as purchasedQty,
bm.uomName,
(select ratioD/ratioN from item_uom where purchaseUnit = 1 and itemId = it.id and item_uom.deleted = 0
) as purchaseRatio,
(select ceil(ratioD/ratioN*sum(bm.qty * psl.batchNeed)) from item_uom where purchaseUnit = 1 and itemId = it.id and item_uom.deleted = 0
) as purchaseQtyNeed,
MAX(bm.uomName) as uomName,
(select ratioD/ratioN from item_uom where purchaseUnit = 1 and itemId = itm.id and item_uom.deleted = 0
LIMIT 1) as purchaseRatio,
ceil(sum(bm.qty * psl.batchNeed) * COALESCE((
select ratioD/ratioN from item_uom where purchaseUnit = 1 and itemId = itm.id and item_uom.deleted = 0 LIMIT 1
), 0)) as purchaseQtyNeed,
(select code from item_uom (select code from item_uom
left join uom_conversion on item_uom.uomId = uom_conversion.id left join uom_conversion on item_uom.uomId = uom_conversion.id
where item_uom.purchaseUnit = 1 and item_uom.itemId = it.id and item_uom.deleted = 0
) as purchaseUnit,
where item_uom.purchaseUnit = 1 and item_uom.itemId = itm.id and item_uom.deleted = 0
LIMIT 1) as purchaseUnit,
min(ps.produceAt) as toProdcueFrom, min(ps.produceAt) as toProdcueFrom,
max(ps.produceAt) as toProdcueAt, max(ps.produceAt) as toProdcueAt,
ucs.udfudesc as stockUnit,
psl.*
MAX(ucs.udfudesc) as stockUnit
from production_schedule_line psl from production_schedule_line psl
left join production_schedule ps on psl.prodScheduleId = ps.id left join production_schedule ps on psl.prodScheduleId = ps.id
@@ -1825,15 +1827,15 @@ open class ProductionScheduleService(
left join bom_material bm on bom.id = bm.bomId left join bom_material bm on bom.id = bm.bomId
left join items itm on bm.itemId = itm.id left join items itm on bm.itemId = itm.id
left join inventory iv on itm.id = iv.itemId left join inventory iv on itm.id = iv.itemId
left join item_uom ius on itm.id = ius.itemId and ius.stockUnit = 1
left join item_uom ius on itm.id = ius.itemId and ius.stockUnit = 1 and ius.deleted = 0
left join uom_conversion ucs on ius.uomId = ucs.id left join uom_conversion ucs on ius.uomId = ucs.id
where ps.produceAt >= :fromDate where ps.produceAt >= :fromDate
and ps.id = (select id from production_schedule where produceAt = ps.produceAt order by id desc limit 1) and ps.id = (select id from production_schedule where produceAt = ps.produceAt order by id desc limit 1)
-- This is water -- This is water
AND it.code != 'GI3236' AND it.code != 'GI3236'
-- order by ps.produceAt asc, psl.itemPriority desc, itemCode asc
group by matCode
AND itm.id IS NOT NULL
group by itm.id, itm.code
"""; """;


@@ -1869,33 +1871,33 @@ open class ProductionScheduleService(
daily_needs AS ( daily_needs AS (
SELECT SELECT
itm.id AS materialId, itm.id AS materialId,
itm.code AS matCode,
itm.name AS matName,
COALESCE(uomM18.code, uomP.code) AS uom,
ceil((iv.onHandQty * (itsm.ratioN / itsm.ratioD)) * (COALESCE(ium18.ratioD, itum.ratioD) / COALESCE(ium18.ratioN, itum.ratioN))) as onHandQty,
ceil((iv.unavailableQty * (itsm.ratioN / itsm.ratioD)) * (COALESCE(ium18.ratioD, itum.ratioD) / COALESCE(ium18.ratioN, itum.ratioN))) as unavailableQty,
COALESCE((
MAX(itm.code) AS matCode,
MAX(itm.name) AS matName,
MAX(COALESCE(uomM18.code, uomP.code)) AS uom,
MAX(ceil((iv.onHandQty * (itsm.ratioN / itsm.ratioD)) * (COALESCE(ium18.ratioD, itum.ratioD) / COALESCE(ium18.ratioN, itum.ratioN)))) as onHandQty,
MAX(ceil((iv.unavailableQty * (itsm.ratioN / itsm.ratioD)) * (COALESCE(ium18.ratioD, itum.ratioD) / COALESCE(ium18.ratioN, itum.ratioN)))) as unavailableQty,
MAX(COALESCE((
SELECT ceil(SUM(pol.qty * (itum.ratioN / itum.ratioD) * (COALESCE(ium18.ratioD, itum.ratioD) / COALESCE(ium18.ratioN, itum.ratioN)))) SELECT ceil(SUM(pol.qty * (itum.ratioN / itum.ratioD) * (COALESCE(ium18.ratioD, itum.ratioD) / COALESCE(ium18.ratioN, itum.ratioN))))
FROM purchase_order_line pol FROM purchase_order_line pol
JOIN purchase_order po ON pol.purchaseOrderId = po.id JOIN purchase_order po ON pol.purchaseOrderId = po.id
WHERE pol.itemId = itm.id WHERE pol.itemId = itm.id
AND DATE(po.estimatedArrivalDate) >= :fromDate AND DATE(po.estimatedArrivalDate) >= :fromDate
AND po.completeDate IS NULL AND po.completeDate IS NULL
), 0) AS purchasedQty,
), 0)) AS purchasedQty,
DATE(ps.produceAt) AS produceDate, DATE(ps.produceAt) AS produceDate,
ceil( SUM(bm.baseQty * psl.batchNeed) * (COALESCE(ium18.ratioD, itum.ratioD) / COALESCE(ium18.ratioN, itum.ratioN)) ) AS qtyNeeded
ceil( SUM(bm.baseQty * psl.batchNeed) * MAX(COALESCE(ium18.ratioD, itum.ratioD) / COALESCE(ium18.ratioN, itum.ratioN)) ) AS qtyNeeded
FROM production_schedule_line psl FROM production_schedule_line psl
JOIN production_schedule ps ON psl.prodScheduleId = ps.id JOIN production_schedule ps ON psl.prodScheduleId = ps.id
JOIN items it ON psl.itemId = it.id JOIN items it ON psl.itemId = it.id
JOIN bom ON it.id = bom.itemId JOIN bom ON it.id = bom.itemId
JOIN bom_material bm ON bom.id = bm.bomId JOIN bom_material bm ON bom.id = bm.bomId
JOIN items itm ON bm.itemId = itm.id JOIN items itm ON bm.itemId = itm.id
JOIN item_uom itum ON itm.id = itum.itemId and itum.purchaseUnit = 1
JOIN item_uom itum ON itm.id = itum.itemId and itum.purchaseUnit = 1 and itum.deleted = 0
JOIN uom_conversion uomP ON itum.uomId = uomP.id JOIN uom_conversion uomP ON itum.uomId = uomP.id
LEFT JOIN latest_supplier_uom lsu ON lsu.materialId = itm.id LEFT JOIN latest_supplier_uom lsu ON lsu.materialId = itm.id
LEFT JOIN item_uom ium18 ON ium18.itemId = itm.id AND ium18.uomId = lsu.uomIdM18 AND ium18.deleted = 0 LEFT JOIN item_uom ium18 ON ium18.itemId = itm.id AND ium18.uomId = lsu.uomIdM18 AND ium18.deleted = 0
LEFT JOIN uom_conversion uomM18 ON uomM18.id = ium18.uomId LEFT JOIN uom_conversion uomM18 ON uomM18.id = ium18.uomId
JOIN item_uom itsm ON itm.id = itsm.itemId and itsm.stockUnit = 1
JOIN item_uom itsm ON itm.id = itsm.itemId and itsm.stockUnit = 1 and itsm.deleted = 0
LEFT JOIN inventory iv ON itm.id = iv.itemId LEFT JOIN inventory iv ON itm.id = iv.itemId
WHERE DATE(ps.produceAt) >= DATE_ADD(:fromDate, INTERVAL 1 DAY) WHERE DATE(ps.produceAt) >= DATE_ADD(:fromDate, INTERVAL 1 DAY)
AND DATE(ps.produceAt) < DATE_ADD(:fromDate, INTERVAL 8 DAY) AND DATE(ps.produceAt) < DATE_ADD(:fromDate, INTERVAL 8 DAY)


Зареждане…
Отказ
Запис