【问题标题】:How to get DISTINCT rows COUNTed with different conditions in a single SELECT statement?如何在单个 SELECT 语句中使用不同条件对 DISTINCT 行进行计数?
【发布时间】:2013-04-13 00:40:18
【问题描述】:

我有一张桌子,上面有来自不同用户的订单。每个用户(由电子邮件地址标识)可以有任意数量的订单。我没有额外的用户表,只有一个普通的 OrderData 表。我想要的听起来很简单:

  1. 用户总数
  2. XYZ 用户数
  3. 非 XYZ 用户数

其中 1. 是 2. 和 3. 的总和。“XYZ”用户由以“@xyz.com”结尾的电子邮件地址定义。

我想要一个查询,在三列中返回三个值。我目前拥有的是:

SELECT 
    (
    SELECT COUNT(DISTINCT User_EmailAddress)
    FROM OrderData
    WHERE User_EmailAddress IS NOT NULL
    AND RequestTime >= @RequestTimeFrom
    AND RequestTime  < @RequestTimeTo
    ) AS [Total Users],
    (
    SELECT COUNT(DISTINCT User_EmailAddress)
    FROM OrderData
    WHERE User_EmailAddress IS NOT NULL
    AND User_EmailAddress LIKE '%@xyz.com'
    AND RequestTime >= @RequestTimeFrom
    AND RequestTime  < @RequestTimeTo
    ) AS [XYZUsers],
    (
    SELECT COUNT(DISTINCT User_EmailAddress)
    FROM OrderData
    WHERE User_EmailAddress IS NOT NULL
    AND User_EmailAddress NOT LIKE '%@xyz.com'
    AND RequestTime >= @RequestTimeFrom
    AND RequestTime  < @RequestTimeTo
    ) AS [Non-XYZ Users]

它返回正确的结果集:

Total Users | XYZ Users | Non-XYZ Users
------------+-----------+--------------
        123 |        23 |           100

有没有更好的方法来编写这个查询,而不是基本上用相同的代码编写三个相似的查询?

【问题讨论】:

    标签: sql sql-server


    【解决方案1】:

    这是一种减少冗余代码并避免多次扫描数据问题的解决方案。由于您真正关心的是计数,因此您甚至不需要返回任何实际数据,并且您可以将几乎所有逻辑折叠成一个 CASE 表达式:

    ;WITH x AS 
    (
      SELECT isxyz = CASE WHEN User_EmailAddress LIKE '%@xyz.com' THEN 1 ELSE 0 END
      FROM dbo.OrderData
      WHERE User_EmailAddress IS NOT NULL
        AND RequestTime >= @RequestTimeFrom
        AND RequestTime  < @RequestTimeTo
        GROUP BY User_EmailAddress
    )
    SELECT 
      TotalUsers      = COUNT(isxyz),
      XYZUsers        = SUM(isxyz),
      [Non-XYZ Users] = COUNT(NULLIF(isxyz,1))
    FROM x;
    

    诀窍是利用聚合来发挥自己的优势。由于1/0 输出不是真正的BIT 列,因此您可以通过SUM 获取1s 的计数。而相反的情况可以通过将1s 更改为NULL 来实现,因为NULLs 会被COUNT 忽略。

    【讨论】:

    • 谢谢,这正是我想要的。除了你不需要a = User_EmailAddress, 行。
    • @Tobias 我最初在输出中有COUNT(a),如果您想测试 CTE 的结果以防聚合看起来不像给您的结果,它会很有用预计。我已将其删除。
    【解决方案2】:

    bluefeet 提供的解决方案仍有一个缺点——每个 DISTINCT 聚合(在本例中为 COUNT)分别使用输入行流。这可能会导致对基表进行单独的扫描/查找 - 每个不同的聚合一组。尽管有时优化器可以将基表查找/扫描假脱机到工作表中,然后计算聚合。

    一种更注重性能的方法(在这种特定情况下)是将总计数放入一个变量中,然后将 xyz 计数放入另一个变量中,然后使用标量算术推导出第三个值(非 xyz) .这样你就可以避免一个假脱机重放(或者更糟糕的是基表查找/扫描操作符。)

    阅读 Paul White 的博客了解更多信息:http://web.archive.org/web/20170606142356/http://sqlblog.com/blogs/paul_white/archive/2011/12/04/is-distinct-aggregation-still-considered-harmful.aspx

    【讨论】:

    • 我的回答没有出现这个问题(只有一次扫描、一个流和一种不同的排序),也不需要将计数放入变量中。
    猜你喜欢
    • 2011-07-20
    • 1970-01-01
    • 1970-01-01
    • 2017-09-06
    • 2012-02-20
    • 1970-01-01
    • 2019-04-06
    • 1970-01-01
    相关资源
    最近更新 更多