分组使用哪条记录?

时间:2017-03-10 22:24:29

标签: sqlite join

下面的代码符合我的要求,但我不明白为什么,因此我不明白它是否可靠/正确。

我正在尝试计算每个动作的持续时间。每个操作都有一个开始时间,标记为操作列中的操作名称,结束时间标记为单词'完成'。如果两个操作具有不同的用户,则它们可以在时间上重叠,但不能与同一用户重叠。

我想搜索每个Action = 'Done'和具有相同用户的记录以及该记录之前的最长时间。我尝试在不同位置使用Max()来查找Start.Time最高Start.Time < End.Time,但它从未奏效。我在子查询中尝试使用Max(),但子查询中的StartStart中的Join不一样。所以我删除了Max(),我得到了我想要的东西。

为什么开始和结束记录正确匹配,而不是匹配每个用户的第一个和最后一个记录?

是否可靠地始终根据Order by子句选择组的最后一条记录?或者我只是幸运?

以下是创建表的代码,插入一些测试数据并执行查询:

CREATE TABLE Log (
    Time     DATETIME,
    User     CHAR,
    [Action] CHAR
);

insert into Log values('2017-01-01 10:00:00', 'Joe', 'Play');
insert into Log values('2017-01-01 10:01:00', 'Joe', 'Done');
insert into Log values('2017-01-01 10:02:00', 'Joe', 'Sing');
insert into Log values('2017-01-01 10:03:00', 'Joe', 'Done');
insert into Log values('2017-01-01 10:04:00', 'Ann', 'Play');
insert into Log values('2017-01-01 10:05:00', 'Joe', 'Play');
insert into Log values('2017-01-01 10:06:00', 'Ann', 'Done');
insert into Log values('2017-01-01 10:07:00', 'Joe', 'Done');
insert into Log values('2017-01-01 10:08:00', 'Ann', 'Play');
insert into Log values('2017-01-01 10:09:00', 'Ann', 'Done');

SELECT Start.*,
       [End].*,
       strftime('%s', [End].Time) - strftime('%s', Start.Time) AS Duration
  FROM Log AS Start
       JOIN
       Log AS [End] ON Start.User = [End].User AND 
                       Start.Time < [End].Time
 WHERE [End].[Action] = 'Done'
 GROUP BY [End].Time
 ORDER BY Duration DESC,
          Start.Time;

2 个答案:

答案 0 :(得分:2)

来自官方SQLite documentation for the SELECT statement

  

当聚合函数为任何一个时,会发生特殊处理   min()或max()。例如:

SELECT a, b, max(c) FROM tab1 GROUP BY a;
     

当min()或max()时   聚合函数用于聚合查询,所有裸列   在结果集中从输入行中获取值,该值也包含   最小值或最大值。所以在上面的查询中,&#34; b&#34;   输出中的列将是&#34; b&#34;的值。输入中的列   具有最大&#34; c&#34;的行值。如果两个人仍然存在歧义   或更多输入行具有相同的最小值或最大值或者是否   该查询包含多个min()和/或max()聚合   功能。只有内置的min()和max()函数才能以这种方式工作。

因此,如果您的查询中只有一个MINMAX调用,那么既不是聚合函数也不是GROUP BY列的所有其他选定列都将使用该行(或更多)准确地说, a 行,因为该列不一定是UNIQUE),其中出现最小值或最大值。

否则,它只返回组中某个任意行的值。

当然,这是一种特定于SQLite的行为,不属于标准SQL。例如,Microsoft SQL Server提供错误:

  

专栏&#39; b&#39;在选择列表中无效,因为它不包含在聚合函数或GROUP BY子句中。

为避免混淆并最大限度地提高可移植性,我建议避免使用此“功能”。

编辑:这就是您需要的吗?

SELECT
    User,
    Start.Action AS Action,
    MAX(Start.Time) AS StartTime,
    End.Time AS EndTime,
    STRFTIME('%s', End.Time) - STRFTIME('%s', MAX(Start.Time)) AS Duration
FROM Log Start INNER JOIN Log End USING (User)
WHERE Start.Action != 'Done' AND End.Action = 'Done' AND Start.Time <= End.Time
GROUP BY User, Start.Action, End.Time

答案 1 :(得分:1)

如果没有max(),则从某个随机行中获取值。在当前的SQLite实现中,这是数据库在处理组时最后要查看的行,因此在使用升序时间戳存储行时,这会导致所需的行。但是,这种行为无法保证,即使在当前版本中,对查询的任何更改都可能使数据库以不同的顺序读取表。

从具有下一个最大时间戳的行查找某个值的最可靠方法是使用相关子查询:

SELECT *,
       strftime('%s', EndTime) - strftime('%s', StartTime) AS Duration
FROM (SELECT Time AS StartTime,
             (SELECT Time
              FROM Log AS L2
              WHERE L2.User   = Log.User
                AND L2.Time   > Log.Time
                AND L2.Action = 'Done'
              ORDER BY L2.Time ASC
              LIMIT 1
             ) AS EndTime,
             User,
             Action
      FROM Log
      WHERE Action != 'Done');