【问题标题】:Merge groups of consecutive rows in T-SQL and sum values from each group合并 T-SQL 中的连续行组,并对每个组中的值求和
【发布时间】:2019-09-19 13:18:06
【问题描述】:

2019 年 10 月 8 日更新:

@Gordon Linoff:我已尝试应用您的解决方案,但我意识到它没有按预期工作。我在这里添加了一个带有 cmets 预期结果的示例 (https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=1b486476d6aeab25997f25e66ee455e9),如果您能帮助我,我将不胜感激。

--

我有一个带有架构的事务表:

CREATE TABLE Transactions (Id int IDENTITY, SessionId int, TransactionType varchar(50), DateTimeEnd datetime, DateStart datetime, Rank int);

以下是一些行示例:

INSERT INTO Transactions (Id, SessionId, TransactionType, DateTimeEnd, DateStart, Rank)
VALUES
 (1, 1, 'Deposit',    '2017-01-20T11:16:33Z', '2017-01-20T11:16:33Z', 600),
 (2, 1, 'Withdrawal', '2017-01-21T11:16:33Z', '2017-01-20T11:16:33Z', 100),
 (3, 2, 'Deposit',    '2017-02-23T11:16:33Z', '2017-02-23T11:16:33Z', 500),
 (4, 1, 'Withdrawal', '2017-01-24T11:16:33Z', '2017-01-21T11:16:33Z', 150),
 (5, 1, 'Withdrawal', '2017-01-26T11:16:33Z', '2017-01-24T11:16:33Z', 150),
 (6, 2, 'Withdrawal', '2017-02-27T11:16:33Z', '2017-02-23T11:16:33Z', 200),
 (7, 1, 'Withdrawal', '2017-01-28T11:16:33Z', '2017-01-26T11:16:33Z', 10),
 (8, 1, 'Withdrawal', '2017-01-30T11:16:33Z', '2017-01-28T11:16:33Z', 10),
 (9, 1, 'Withdrawal', '2017-01-31T11:16:33Z', '2017-01-30T11:16:33Z', 10);

我想要的是一个 T-SQL 查询,用于按 SessionId、TransactionType 和每个组合并连续行组,以仅保留具有最小 DateTimeEnd 的行。此外,保留的行的 Rank 值必须是来自组的行的 Rank 值的总和。 T-SQL 查询需要在 Microsoft Azure SQL 数据仓库的 MS SQL Server 中运行。

期望的结果:

|    Id    |     SessionId    | Transaction |       DateTimeEnd  |      DateStart     |   Rank  |
|----------|------------------|-------------|--------------------|--------------------|---------|
|    1     |         1        |      Deposit|2017-01-20T11:16:33Z|2017-01-20T11:16:33Z|   600   |
|    2     |         1        |   Withdrawal|2017-01-21T11:16:33Z|2017-01-20T11:16:33Z|   100   |
|  4       |         1        |   Withdrawal|2017-01-24T11:16:33Z|2017-01-21T11:16:33Z|   300   |
|  7       |         1        |   Withdrawal|2017-01-28T11:16:33Z|2017-01-26T11:16:33Z|    30   |
|    3     |         2        |      Deposit|2017-02-23T11:16:33Z|2017-02-23T11:16:33Z|   500   |
|    6     |         2        |   Withdrawal|2017-02-27T11:16:33Z|2017-02-23T11:16:33Z|   200   |

我尝试了很多方法都无法实现。

【问题讨论】:

  • 其实我是通过Microsoft Azure SQL Data Warehouse - 10.0.9999.0访问数据库的。但是,我不知道底层 MS SQL Server 的版本。另外,我通过 python 使用 Microsoft ODBC Driver 17 for SQL Server 连接到它。
  • SELECT @@VERSION 会告诉你。如果是Azure SQL Server,应该是最新的release版本(2017最新的CU iirc)。
  • Microsoft Azure SQL 数据仓库 - 10.0.9999.0 2019 年 8 月 16 日 21:18:18 版权所有 (c) Microsoft Corporation
  • 哦,我的错,你说的是 Azure 数据仓库。那么“连续行”是什么意思?只是所有具有相同 SessionId 的行?或者您的意思是按日期字段之一顺序排列?您可以将预期结果添加到您的帖子中吗?
  • 为什么输出中有id 4 和7 的行?如果我按照你的逻辑,我认为它们不应该在那里,因为 id 2 的行具有相同的 SessionIdTransaction 以及更早的 DateTimeEnd

标签: sql sql-server tsql azure-sql-data-warehouse


【解决方案1】:

正如 GMB 指出的那样,这是一个孤岛问题。因为要保留第一行,所以我将建议使用lag() 方法,而不是行号的差异:

