【问题标题】:Most efficient way to count number of matching rows with multiple criterias at once一次计算具有多个条件的匹配行数的最有效方法
【发布时间】:2016-03-15 23:44:01
【问题描述】:

我有一个非常大的表(称为 device_operation 有 5000 万行),其中包含产品在其生命周期中的所有操作(例如“开始”、“停止”、“重新填充”、...”和状态这些操作(行状态:已完成,失败),以及关联设备的 ID(行 device_id)和每个操作的时间戳(行 create_date)。

类似这样的:

/------+-----------+------------------+---------\
|   ID | Device ID | Create_Date      |  Status |
+------+-----------+------------------+---------+
|    1 |         1 | 2012-03-04 01:43 | Success |
|    2 |         4 | 2012-04-04 02:34 |  Failed |
|    3 |         9 | 2013-01-01 01:23 |  Failed |
|    4 |         4 | 2013-12-12 12:34 | Success |
|    5 |        23 | 2014-02-01 03:45 | Success |
|    6 |         1 | 2014-05-03 08:34 |  Failed |
\------+-----------+------------------+---------/

我还有另一个表(称为订阅),它告诉我产品的保修开始时间(行 create_date)(行 device_id)。保修期为一年。

/-----------+------------------\
| Device ID |      Create_Date |
+-----------+------------------+
|         2 | 2011-04-03 05:00 |
|         4 | 2012-03-05 03:45 |
|         5 | 2012-03-05 06:07 |
|       ... |              ... |
\-----------+------------------/

我正在使用 PostgreSQL。

我想做以下事情:

  • 列出在给定日期 (2014-07-06) 之前至少有一次成功操作的所有设备 ID

对于这些设备中的每一个,计数:

  • 在该日期 + 2 天 (2014-07-08) 之后的失败操作次数,并且尝试操作时设备在保修期内
  • 在该日期 + 2 天 (2014-07-08) 之后的失败操作次数,并且在尝试操作时设备不在保修范围内
  • 该日期之后成功操作的次数(设备是否在保修期内)

我在以下方面取得了一些有限的成功(为了便于阅读,查询已被稍微简化 - 还涉及到其他连接以获取订阅表,以及将设备包含在列表中的其他条件):

SELECT distinct device_operation.device_id as did, subscription.create_date,
(
    SELECT COUNT(*)
    FROM device_operation dop
    WHERE dop.device_id = device_operation.device_id and
    dop.create_date > '2014-07-08' and
    dop.status = 'Success'
) as success,
(
    SELECT COUNT(*)
    FROM device_operation dop2
    WHERE
    dop2.device_id = subscription.device_id and
    dop2.create_date > '2014-07-08' and
    dop2.status = 'Failed' and
    dop2.create_date <= subscription.create_date + interval '1 year'
) as failed_during_warranty,
(
    SELECT COUNT(*)
    FROM device_operation dop2
    WHERE
    dop2.device_id = subscription.device_id and
    dop2.create_date > '2014-07-08' and
    dop2.status = 'Failed' and
    dop2.create_date > subscription.create_date + interval '1 year'
) as failed_after_warranty,
FROM device_operation, subscription
WHERE
device_operation.status = 'Success' and -- list operations which are successful
device_operation.create_date <= '2014-07-06' and -- list operations before that date
device_operation.device_id = subscription.device_id -- get warranty start for each operation
ORDER BY success DESC, failed_during_warranty DESC, failed_after_warranty DESC

您可以猜到,它太慢了,我无法运行查询。但是,它可以让您了解结构。

我尝试使用 NULLIF 将请求合并为一个,希望它能让 PostgreSQL 只列出一次子查询而不是 3 个,但它返回“子查询必须只返回一列”:

SELECT distinct device_operation.device_id as did, subscription.create_date,
(
SELECT COUNT(NULLIF(dop2.status != 'Success', true)) as completed, 
    COUNT(NULLIF(dop2.status != 'Failed' or not (dop2.create_date <= subscription.create_date + interval '1 year'), true)) as failed_in_warranty, 
    COUNT(NULLIF(dop2.status != 'Failed' or     (dop2.create_date <= subscription.create_date + interval '1 year'), true)) as failed_after_warranty
FROM device_operation dop2
WHERE
    dop2.device_id = device_operation.device_id and
    dop2.device_id = subscription.device_id and
    dop2.create_date > '2014-07-08'
) as subq
FROM device_operation, subscription
WHERE
device_operation.status = 'Success' and -- list operations which are successful
device_operation.create_date <= '2014-07-06' and -- list operations before that date
device_operation.device_id = subscription.device_id -- get warranty start for each operation
ORDER BY success DESC, failed_in_warranty DESC, failed_outside_warranty DESC

我也尝试将子查询移动到 FROM 子句,但这不起作用,因为我需要为主查询的每一行运行子查询(或者我是否?也许有更好的方法)

我期望的是这样的:

/-----------+---------+------------------------+-----------------------\
| Device ID | Success | Failed during warranty | Failed after warranty |
+-----------+---------+------------------------+-----------------------+
|    194853 |      10 |                      0 |                     0 |
|      7853 |       5 |                      5 |                     0 |
|      5848 |       3 |                      0 |                    56 |
|   8546455 |       0 |                     45 |                     0 |
|       102 |       0 |                      4 |                     1 |
|  69329548 |       0 |                      0 |                     9 |
|        17 |       0 |                      0 |                     0 |
\-----------+---------+------------------------+-----------------------+

有人可以帮我找到最有效的方法吗?

编辑:极端情况:您可以认为所有设备都有订阅条目。

非常感谢!

【问题讨论】:

  • 我想你可以从stackoverflow.com/questions/14048098/…得到提示
  • 正如我所说:“我还有另一个表(称为订阅),它告诉我产品(行 device_id)的保修何时开始(行 create_date)。保修期为一年。”跨度>
  • 关于提供的链接,它没有帮助,除非我没有看到任何东西。它准确地描述了我在第二次尝试中尝试做的事情,并且我已经描述了为什么它在我的问题中不起作用(在子查询中不能返回超过一行)。我相信,我必须从我必须获取的信息的结构中处理一个子查询。
  • 我认为这个SELECT distinct device_operation.device_id as did, subscription.create_date, 应该取自不同的表,例如devices。 BTW,你检查过执行计划吗?

标签: sql postgresql


【解决方案1】:

我认为您只需要条件聚合。我发现数据结构和逻辑有点难以理解,但我认为以下基本上是您需要的:

SELECT d.device_id,
       SUM(CASE WHEN d.status = 'Failed' AND d.create_date <= '2014-07-06' + interval '2 day'
                THEN 1 ELSE 0
           END) as NumFails,
       SUM(CASE WHEN d.status = 'Failed' AND d.create_date <= '2014-07-06' + interval '2 day' AND
                     d.create_date > s.create_date + interval '1 year'
                THEN 1 ELSE 0
           END) as NumFailsNoWarranty,
       SUM(CASE WHEN d.status = 'Success' AND d.create_date <= '2014-07-06' + interval '2 day'
                THEN 1 ELSE 0
           END) as NumSuccesses
FROM device_operation d JOIN
     subscription s
     ON d.device_id = s.device_id
GROUP BY d.device_id
HAVING SUM(CASE WHEN d.status = 'Success' AND d.create_date <= '2014-07-06' THEN 1 ELSE 0 END) > 0;

【讨论】:

  • 嗯,我不认为这样做(我试过)。让我重新表述我的需求: - 我需要列出在某个日期之前至少有一次成功操作的所有设备。然后,对于这些设备中的每一个,我需要查看在该日期之后它们发生了什么:在该日期之后有多少操作成功?有多少在保修期内失败?之后失败了多少?这就是我使用子查询的原因,因为这是执行我上面所说的直接方法。但是,性能损失这么大我做不到!而且我确信必须有另一种方法来做到这一点。感谢收看!
猜你喜欢
  • 2011-01-23
  • 1970-01-01
  • 2021-01-29
  • 2016-10-29
  • 2017-06-24
  • 2021-06-15
  • 2011-03-22
  • 2022-01-24
相关资源
最近更新 更多