製品情報、具体的には材料別の製品の梱包重量を保持するデータベースがあります。すべての製品に実際の梱包重量があるわけではないため、これらの製品をグループ化して平均重量を決定するシステムがあります。
たとえば、新製品「豆の缶詰」がある場合、これは「缶詰」というグループに入れられます。「缶」グループの他の製品には梱包重量があるため、グループの平均重量を決定するための計算があります (素材別)。
重みデータを提示するとき、利用可能な場合は実際の重みを使用し、利用できない場合はグループの重みを使用するようにフォールバックします。問題は、製品と実際の重量/グループの重量の関係が 1 対多であるため、製品に実際の重量とグループの重量の両方がある場合、重複データの複数の行が返される可能性があることです。
ライブ システムには約 1,000 万の製品と 300 万を超える重量があるため、適切に機能するソリューションが必要です。
私の現在の方法は、すべての行を選択してから重みの AVG を取得することですが、これはかなり「ぎこちない」解決策のようです。これを行うより良い方法はありますか?
構成されたデータを使用した(かなり長い)例があります:
DECLARE @Product TABLE (
ProductId INT,
GroupId INT,
ProductName VARCHAR(50),
PRIMARY KEY (ProductId));
DECLARE @Group TABLE (
GroupId INT,
GroupName VARCHAR(50),
PRIMARY KEY (GroupId));
DECLARE @Material TABLE (
MaterialId INT,
MaterialName VARCHAR(50),
PRIMARY KEY (MaterialId));
DECLARE @ProductWeight TABLE (
ProductId INT,
MaterialId INT,
[Weight] NUMERIC(19,2),
PRIMARY KEY (ProductId, MaterialId));
DECLARE @GroupWeight TABLE (
GroupId INT,
MaterialId INT,
[Weight] NUMERIC(19,2),
PRIMARY KEY (GroupId, MaterialId));
--Materials, only three for this example
INSERT INTO @Material VALUES (1, 'Paper');
INSERT INTO @Material VALUES (2, 'Steel');
INSERT INTO @Material VALUES (3, 'Glass');
--Two groups, one for cans and one for bottles
INSERT INTO @Group VALUES (1, 'Cans');
INSERT INTO @Group VALUES (2, 'Bottles');
--Five products, two "cans" and three "bottles"
INSERT INTO @Product VALUES (1, 1, 'Can of soup');
INSERT INTO @Product VALUES (2, 1, 'Can of beans');
INSERT INTO @Product VALUES (3, 2, 'Bottle of beer');
INSERT INTO @Product VALUES (4, 2, 'Bottle of wine');
INSERT INTO @Product VALUES (5, 2, 'Bottle of sauce');
--Three products have actual weights
INSERT INTO @ProductWeight VALUES (1, 1, 5.2);
INSERT INTO @ProductWeight VALUES (1, 2, 23.1);
INSERT INTO @ProductWeight VALUES (3, 1, 4.6);
INSERT INTO @ProductWeight VALUES (3, 2, 2.4);
INSERT INTO @ProductWeight VALUES (3, 3, 185.9);
INSERT INTO @ProductWeight VALUES (4, 1, 5.1);
INSERT INTO @ProductWeight VALUES (4, 2, 2.6);
INSERT INTO @ProductWeight VALUES (4, 3, 650.4);
--Calculate the group weights
INSERT INTO @GroupWeight
SELECT p.GroupId, pw.MaterialId, AVG(pw.[Weight])
FROM @ProductWeight pw INNER JOIN @Product p ON p.ProductId = pw.ProductId
GROUP BY p.GroupId, pw.MaterialId;
--Now display the product information, use the actual weights where available and the group weights otherwise
SELECT
p.ProductName,
m.MaterialName,
CASE WHEN pw.[Weight] IS NOT NULL THEN 'Product' ELSE 'Group' END AS WeightSource,
AVG(COALESCE(pw.[Weight], gw.[Weight])) AS [Weight]
FROM
@Product p
LEFT JOIN @ProductWeight pw ON pw.ProductId = p.ProductId
LEFT JOIN @GroupWeight gw ON gw.GroupId = p.GroupId
LEFT JOIN @Material m ON m.MaterialId = COALESCE(pw.MaterialId, gw.MaterialId)
GROUP BY
p.ProductName,
m.MaterialName,
CASE WHEN pw.[Weight] IS NOT NULL THEN 'Product' ELSE 'Group' END;
これを実行すると、重みのソースを含め、正確に必要な形式でデータが返されます。つまり、実際の重みまたはグループの重みの場合:
ProductName MaterialName WeightSource Weight
Bottle of beer Glass Product 185.900000
Bottle of beer Paper Product 4.600000
Bottle of beer Steel Product 2.400000
Bottle of sauce Glass Group 418.150000
Bottle of sauce Paper Group 4.850000
Bottle of sauce Steel Group 2.500000
Bottle of wine Glass Product 650.400000
Bottle of wine Paper Product 5.100000
Bottle of wine Steel Product 2.600000
Can of beans Paper Group 5.200000
Can of beans Steel Group 23.100000
Can of soup Paper Product 5.200000
Can of soup Steel Product 23.100000
しかし、これを行うためのより効率的な方法があるに違いないと感じずにはいられませんか?
編集 - UNION ALL を使用してみました。
WITH RawData AS (
SELECT
p.ProductName,
m.MaterialName,
'Product' AS WeightSource,
pw.[Weight]
FROM
@Product p
INNER JOIN @ProductWeight pw ON pw.ProductId = p.ProductId
INNER JOIN @Material m ON m.MaterialId = pw.MaterialId
UNION ALL
SELECT
p.ProductName,
m.MaterialName,
'Group' AS WeightSource,
gw.[Weight]
FROM
@Product p
INNER JOIN @GroupWeight gw ON gw.GroupId = p.GroupId
INNER JOIN @Material m ON m.MaterialId = gw.MaterialId),
RankedWeightSource AS (
SELECT
ProductName,
WeightSource,
ROW_NUMBER() OVER (PARTITION BY ProductName ORDER BY WeightSource DESC) AS RowRank
FROM
RawData
GROUP BY
ProductName,
WeightSource),
BestWeightSource AS (
SELECT
ProductName,
WeightSource
FROM
RankedWeightSource
WHERE
RowRank = 1)
SELECT
*
FROM
RawData rd
INNER JOIN BestWeightSource bws ON bws.ProductName = rd.ProductName AND bws.WeightSource = rd.WeightSource;