问题是您有一个返回多行的子查询,并且您试图将其作为值存储在单行/列组合中。
为了理解为什么会这样,让我们看一下外部查询“大西洋运费”的结果:
表 1 - 大西洋运费
ShipDate Atlantic Freight Charge
01/01/2012 1.00
01/02/2012 1.00
01/03/2012 1.00
01/04/2012 1.00
01/05/2012 1.00
让我们看看内部子查询可能返回什么:
表 2 - 非大西洋运费
ShipDate Non-Atlantic Freight Charge
01/01/2012 2.00
01/02/2012 3.00
01/03/2012 4.00
01/04/2012 5.00
01/05/2012 6.00
最后,Non-Atlantic Freight Charge
表 2 的不同行是什么样的?
表 3 - 不同的非大西洋运费
Non-Atlantic Freight Charge
2.00
3.00
4.00
5.00
6.00
现在,在 SQL 中,您根据您的 SELECT 子句指定您想要一个包含三列的报告。这是如何布置的:
SELECT DISTINCT
FH.ShipDate
, AVG(FH.[Dist Freight]) AS [Atlantic Freight Charge]
, (SELECT DISTINCT [Non-Atlantic Freight Charge]
FROM
(SELECT DISTINCT FH.ShipDate, AVG(FH.[Dist Freight]) AS [Non-Atlantic Freight Charge]
FROM dbo.vw_FreightHistory AS FH
WHERE VendorName != 'Atlantic Trucking'
GROUP BY ShipDate, VendorName) AS [Non-Atlantic Freight Charge])
您会看到第一列是ShipDate
,第二列是Atlantic Freight Charge
,第三列是每个与内部子查询不同Non-Atlantic Freight Charge
的查询。
为了让 SQL Server 正确地表示这一点,想象一下尝试将该查询的结果放在第一个表中。
所以对于表 1 的第一行:
ShipDate Atlantic Freight Charge
01/01/2012 1.00
我们需要添加一个列Non-Atlantic Freight Charge
,我们需要在其中存储表 3 中的查询结果:
| ShipDate | Atlantic Freight Charge | Non-Atlantic Freight Charge |
|---------------|-------------------------|---------------------------------|
| 01/01/2012 | 1.00 | | Non-Atlantic Freight Charge | |
| | | |-----------------------------| |
| | | | 2.00 | |
| | | | 3.00 | |
| | | | 4.00 | |
| | | | 5.00 | |
| | | | 6.00 | |
| | | |-----------------------------| |
|---------------------------------------------------------------------------|
哦哦。我们有一张桌子,在我们的桌子里面。
这就是问题所在,我们有一张桌子放在另一张桌子里面!
因此,您的问题有两种解决方案。您应该评估每个的性能。
第一种是使用称为公用表表达式或 CTE的功能来运行两个单独的查询并连接结果。
该查询将如下所示:
CTE 解决方案
; WITH Atlantic AS (
SELECT FH.ShipDate, AVG(FH.[Dist Freight]) AS [Atlantic Freight Charge]
FROM dbo.vw_FreightHistory as FH
WHERE VendorName = 'Atlantic Trucking'
GROUP BY ShipDate
)
, NonAtlantic AS (
SELECT FH.ShipDate, AVG(FH.[Dist Freight]) AS [Non-Atlantic Freight Charge]
FROM dbo.vw_FreightHistory as FH
WHERE VendorName != 'Atlantic Trucking'
GROUP BY ShipDate
)
SELECT COALESCE(Atlantic.ShipDate, NonAtlantic.ShipDate)
, ISNULL([Atlantic Freight Charge], 0) AS [Atlantic Freight Charge]
, ISNULL([Non-Atlantic Freight Charge], 0) AS [Non-Atlantic Freight Charge]
FROM Atlantic
FULL OUTER JOIN NonAtlantic
ON Atlantic.ShipDate = NonAtlantic.ShipDate
我做了一些更改,我需要指出:
- 我删除了“Order By”,一般来说,排序应该由消耗 SQL Server 数据的任何东西来完成,当您的客户端应用程序也可以这样做时,不要通过要求它订购某些东西来不必要地对服务器征税。
ORDER BY
无论如何,在公用表表达式中实际上是被禁止的,所以我必须将该子句移到最后。
- 我已将您的查询分成两部分,Atlantic 和 NonAtlantic,并使用 a
FULL OUTER JOIN
将它们连接起来,因此其中缺少的任何行仍会出现,但会显示为零。确保这是您想要的。
- 我使用 a
COALESCE
来确保如果有一天没有Atlantic Freight Charge
s 并且因此在Atlantic
CTE 中没有与该天对应的 ShipDate,那么它将使用NonAtlantic
CTE 中的日期。
它的工作方式是连接两个查询,如下所示:
ShipDate Atlantic Freight Charge | FULL OUTER JOIN | ShipDate Non-Atlantic Freight Charge
01/01/2012 1.00 | | NULL NULL
01/02/2012 1.00 | | NULL NULL
01/03/2012 1.00 | | NULL NULL
01/04/2012 1.00 | | 01/03/2012 2.00
01/05/2012 1.00 | | 01/04/2012 3.00
NULL NULL | | 01/05/2012 4.00
NULL NULL | | 01/06/2012 5.00
NULL NULL | | 01/07/2012 6.00
因此COALESCE
,ISNULL
允许我将其转换为一组数据,如下所示:
ShipDate Atlantic Freight Charge Non-Atlantic Freight Charge
01/01/2012 1.00 0.00
01/02/2012 1.00 0.00
01/03/2012 1.00 0.00
01/04/2012 1.00 2.00
01/05/2012 1.00 3.00
01/05/2012 0.00 4.00
01/06/2012 0.00 5.00
01/07/2012 0.00 6.00
然而,这可能不是最好的解决方案
这是最容易实现的,获取两个查询,运行它们,然后加入结果。但 SQL Server 支持聚合函数,可让您对结果进行分区。您可能有兴趣研究OVER 子句的语义,以了解有关如何仅在单个查询中运行报告的更多信息。我自己也实现了这样的查询,但通常使用SUM
s,而不是AVG
s。我会使用 OVER 子句提供解决方案的可能实现,但它可能有点过于复杂,我担心我会弄乱正确地平均结果。实际上,现在我考虑一下,这样的事情可能会很好:
SELECT FH.ShipDate
, AVG(CASE WHEN VendorName = 'Atlantic Trucking' THEN FH.[Dist Freight] ELSE NULL END) AS [Atlantic Freight Charge]
, AVG(CASE WHEN VendorName != 'Atlantic Trucking' THEN FH.[Dist Freight] ELSE NULL END) AS [Non-Atlantic Freight Charge]
FROM dbo.vw_FreightHistory as FH
GROUP BY ShipDate
ORDER BY ShipDate
但是我忘记了 AVG 是否计算空行。
无论如何,我希望我已经回答了您的问题,并帮助您理解了您的查询为何有问题。