【问题标题】:Count in-between rows from same column values计算来自相同列值的中间行
【发布时间】:2017-02-03 21:10:24
【问题描述】:

在 Microsoft SQL Server 中,如何根据同一列的值计算中间行数。

示例

/-----------------------------------------\
| ID ---------- Event -------- UserID ----|
| 1 ----------- START -------- 000001 ----|
| 2 ----------- START -------- 000002 ----|
| 3 ----------- END   -------- 000001 ----|
| 4 ----------- PL    -------- 000002 ----|
| 5 ----------- END   -------- 000002 ----|
\-----------------------------------------/

考虑UserID 000002,它有 3 行,ID 是 2、4 和 5。 根据来自此link 的查询,我可以获得STARTEND 时间,但是如何获取每个@ 的STARTEND 之间的行数 987654328@

预期结果

/-------------------------------------------------------\
| UserID ------------------------- Row Count -----------|
| 000001 ------------------------- 2         -----------|
| 000002 ------------------------- 3         -----------|
\-------------------------------------------------------/

【问题讨论】:

  • 不简单,请阅读
  • @aioracle:-您的问题不清楚...因为您告诉过“如何获取每个 USERID 的 START 和 END 之间的行数”以及您的预期结果也不同..请清楚你想要什么..?
  • 在开始和结束之间意味着应该有某种方式来对行进行排序。您使用的是什么版本的 sql server?
  • 一个用户可以有多个开始吗?不止一个结局?开始前的记录?结束后的记录?如果是这样,在这些情况下该怎么办?
  • 正如您所说的 between 其他行,我想这里的 ID 不仅仅是一些用于识别记录的技术 ID,您还可以考虑 ID 较小的记录 previous 到具有更高 ID 的记录。这是正确的吗?

标签: sql sql-server


【解决方案1】:

SELECT USERID , sum(CASE WHEN MainT.ID BETWEEN StartT.ID AND EndT.ID THEN 1 ELSE 0 END) AS RowCount FROM Table1 AS MainT INNER JOIN Table1 AS StartT ON MainT.USERID = StartT.USERID AND StartT.event = 'START' INNER JOIN Table1 AS EndT ON MainT.USERID = EndT.USERID AND EndT.event = 'END' GROUP BY USERID

这应该会产生所需的输出,假设如下:

  • ID 是(自动)递增的,因此用户的 START 事件具有最低 ID。希望在真实数据中可以使用时间戳。
  • 每个相关用户都有一个 START 和零个或一个 END 事件,但不包括在数据集之外开始或结束的用户。

回应评论:

通常,您不能为未分组的字段显示“该”值,因为可能有多个值在起作用。这意味着您需要告诉 SQL Server 如何使用某种聚合函数来处理这些值。

如果您确定每个组只有一个可能的值,您可以作弊并取 min(event) 或 max(event)。这会为您提供组中出现的按字母顺序的第一个或最后一个值。但是,如果您稍后在该表中包含更多事件,这有风险,并且可能会导致问题。

如果只有少数,您可以按事件类型拆分计数:

sum(CASE WHEN **MainT.event ='EventA' AND** MainT.ID BETWEEN StartT.ID AND EndT.ID THEN 1 ELSE 0 END) AS EventACount, sum(CASE WHEN **MainT.event ='EventB' AND** MainT.ID BETWEEN StartT.ID AND EndT.ID THEN 1 ELSE 0 END) AS EventBCount,

最后,您可以使用某种列表聚合,将组中出现的所有事件类型连接起来。 This question 对此进行了更详细的说明。

【讨论】:

  • 在这些假设下,第二次连接应该是左连接。
  • 我排除了还没有 END 记录的用户,因此是内部连接。如果需要“计数到现在”,它应该是左连接,并且案例应该是“CASE WHEN MainT.ID BETWEEN StartT.ID AND ISNULL(EndT.ID,MaintT.ID) THEN 1 ELSE 0 END跨度>
  • 我明白了。我认为这个问题需要更多的解释。你的回答对我来说似乎很好。 +1
  • @Cyrus 该解决方案按预期工作。是否有可能在另一列中显示 START 和 END 之间的事件?
  • @Cyrus,我无法标记 2 个答案。您的解决方案也适用于我。
【解决方案2】:

场景没有很好地定义,您可以在解决方案的不同复杂性中看到它。

这将处理简单的用例。

select      UserID
           ,count(*)    as cnt
from        mytable

这将处理复杂的用例。

select      UserID
           ,min(ID)     as from_ID
           ,max(ID)     as to_ID
           ,count(*)    as events

from       (select      UserID,ID,Event
                       ,    count(case when Event in ('START','END') then 1 end) over 
                            (
                                partition by    UserID 
                                order by        Id 
                                rows            unbounded preceding
                            )   
                        -   case when Event = 'END' then 1 else 0 end   as group_seq

            from        mytable
            ) t

group by    UserID
           ,group_seq

having      min(case when Event = 'START' then 1 end) = 1

order by    UserID
           ,from_id

【讨论】:

  • 复杂用例的解决方案为我提供了解决方案,但是,如果我想知道 START 和 END 之间的事件值是什么,我该怎么做呢?
  • 您期望看到的结果如何?
  • 附言。如果您只运行内部查询,您会看到每组事件(START 到 END)都有自己的group_seq
  • 如果我想显示为 /---------------------------------- ----------------------------------------------------\ | UserID -------- Row Count -------- valuebetweenS_E ----- | | 000001 -------- 2 ------------ NULL --------- | | 000002 -------- 3 ----------- PL --------------------------| \-------------------------------------------------- -------------------------/
  • 可以有多个值吗? (P.s.您的原帖已经回复,请采纳)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-05-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-11
  • 2013-08-05
  • 1970-01-01
相关资源
最近更新 更多