トリックは、tbl_item から 2 列の結果セットを取得することです。私が「一致」と「マップ」と呼ぶ 2 つの列。「一致」列の値は、「一致する」行を見つけるために JOIN 述語で使用するものです。しかし、マップ列から値を返し、それに対して GROUP BY を実行します。
あなたの例では、次のクエリで必要な結果セットが得られると思います。
SELECT IF(i.old_code IS NOT NULL,i.old_code,i.code) AS `match`
, IF(i.parent_code IS NOT NULL,i.parent_code,i.code) AS map
FROM tbl_item i
LEFT
JOIN tbl_item j
ON j.old_code = i.code
WHERE j.old_code IS NULL
UNION ALL
SELECT k.code
, k.code
FROM tbl_item k
WHERE k.old_code IS NOT NULL
これにより、次の結果セットが得られます。
+-------+-------+
| match | map |
+-------+-------+
| 20 | 2 |
| 2A | 2 |
| 2B | 2 |
| 3 | 3 |
| 4 | 4 |
| 2 | 2 |
+-------+-------+
「一致」列が一意であることを確認する必要があります。これにより、tbl_invty の行がこのセットの複数の行に誤って一致することはありません。これは、このデータ セットでは問題ではありませんが、より一般的なケースでは問題になる可能性があります。また、これは古いものから新しいものへの「1レベル」のみを行います。「新しい」コードが後で置き換えられた場合、このクエリは「最新」を見つけず、行の値のみを見つけます。繰り返しますが、このデータの問題ではありませんが、より一般的なケースでは、これが問題になる可能性があります。
(クエリを記述する他の方法があります。たとえば、IF ではなく CASE 式を使用するか、より簡潔に:
SELECT IFNULL(i.old_code,i.code) AS `match`
, IFNULL(i.parent_code,i.code) AS map
FROM tbl_item i
LEFT
JOIN tbl_item j
ON j.old_code = i.code
WHERE j.old_code IS NULL
UNION ALL
SELECT k.code
, k.code
FROM tbl_item k
WHERE k.old_code IS NOT NULL
とにかく、適切な結果セットを取得するクエリがあれば、SUM(QTY) を簡単に取得できます。そのクエリを別のクエリの行ソースとして使用するだけです。(これを「インライン ビュー」と呼びますが、MySQL では「派生テーブル」と呼んでいます。これは、MySQL が実際にビュー クエリを処理する方法をより正確に表しています。)
SELECT m.map AS CODE
, SUM(v.qty) AS QTY
FROM tbl_invty v
JOIN (
SELECT IFNULL(i.old_code,i.code) AS `match`
, IFNULL(i.parent_code,i.code) AS map
FROM tbl_item i
LEFT
JOIN tbl_item j
ON j.old_code = i.code
WHERE j.old_code IS NULL
UNION ALL
SELECT k.code
, k.code
FROM tbl_item k
WHERE k.old_code IS NOT NULL
) m
ON m.match = v.code
GROUP
BY m.map
ファローアップ
Q: CODE に PARENT_CODE と OLD_CODE の両方が含まれている場合はどうなりますか (これが可能なシナリオであることがわかりました...
A: "match" および "map" コードを返すには、クエリをテストする必要があります。
その新しいケース (PARENT_CODE と OLD_CODE の両方を含む行) を考えると、調整が必要です。実際には、各ケースを実行して、各ケースで何を返す必要があるかを判断する必要があります。
テーブル内のすべてのコード (CODE 列、PARENT_CODE 列、または OLD_CODE 列にあるかどうか) を「一致」コードとして選択し、それぞれに適切な「マップ」コードを導き出したいようです。
テーブルの CODE 列は NOT NULL であると仮定しtbl_item
ます (これを単純化するためです)。
これらは、これらすべてのケースを処理するために、私が考えている 4 つのクエリです。
-- rows with PARENT_CODE, match=CODE map=PARENT_CODE
SELECT i.code AS `match_code`
, i.parent_code AS `map_code`
FROM tbl_item i
WHERE i.parent_code IS NOT NULL
-- rows with PARENT_CODE and OLD_CODE, match=OLD_CODE map=PARENT_CODE
SELECT j.old_code
, j.parent_code
FROM tbl_item j
WHERE j.parent_code IS NOT NULL
AND j.old_code IS NOT NULL
-- rows with no PARENT_CODE, match=CODE map=CODE
SELECT k.code
, k.code
FROM tbl_item k
WHERE k.parent_code IS NULL
-- rows with OLD_CODE and no PARENT_CODE, match=OLD_CODE map=CODE
SELECT l.old_code
, l.code
FROM tbl_item l
WHERE l.parent_code IS NULL
AND l.old_code IS NOT NULL
これらは、UNION ALL 演算子を使用して結合されます。
同じ CODE が複数回出現し、それぞれが異なる PARENT_CODE を指している可能性がある奇抜なデータを想像できます。
TBL_ITEM の奇抜な (予期しない) 行の例。
+------+-------------+----------+
| CODE | PARENT_CODE | OLD_CODE |
+------+-------------+----------+
| 77A | 77A | NULL |
| 77A | 77B | 77A |
| 77 | 77 | 77A |
| 77 | 77A | NULL |
+------+-------------+----------+
このような混乱をどうしますか?
SUM(QTY) を取得する限り、"map/match" 行セットで保証する必要があるのは、特定の CODE が 1 回だけ出現することです。(そこに倍数があると、複数に一致するため、SUM が高くなりすぎます...
簡単な解決策は、クエリを別のクエリでラップして、重複を排除し、マップするコードを 1 つだけ選択することです。これは「正しい」修正ではないかもしれませんが、作業できる結果セットが得られます。
SELECT u.match
, MIN(u.map) AS map
FROM (
-- query to get match/map rowset here
) u
GROUP BY u.match
次に、このクエリを、以前と同じクエリである SUM(QTY) を取得するインライン ビューとして使用します。としてエイリアス化されたインライン ビューでそのクエリを変更したところm
です。
SELECT m.map AS CODE
, SUM(v.qty) AS QTY
FROM tbl_invty v
JOIN (
SELECT u.match
, MIN(u.map) AS map
FROM (
-- query to get match/map rowset here
) u
GROUP BY u.match
) m
ON m.match = v.code
GROUP
BY m.map
この時点で、言うまでもなく、クエリを単純にするために本当に必要なのは、 (一意でnull ではない)match_code
とオプションの の 2 つの列だけを持つ ですmap_code
。
これにより、非常に単純なクエリが作成されます。
SELECT IFNULL(m.map_code,m.match_code) AS CODE
, SUM(v.qty) AS QTY
FROM tbl_invty v
JOIN tbl_match_map m
ON m.match_code = v.code
GROUP
BY IFNULL(m.map_code,m.match_code)
大変な作業である特定のテーブルから「tbl_match_map」行セットを生成しています。