SELECT SessionId, TransactionType, DateTimeEnd,DateStart, sumRank
FROM (SELECT t.*,
             SUM(Rank) OVER (PARTITION BY SessionId, TransactionType, grp) as sumRank
      FROM (SELECT t.*,
                   SUM(CASE WHEN prev_st_id = prev_id THEN 0 ELSE 1 END) OVER (ORDER BY id) as grp
            FROM (SELECT t.*,
                         LAG(id) OVER (PARTITION BY SessionId, TransactionType ORDER BY id) as prev_st_id,
                         LAG(id) OVER (PARTITION BY SessionId ORDER BY id) as prev_id
                  FROM Transactions t
                 ) t
           ) t
     ) t
WHERE prev_st_id <> prev_id OR prev_st_id IS NULL;

这是做什么的?

  • 最内层的子查询计算整体和会话/事务类型的 id 滞后。这使用了id,因为它看起来比日期/时间更稳定(其中一列中有重复的日期/时间值)。
  • 当 id 不同时,就会识别出一个新岛。累积总和可识别组。
  • 然后,此grp 用于使用窗口函数计算整个组的值。
  • 然后,外部查询只过滤到每个组中的第一行。

Here 是一个 dbfiddle。

【讨论】:

  • 我已经尝试应用您的解决方案,但我意识到它没有按预期工作。我在这里添加了一个带有 cmets 预期结果的示例 (dbfiddle.uk/…),如果您能帮助我,我将不胜感激。
  • @EfstathiosChatzikyriakidis 。 . .我认为是因为最里面的子查询中的一个窗口函数丢失了PARTITION BY sessionid
【解决方案2】:

这是一个差距和岛屿的变体。

我会这样处理它:

1) 首先,识别和合并记录组。以下查询为您提供每个组的最小组DateTimeEnd,以及排名总和

SELECT 
    SessionId, 
    TransactionType, 
    SUM(Rank) SumRank, 
    MIN(DateTimeEnd) MinDateTimeEnd
FROM (
    SELECT 
        t.*,
        ROW_NUMBER() OVER(ORDER BY DateTimeEnd) rn1,
        ROW_NUMBER() OVER(PARTITION BY SessionId, TransactionType ORDER BY DateTimeEnd) rn2
    FROM Transactions t
 ) x
GROUP BY SessionId, TransactionType, rn1 - rn2

返回:

会话 ID |交易类型 |总排名 |最小日期时间结束 --------: | :---------------- | ------: | :----------------- 1 |存款 | 600 | 20/01/2017 11:16:33 1 |提款 | 430 | 21/01/2017 11:16:33 2 |存款 | 500 | 23/02/2017 11:16:33 2 |提款 | 200 | 27/02/2017 11:16:33

2)然后,将上述查询的结果与原表连接起来,拉出剩下的列:

SELECT 
    t.id,
    t.SessionId,
    t.TransactionType,
    t.DateTimeEnd,
    t.DateStart,
    x.SumRank
FROM Transactions t
INNER JOIN (
    SELECT 
        SessionId, 
        TransactionType, 
        SUM(Rank) SumRank, 
        MIN(DateTimeEnd) MinDateTimeEnd
    FROM (
        SELECT 
            t.*,
            ROW_NUMBER() OVER(ORDER BY DateTimeEnd) rn1,
            ROW_NUMBER() OVER(PARTITION BY SessionId, TransactionType ORDER BY DateTimeEnd) rn2
        FROM Transactions t
    ) x
    GROUP BY SessionId, TransactionType, rn1 - rn2
) x 
    ON  x.SessionId = t.SessionId
    AND x.TransactionType = t.TransactionType
    AND x.MinDateTimeEnd = t.DateTimeEnd

产量:

编号 |会话 ID |交易类型 |日期时间结束 |日期开始 | SumRank -: | --------: | :---------------- | :----------------- | :----------------- | ------: 1 | 1 |存款 | 20/01/2017 11:16:33 | 20/01/2017 11:16:33 | 600 2 | 1 |提款 | 21/01/2017 11:16:33 | 20/01/2017 11:16:33 | 430 3 | 2 |存款 | 23/02/2017 11:16:33 | 23/02/2017 11:16:33 | 500 6 | 2 |提款 | 2017 年 2 月 27 日 11:16:33 | 23/02/2017 11:16:33 | 200

Demo on DB Fiddle

注意:如评论所述,我认为您显示的预期结果存在故障。带有ids 47 的行不应出现在输出中,因为ID 为2 的行具有相同的SessionIdTransactionType 以及更早的DateTimeEnd

【讨论】:

  • 嗨!感谢您的实施方法。我尝试过类似的方法,但它不是 100% 正确的。我不想按 SessionId 和 TransactionType 分组,因为这会返回每个 SessionId 和 TransactionType 的唯一行。相反,我想合并 仅连续 行具有相同的 SessionId 和 TransactionType。您可以看到,在 desired output 中有两行 4、7,它们来自于 (4, 5) 和 (7, 8, 9) 的合并。我们保留了 4 和 7,因为它们的组中有最小的 DateTimeEnd,而且 Rank 的总和是 4 的 300 和 7 的 30。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-17
  • 2018-02-15
  • 2018-07-08
  • 1970-01-01
  • 2022-12-03
  • 1970-01-01
相关资源
最近更新 更多