【问题标题】:Two sums and three tables in one SQLite query一个 SQLite 查询中的两个总和和三个表
【发布时间】:2014-01-24 09:22:48
【问题描述】:

我有三个表:活动、动作(每个动作是一个活动的一次执行)和照片(每个动作都可以附加照片)。

这里是an SQL Fiddle for this

现在我想按降序检索活动,并且对于每个活动,我想要花费在它上的总时间和附加到它的总照片。使用最后一个动作的停止时间计算的活动顺序。

例如对于以下数据

activities
------------------
 _id |   title
------------------
   1 | Activity 1
   2 | Activity 2
   3 | Activity 3
   4 | Activity 4

actions
-------------------------------------------------------------
_id | activity_id |    date_started     |     date_stopped
-------------------------------------------------------------
  1 |           1 | 2014-01-23 20:45:03 | 2014-01-23 20:45:24
  2 |           2 | 2014-01-23 20:45:27 | 2014-01-23 20:45:29
  3 |           3 | 2014-01-23 20:45:31 | 2014-01-23 20:45:43
  4 |           1 | 2014-01-23 20:45:46 | 2014-01-23 20:45:48
  5 |           4 | 2014-01-23 20:45:50 | 2014-01-23 20:46:19

photos
--------------------------------------------------------
_id | action_id |      date_taken     |     path
--------------------------------------------------------
  1 |         1 | 2014-01-23 20:45:11 | 758712034.jpg
  2 |         1 | 2014-01-23 20:45:21 | 537444469.jpg
  3 |         3 | 2014-01-23 20:45:39 | 28884579.jpg
  4 |         5 | 2014-01-23 20:45:58 | 1519722792.jpg
  5 |         5 | 2014-01-23 20:46:08 | 298808374.jpg
  6 |         5 | 2014-01-23 20:46:15 | 2059925529.jpg

我希望通过此查询获得所需的数据:

SELECT
    activityId, title, sum(seconds) AS totalSeconds, sum(cnt) AS totalPhotos 
FROM
    (
        SELECT
            activities._id AS activityId, activities.title AS title,
            actions._id AS actionId,
            strftime("%s", ifnull(actions.date_stopped, 'now')) -
            strftime("%s", actions.date_started) AS seconds,
            count(photos._id) AS cnt
        FROM
            activities JOIN actions ON activities._id = actions.activity_id
            LEFT OUTER JOIN photos ON photos.action_id = actions._id
        GROUP BY 1,2,3,4
        ORDER BY actionId DESC
    )
GROUP BY 1

但是,不幸的是,它给出了这样的结果:

activityId |   title    | totalSeconds | totalPhotos 
--------------------------------------------------------
         1 | Activity 1 |           23 |           2
         2 | Activity 2 |            2 |           0
         3 | Activity 3 |           12 |           1
         4 | Activity 4 |           29 |           3

我正在努力解决这个问题(请参阅操作表中activity_id 的顺序):

activityId |   title    | totalSeconds | totalPhotos 
--------------------------------------------------------
         4 | Activity 4 |           29 |           3
         1 | Activity 1 |           23 |           2
         3 | Activity 3 |           12 |           1             
         2 | Activity 2 |            2 |           0

如何更改我的查询以获得我想要的?

