Sql 当有两个可能的表包含详细信息时,选择数据的最佳方法是什么?
我有一个保存产品信息的数据库,特别是按材料分类的产品包装重量。并非每个产品都有实际的包装重量,因此有一个系统通过将这些产品分组来确定这些产品的平均重量 例如,如果有一种新产品“豆子罐头”,那么它可能会被放入一个名为“罐头”的组中。“罐”组中的其他产品将具有包装重量,因此需要进行计算以确定该组的平均重量(按材料) 在显示权重数据时,如果实际权重可用,我希望使用实际权重,如果不可用,则使用组权重。问题在于,产品与实际重量/组重量之间的关系是一对多的,因此,如果产品同时具有实际重量和组重量,则可能返回多行重复数据 在live系统中,大约有1000万个产品和300多万个重量,因此我需要一个性能良好的解决方案 我目前的方法是只选择所有行,然后取平均权重,但这似乎是一个相当“笨拙”的解决方案。有更好的方法吗 我有一个(相当长的)使用虚构数据的例子:Sql 当有两个可能的表包含详细信息时,选择数据的最佳方法是什么?,sql,sql-server,Sql,Sql Server,我有一个保存产品信息的数据库,特别是按材料分类的产品包装重量。并非每个产品都有实际的包装重量,因此有一个系统通过将这些产品分组来确定这些产品的平均重量 例如,如果有一种新产品“豆子罐头”,那么它可能会被放入一个名为“罐头”的组中。“罐”组中的其他产品将具有包装重量,因此需要进行计算以确定该组的平均重量(按材料) 在显示权重数据时,如果实际权重可用,我希望使用实际权重,如果不可用,则使用组权重。问题在于,产品与实际重量/组重量之间的关系是一对多的,因此,如果产品同时具有实际重量和组重量,则可能返回
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;
我以前在类似情况下所做的是引入一个包含所有可能值的原始查询,以及该值的优先级;然后使用
行号
外部查询仅获取优先级最高的值
我将使用您的(优秀的)示例数据,在插入@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
这将为每个具有特定重量的产品材料返回一行,再为每个产品材料返回一行。每行表示是产品重量还是组重量
然后我们可以按优先级对行进行编号:
-- 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
我想除了顺序和你的一样
当然,你必须自己检查性能。你可能可以交叉应用
一个联合所有人
语句。@ta.speot.is一个联合所有人
就够了吗?@RaduGheorghiu当然。您可以将其粘贴到子查询或CTE中,但我个人认为在这种情况下,交叉应用
将是最简洁的。我使用CROSS-APPLY
时,它将类似于“现在非平凡地计算这个值”,就像使用用户定义的表函数一样。@ta.speot.is这就是我所想的。不过,我从来没有想过交叉应用,我必须检查一下,看看有什么可能。或者,如果您不介意维护多个对象,请将UNION ALL
粘贴到vwProductAndWeights
中,这很好用,比我提出的UNION ALL解决方案更好。我特别喜欢引入优先级,这样您就可以在行号()中使用它。然而,我对我的原始解决方案进行了测试,它实际上稍微慢了一点,当背靠背执行时,我的第一个查询大约得到43%,而您的查询得到57%。我将在真实的数据集上尝试这个方法,看看这个性能是否以同样的方式扩展。好消息是,当我将这个逻辑应用到生产版本中,并在50万个产品上运行它时,性能有了明显的提高。这一次我原来的方法是59%,而你的方法是41%,所以这是一个相当不错的改进,脚本现在更可读。在我的生产环境中,事情比我给出的简化示例复杂得多。例如,有五个潜在的重量来源,还有包装水平、包装类型、包装单位等需要担心。@RichardHansell我不知道你是否已经知道,但如果你不知道,还有其他观看的人:@AkashM-不,我不知道,但我现在再次感谢你。最后,我通过创建两个视图进行了一个简单的测试(不是最准确的,但对于我的目的来说已经足够接近了),一个以原始方式工作,另一个以新的“改进”方式工作。当在整个数据库中运行时,这两个视图都返回55281074行,而没有执行其他任何操作。原来的方法是03:32,新方法是02:55,所以这是我期望的顺序的明显改进?