SELECT子查询的“子查询返回的值超过1”

时间:2013-07-19 16:11:26

标签: sql sql-server sql-server-2008 subquery

我正在使用MS SQL Server Management Studio 2008.我遇到编写子查询的问题。这是整个查询,如下所示:

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])
FROM dbo.vw_FreightHistory as FH
WHERE VendorName = 'Atlantic Trucking'
GROUP BY ShipDate, VendorName
ORDER BY ShipDate

当我添加第二个子查询时,问题首先出现了。第一个子查询没有返回任何错误,但它显示了每个ShipDate记录中“Dist Freight”的全部平均总和,而不是仅仅该ShipDate的平均值。我编写了第二个子查询来尝试修复它,但现在我收到了这个错误:

  

Msg 512,Level 16,State 1,Line 1
  子查询返回的值超过1。当子查询遵循=,!=,<,< =,>,> =或子查询用作表达式时,不允许这样做。

如果我要澄清任何事情,请告诉我。

3 个答案:

答案 0 :(得分:4)

我认为你想要的是这样的:

 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

答案 1 :(得分:4)

问题是您有一个子查询返回多行,并且您尝试将其存储为单行/列组合中的值。

要理解为什么会出现这种情况,让我们看一下外部查询的结果,即“大西洋运费”:

表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

最后,表2的不同Non-Atlantic Freight Charge行是什么样的?

表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                        | |
|               |                         | |-----------------------------| |
|---------------------------------------------------------------------------|

呃哦。 我们的桌子里有一张桌子

那就是问题,我们在另一张桌子里面有一张桌子!

因此,您的问题有两个解决方案。你应该评估每个人的表现。

第一种方法是使用名为Common Table Expressions or CTEs的功能运行两个单独的查询并加入结果。

该查询将如下所示:

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

我做了一些改变,我需要指出:

  1. 我删除了“Order By”,一般情况下,订购应该通过消耗SQL Server数据的任何方式来完成,不要通过要求它在您的客户端应用程序只能执行此操作时对其进行排序而不必要地对服务器征税同样。
  2. 无论如何,
  3. ORDER BY在公用表表达式中实际上是被禁止的,因此我必须将该子句移到最后。
  4. 我已将您的查询分为两部分,Atlantic和NonAtlantic,并使用FULL OUTER JOIN连接它们,因此在一个中缺少的任何行仍将显示,但它将显示为零。确保这是你想要的。
  5. 我使用COALESCE来确保如果有一天没有Atlantic Freight Charge s,因此Atlantic CTE中没有与当天相对应的ShipDate,那么它将会使用NonAtlantic CTE中的日期。
  6. 这种方式的工作方式是它连接两个查询:

    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
    

    因此COALESCEISNULL允许我将其转换为一组数据,如下所示:

    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 Clause的语义,以便了解有关如何仅在单个查询中运行报表的更多信息。我自己实现了类似的查询,但通常使用的是SUM,而不是AVG。我会提供一个使用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是否计算了空行。

    无论如何,我希望我已经回答了你的问题,并帮助你理解为什么你的查询有问题。

答案 2 :(得分:0)

如果返回多行,这取决于您希望在单列中显示的内容?

如果您需要总计费用,请在多行返回子查询周围使用SUM()

SELECT DISTINCT FH.ShipDate, AVG(FH.[Dist Freight]) AS [Atlantic Freight Charge], 
SUM(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]))
FROM dbo.vw_FreightHistory as FH
WHERE VendorName = 'Atlantic Trucking'
GROUP BY ShipDate, VendorName
ORDER BY ShipDate