【问题讨论】:

    标签: sql sqlite join count aggregate-functions


    【解决方案1】:

    感谢您设置 SQL Fiddle。这让事情变得更容易)。

    您正朝着正确的方向前进 - 您可能只需在查询末尾添加ORDER BY totalSeconds DESC。但是,您的查询有几个问题,可能会更好:

    SELECT Activities._id, Activities.title, Actions.totalSeconds, Actions.totalPhotos
    FROM Activities
    JOIN (SELECT Actions.activity_id, 
                 SUM(STRFTIME("%s", COALESCE(Actions.date_stopped, 'now')) 
                                - STRFTIME("%s", Actions.date_started)) AS totalSeconds, 
                 SUM(COALESCE(Photos.photoCount, 0)) as totalPhotos,
                 MAX(COALESCE(Actions.date_stopped, DATETIME('now'))) as mostRecent
          FROM Actions
          LEFT JOIN (SELECT action_id, COUNT(*) as photoCount
                     FROM Photos
                     GROUP BY action_id) Photos
                 ON Photos.action_id = Actions._id
          GROUP BY Actions.activity_id) Actions
       ON Actions.activity_id = Activities._id
    ORDER BY Actions.mostRecent DESC
    

    (和working result fiddle

    具体来说:

    1. 您按所有列分组(在内部查询中)。在这种情况下,您要么想要DISTINCT(从概念上/逻辑上),要么最好将查询更改为更小。请注意,通过像我在这里使用的表格进行聚合,更有可能使用索引。
    2. 您按列编号进行分组:始终拼出您想要的列。在极端情况下,如果有人更改了SELECT 列表中列的顺序,但没有更改了GROUP BY,您的结果可能会以您意想不到的方式发生变化,并且会收到错误。
    3. 您的内部查询有一个ORDER BY。这是完全没有必要的,并且会迫使引擎做额外的工作。
    4. 您的外部GROUP BY 仅引用了一列,但有一列未聚合/分组。在这种情况下,它给出了正确的结果,但这是一个危险的特性;如果可能有多个值,则无法确定选择哪一个。默认情况下避免这种情况。
    5. 在可用的情况下首选 SQL 标准函数(除非出于特定性能原因)-IFNULL() 并非在所有平台上,但COALESCE 。除非日期/时间数学(这通常取决于 RDBMS),否则此查询将适用于所有平台。

    (顺便说一句,我对 SQLite 缺少日期/时间/时间戳类型感到恼火,但这并不是你的错……)

    【讨论】:

    • 感谢您指出查询中的问题。他们对我来说是合理的。不幸的是,我认为通过Actions.totalSeconds 订购是错误的。它确实为测试数据提供了所需的结果,但动作或活动的持续时间与订单无关。活动停止的时间是相关的(最后停止的应该是结果中的第一个)
    • 所以你知道,这是你第一次给出顺序应该是什么;这就是为什么@Ondemannen 和我都给出了我们所做的答案。正在更新答案。
    • 我确实说过“使用最后一个动作的停止时间计算的活动顺序。”,但可能不是那么明显。无论如何,谢谢你的更新,它解决了这个问题。你不仅解决了这个问题,还给出了一些很好的建议。谢谢。
    【解决方案2】:
    SELECT
        activityId, title, sum(seconds) AS totalSeconds, sum(cnt) AS totalPhotos 
    FROM
        (
            SELECT
                activities._id AS activityId, activities.title AS title,
                actions._id AS actionId,
                strftime("%s", ifnull(actions.date_stopped, 'now')) -
                strftime("%s", actions.date_started) AS seconds,
                count(photos._id) AS cnt
            FROM
                activities JOIN actions ON activities._id = actions.activity_id
                LEFT OUTER JOIN photos ON photos.action_id = actions._id
            GROUP BY 1,2,3,4
            ORDER BY actionId DESC
        )
    GROUP BY 1
    ORDER BY seconds DESC;
    

    返回:

    4|Activity 4|29|3
    1|Activity 1|23|2
    3|Activity 3|12|1
    2|Activity 2|2|0
    

    但我可能误读了这个问题,因为我添加的唯一内容是 ORDER BY seconds DESC 行。如果您从 seconds 更改为 cnt,那么您将收到相同的结果。

    【讨论】:

    • 很遗憾,这是不正确的。秒数与我想要获得的订单完全无关。
    • seconds改成cnt怎么样?
    • 它们也与订单无关。
    • 如果在内部选择中添加 date_stopped 作为列,在外部选择中添加 get MAX(date_stopped) 会更容易吗?
    猜你喜欢
    • 2023-03-24
    • 2019-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-26
    • 2014-06-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多