3

製品情報、具体的には材料別の製品の梱包重量を保持するデータベースがあります。すべての製品に実際の梱包重量があるわけではないため、これらの製品をグループ化して平均重量を決定するシステムがあります。

たとえば、新製品「豆の缶詰」がある場合、これは「缶詰」というグループに入れられます。「缶」グループの他の製品には梱包重量があるため、グループの平均重量を決定するための計算があります (素材別)。

重みデータを提示するとき、利用可能な場合は実際の重みを使用し、利用できない場合はグループの重みを使用するようにフォールバックします。問題は、製品と実際の重量/グループの重量の関係が 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;
4

1 に答える 1

1

同様の状況で以前に行ったことは、値の優先順位とともに、可能なすべての値を含む生のクエリを導入することです。次に、ROW_NUMBER外部クエリを使用して、優先順位が最も高い値だけを取得します。

私はあなたの (優れた) サンプル データを使用するつもりです@GroupWeight

これは私たちの生データです:

-- the product weights (use INNER JOIN to only find 
--   the products with their own weights)
SELECT
    p.ProductId,
    p.ProductName,
    m.MaterialId,
    m.MaterialName,
    pw.Weight,
    'Product' WeightSource,
    20 Precedence
FROM
    @Product p
    INNER JOIN @ProductWeight pw ON pw.ProductId = p.ProductId
    INNER JOIN @Material m ON m.MaterialId = pw.MaterialId
UNION ALL
-- the group weight
SELECT
    p.ProductId,
    p.ProductName,
    m.MaterialId,
    m.MaterialName,
    gw.Weight,
    'Group' WeightSource,
    10 Precedence
FROM
    @Product p
    INNER JOIN @GroupWeight gw on gw.GroupId = p.GroupId
    INNER JOIN @Material m ON m.MaterialId = gw.MaterialId

これにより、特定の重量を持つ製品素材ごとに 1 行と、製品素材ごとに 1 行が返されます。各行は、製品の重量かグループの重量かを示します。

次に、優先順位に従って行に番号を付けることができます。

-- assume the above is in a CTE named AllWeights
SELECT 
    *,
    ROW_NUMBER() OVER (PARTITION BY ProductId, MaterialId 
                       ORDER BY Precedence DESC) rn
FROM 
    AllWeights

これにより、特定の製品材料のどの行が関連する行であるかを示す追加の指示を含む同じデータが得られるため、最終的にそれを取得できます。

-- assume the above is in a CTE named RowNumbered
SELECT
    ProductName,
    MaterialName,
    WeightSource,
    Weight
FROM
    RowNumbered
WHERE
    rn = 1
;

これで完了です。


すべてを一緒に入れて:

;WITH AllWeights AS (
-- the product weights (use INNER JOIN to only find 
--   the products with their own weights)
SELECT
    p.ProductId,
    p.ProductName,
    m.MaterialId,
    m.MaterialName,
    pw.Weight,
    'Product' WeightSource,
    20 Precedence
FROM
    @Product p
    INNER JOIN @ProductWeight pw ON pw.ProductId = p.ProductId
    INNER JOIN @Material m ON m.MaterialId = pw.MaterialId
UNION ALL
-- the group weight
SELECT
    p.ProductId,
    p.ProductName,
    m.MaterialId,
    m.MaterialName,
    gw.Weight,
    'Group' WeightSource,
    10 Precedence
FROM
    @Product p
    INNER JOIN @GroupWeight gw on gw.GroupId = p.GroupId
    INNER JOIN @Material m ON m.MaterialId = gw.MaterialId
),
RowNumbered AS (
SELECT 
    *,
    ROW_NUMBER() OVER (PARTITION BY ProductId, MaterialId 
                       ORDER BY Precedence DESC) rn
FROM 
    AllWeights
)
SELECT
    ProductName,
    MaterialName,
    WeightSource,
    Weight
FROM
    RowNumbered
WHERE
    rn = 1
;

出力:

ProductName          MaterialName WeightSource Weight
-------------------- ------------ ------------ ------------
Can of soup          Paper        Product      5.20
Can of soup          Steel        Product      23.10
Can of beans         Paper        Group        5.20
Can of beans         Steel        Group        23.10
Bottle of beer       Paper        Product      4.60
Bottle of beer       Steel        Product      2.40
Bottle of beer       Glass        Product      185.90
Bottle of wine       Paper        Product      5.10
Bottle of wine       Steel        Product      2.60
Bottle of wine       Glass        Product      650.40
Bottle of sauce      Paper        Group        4.85
Bottle of sauce      Steel        Group        2.50
Bottle of sauce      Glass        Group        418.15

順番以外はあなたと同じだと思います。

もちろん、自分でパフォーマンスを確認する必要があります。

于 2013-10-09T12:59:45.603 に答える