AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / dba / 问题 / 163557
Accepted
Pரதீப்
Pரதீப்
Asked: 2017-02-09 00:55:39 +0800 CST2017-02-09 00:55:39 +0800 CST 2017-02-09 00:55:39 +0800 CST

根据另一列重置运行总计

  • 772

我正在尝试计算运行总数。但是当累积和大于另一列值时它应该重置

create table #reset_runn_total
(
id int identity(1,1),
val int, 
reset_val int,
grp int
)

insert into #reset_runn_total
values 
(1,10,1),
(8,12,1),(6,14,1),(5,10,1),(6,13,1),(3,11,1),(9,8,1),(10,12,1)


SELECT Row_number()OVER(partition BY grp ORDER BY id)AS rn,*
INTO   #test
FROM   #reset_runn_total

索引详情:

CREATE UNIQUE CLUSTERED INDEX ix_load_reset_runn_total
  ON #test(rn, grp) 

样本数据

+----+-----+-----------+-----+
| id | val | reset_val | Grp |
+----+-----+-----------+-----+
|  1 |   1 |        10 | 1   |
|  2 |   8 |        12 | 1   |
|  3 |   6 |        14 | 1   |
|  4 |   5 |        10 | 1   |
|  5 |   6 |        13 | 1   |
|  6 |   3 |        11 | 1   |
|  7 |   9 |         8 | 1   |
|  8 |  10 |        12 | 1   |
+----+-----+-----------+-----+ 

预期结果

+----+-----+-----------------+-------------+
| id | val |    reset_val    | Running_tot |
+----+-----+-----------------+-------------+
|  1 |   1 | 10              |       1     |  
|  2 |   8 | 12              |       9     |  --1+8
|  3 |   6 | 14              |       15    |  --1+8+6 -- greater than reset val
|  4 |   5 | 10              |       5     |  --reset 
|  5 |   6 | 13              |       11    |  --5+6
|  6 |   3 | 11              |       14    |  --5+6+3 -- greater than reset val
|  7 |   9 | 8               |       9     |  --reset -- greater than reset val 
|  8 |  10 | 12              |      10     |  --reset
+----+-----+-----------------+-------------+

询问:

我使用Recursive CTE. 原始问题在这里https://stackoverflow.com/questions/42085404/reset-running-total-based-on-another-column

;WITH cte
     AS (SELECT rn,id,
                val,
                reset_val,
                grp,
                val                   AS running_total,
                Iif (val > reset_val, 1, 0) AS flag
         FROM   #test
         WHERE  rn = 1
         UNION ALL
         SELECT r.*,
                Iif(c.flag = 1, r.val, c.running_total + r.val),
                Iif(Iif(c.flag = 1, r.val, c.running_total + r.val) > r.reset_val, 1, 0)
         FROM   cte c
                JOIN #test r
                  ON r.grp = c.grp
                     AND r.rn = c.rn + 1)
SELECT *
FROM   cte 

T-SQL在不使用. 的情况下有没有更好的选择CLR?

sql-server sql-server-2012
  • 4 4 个回答
  • 6234 Views

4 个回答

  • Voted
  1. Best Answer
    Joe Obbish
    2017-02-09T21:37:01+08:002017-02-09T21:37:01+08:00

    我已经研究过类似的问题,但从未能够找到对数据进行单次传递的窗口函数解决方案。我不认为这是可能的。窗口函数需要能够应用于列中的所有值。这使得像这样的重置计算非常困难,因为一次重置会更改所有以下值的值。

    考虑这个问题的一种方法是,如果你计算一个基本的运行总计,只要你能从正确的前一行中减去运行总计,你就可以获得你想要的最终结果。例如,在您的示例数据中,id4 的值是running total of row 4 - the running total of row 3. 6的值id是running total of row 6 - the running total of row 3因为尚未发生重置。id7的值是running total of row 7 - the running total of row 6等。

    我会在一个循环中使用 T-SQL 来解决这个问题。我有点得意忘形,认为我有一个完整的解决方案。对于 300 万行和 500 个组,代码在我的桌面上在 24 秒内完成。我正在使用具有 6 个 vCPU 的 SQL Server 2016 开发人员版进行测试。我一般利用并行插入和并行执行,因此如果您使用的是旧版本或有 DOP 限制,您可能需要更改代码。

    在我用来生成数据的代码下方。范围应该与您的样本数据相似VAL。RESET_VAL

    drop table if exists reset_runn_total;
    
    create table reset_runn_total
    (
    id int identity(1,1),
    val int, 
    reset_val int,
    grp int
    );
    
    DECLARE 
    @group_num INT,
    @row_num INT;
    BEGIN
        SET NOCOUNT ON;
        BEGIN TRANSACTION;
    
        SET @group_num = 1;
        WHILE @group_num <= 50000 
        BEGIN
            SET @row_num = 1;
            WHILE @row_num <= 60
            BEGIN
                INSERT INTO reset_runn_total WITH (TABLOCK)
                SELECT 1 + ABS(CHECKSUM(NewId())) % 10, 8 + ABS(CHECKSUM(NewId())) % 8, @group_num;
    
                SET @row_num = @row_num + 1;
            END;
            SET @group_num = @group_num + 1;
        END;
        COMMIT TRANSACTION;
    END;
    

    算法如下:

    1)首先将具有标准运行总计的所有行插入到临时表中。

    2)在一个循环中:

    2a) 对于每个组,计算表中剩余的运行总计高于 reset_value 的第一行,并将 id、太大的运行总计和之前太大的运行总计存储在临时表中。

    2b) 将第一个临时表中的行删除到一个结果临时表中,该表的值ID小于或等于ID第二个临时表中的 。根据需要使用其他列调整运行总计。

    3)删除后不再处理的行再运行DELETE OUTPUT到结果表中。这适用于组末尾从不超过重置值的行。

    我将逐步介绍上述算法在 T-SQL 中的一种实现。

    首先创建一些临时表。#initial_results保存具有标准运行总计的原始数据,在#group_bookkeeping每个循环中更新以确定可以移动哪些行,并#final_results包含针对重置调整的运行总计的结果。

    CREATE TABLE #initial_results (
    id int,
    val int, 
    reset_val int,
    grp int,
    initial_running_total int
    );
    
    CREATE TABLE #group_bookkeeping (
    grp int,
    max_id_to_move int,
    running_total_to_subtract_this_loop int,
    running_total_to_subtract_next_loop int,
    grp_done bit, 
    PRIMARY KEY (grp)
    );
    
    CREATE TABLE #final_results (
    id int,
    val int, 
    reset_val int,
    grp int,
    running_total int
    );
    
    INSERT INTO #initial_results WITH (TABLOCK)
    SELECT ID, VAL, RESET_VAL, GRP, SUM(VAL) OVER (PARTITION BY GRP ORDER BY ID) RUNNING_TOTAL
    FROM reset_runn_total;
    
    CREATE CLUSTERED INDEX i1 ON #initial_results (grp, id);
    
    INSERT INTO #group_bookkeeping WITH (TABLOCK)
    SELECT DISTINCT GRP, 0, 0, 0, 0
    FROM reset_runn_total;
    

    之后我在临时表上创建聚集索引,以便可以并行完成插入和索引构建。在我的机器上产生了很大的不同,但在你的机器上可能没有。在源表上创建索引似乎没有帮助,但这可能对您的机器有所帮助。

    下面的代码在循环中运行并更新簿记表。对于每个组,我们需要找到ID应该移动到结果表中的最大值。我们需要该行的运行总计,以便我们可以从初始运行总计中减去它。grp_done当不再需要为 a 做任何工作时,该列设置为 1 grp。

    WITH UPD_CTE AS (
            SELECT 
            #grp_bookkeeping.GRP
            , MIN(CASE WHEN initial_running_total - #group_bookkeeping.running_total_to_subtract_next_loop > RESET_VAL THEN ID ELSE NULL END) max_id_to_update
            , MIN(#group_bookkeeping.running_total_to_subtract_next_loop) running_total_to_subtract_this_loop
            , MIN(CASE WHEN initial_running_total - #group_bookkeeping.running_total_to_subtract_next_loop > RESET_VAL THEN initial_running_total ELSE NULL END) additional_value_next_loop
            , CASE WHEN MIN(CASE WHEN initial_running_total - #group_bookkeeping.running_total_to_subtract_next_loop > RESET_VAL THEN ID ELSE NULL END) IS NULL THEN 1 ELSE 0 END grp_done
            FROM #group_bookkeeping 
            INNER JOIN #initial_results IR ON #group_bookkeeping.grp = ir.grp
            WHERE #group_bookkeeping.grp_done = 0
            GROUP BY #group_bookkeeping.GRP
        )
        UPDATE #group_bookkeeping
        SET #group_bookkeeping.max_id_to_move = uv.max_id_to_update
        , #group_bookkeeping.running_total_to_subtract_this_loop = uv.running_total_to_subtract_this_loop
        , #group_bookkeeping.running_total_to_subtract_next_loop = uv.additional_value_next_loop
        , #group_bookkeeping.grp_done = uv.grp_done
        FROM UPD_CTE uv
        WHERE uv.GRP = #group_bookkeeping.grp
    OPTION (LOOP JOIN);
    

    真的不是LOOP JOIN一般提示的粉丝,但这是一个简单的查询,它是获得我想要的最快的方法。为了真正优化响应时间,我想要并行嵌套循环连接而不是 DOP 1 合并连接。

    下面的代码在循环中运行并将数据从初始表移动到最终结果表中。注意对初始运行总计的调整。

    DELETE ir
    OUTPUT DELETED.id,  
        DELETED.VAL,  
        DELETED.RESET_VAL,  
        DELETED.GRP ,
        DELETED.initial_running_total - tb.running_total_to_subtract_this_loop
    INTO #final_results
    FROM #initial_results ir
    INNER JOIN #group_bookkeeping tb ON ir.GRP = tb.GRP AND ir.ID <= tb.max_id_to_move
    WHERE tb.grp_done = 0;
    

    为了您的方便,下面是完整的代码:

    DECLARE @RC INT;
    BEGIN
    SET NOCOUNT ON;
    
    CREATE TABLE #initial_results (
    id int,
    val int, 
    reset_val int,
    grp int,
    initial_running_total int
    );
    
    CREATE TABLE #group_bookkeeping (
    grp int,
    max_id_to_move int,
    running_total_to_subtract_this_loop int,
    running_total_to_subtract_next_loop int,
    grp_done bit, 
    PRIMARY KEY (grp)
    );
    
    CREATE TABLE #final_results (
    id int,
    val int, 
    reset_val int,
    grp int,
    running_total int
    );
    
    INSERT INTO #initial_results WITH (TABLOCK)
    SELECT ID, VAL, RESET_VAL, GRP, SUM(VAL) OVER (PARTITION BY GRP ORDER BY ID) RUNNING_TOTAL
    FROM reset_runn_total;
    
    CREATE CLUSTERED INDEX i1 ON #initial_results (grp, id);
    
    INSERT INTO #group_bookkeeping WITH (TABLOCK)
    SELECT DISTINCT GRP, 0, 0, 0, 0
    FROM reset_runn_total;
    
    SET @RC = 1;
    WHILE @RC > 0 
    BEGIN
        WITH UPD_CTE AS (
            SELECT 
            #group_bookkeeping.GRP
            , MIN(CASE WHEN initial_running_total - #group_bookkeeping.running_total_to_subtract_next_loop > RESET_VAL THEN ID ELSE NULL END) max_id_to_move
            , MIN(#group_bookkeeping.running_total_to_subtract_next_loop) running_total_to_subtract_this_loop
            , MIN(CASE WHEN initial_running_total - #group_bookkeeping.running_total_to_subtract_next_loop > RESET_VAL THEN initial_running_total ELSE NULL END) additional_value_next_loop
            , CASE WHEN MIN(CASE WHEN initial_running_total - #group_bookkeeping.running_total_to_subtract_next_loop > RESET_VAL THEN ID ELSE NULL END) IS NULL THEN 1 ELSE 0 END grp_done
            FROM #group_bookkeeping 
            CROSS APPLY (SELECT ID, RESET_VAL, initial_running_total FROM #initial_results ir WHERE #group_bookkeeping.grp = ir.grp ) ir
            WHERE #group_bookkeeping.grp_done = 0
            GROUP BY #group_bookkeeping.GRP
        )
        UPDATE #group_bookkeeping
        SET #group_bookkeeping.max_id_to_move = uv.max_id_to_move
        , #group_bookkeeping.running_total_to_subtract_this_loop = uv.running_total_to_subtract_this_loop
        , #group_bookkeeping.running_total_to_subtract_next_loop = uv.additional_value_next_loop
        , #group_bookkeeping.grp_done = uv.grp_done
        FROM UPD_CTE uv
        WHERE uv.GRP = #group_bookkeeping.grp
        OPTION (LOOP JOIN);
    
        DELETE ir
        OUTPUT DELETED.id,  
            DELETED.VAL,  
            DELETED.RESET_VAL,  
            DELETED.GRP ,
            DELETED.initial_running_total - tb.running_total_to_subtract_this_loop
        INTO #final_results
        FROM #initial_results ir
        INNER JOIN #group_bookkeeping tb ON ir.GRP = tb.GRP AND ir.ID <= tb.max_id_to_move
        WHERE tb.grp_done = 0;
    
        SET @RC = @@ROWCOUNT;
    END;
    
    DELETE ir 
    OUTPUT DELETED.id,  
        DELETED.VAL,  
        DELETED.RESET_VAL,  
        DELETED.GRP ,
        DELETED.initial_running_total - tb.running_total_to_subtract_this_loop
        INTO #final_results
    FROM #initial_results ir
    INNER JOIN #group_bookkeeping tb ON ir.GRP = tb.GRP;
    
    CREATE CLUSTERED INDEX f1 ON #final_results (grp, id);
    
    /* -- do something with the data
    SELECT *
    FROM #final_results
    ORDER BY grp, id;
    */
    
    DROP TABLE #final_results;
    DROP TABLE #initial_results;
    DROP TABLE #group_bookkeeping;
    
    END;
    
    • 6
  2. McNets
    2017-02-10T10:38:04+08:002017-02-10T10:38:04+08:00

    使用光标:

    ALTER TABLE #reset_runn_total ADD RunningTotal int;
    
    DECLARE @id int, @val int, @reset int, @acm int, @grp int, @last_grp int;
    SET @acm = 0;
    
    DECLARE curRes CURSOR FAST_FORWARD FOR 
    SELECT id, val, reset_val, grp
    FROM #reset_runn_total
    ORDER BY grp, id;
    
    OPEN curRes;
    FETCH NEXT FROM curRes INTO @id, @val, @reset, @grp;
    SET @last_grp = @grp;
    
    WHILE @@FETCH_STATUS = 0  
    BEGIN
        IF @grp <> @last_grp SET @acm = 0;
        SET @last_grp = @grp;
        SET @acm = @acm + @val;
        UPDATE #reset_runn_total
        SET RunningTotal = @acm
        WHERE id = @id;
        IF @acm > @reset SET @acm = 0;
        FETCH NEXT FROM curRes INTO @id, @val, @reset, @grp;
    END
    
    CLOSE curRes;
    DEALLOCATE curRes;
    
    +----+-----+-----------+-------------+
    | id | val | reset_val | RunningTotal|
    +----+-----+-----------+-------------+
    | 1  | 1   | 10        |     1       |
    +----+-----+-----------+-------------+
    | 2  | 8   | 12        |     9       |
    +----+-----+-----------+-------------+
    | 3  | 6   | 14        |     15      |
    +----+-----+-----------+-------------+
    | 4  | 5   | 10        |     5       |
    +----+-----+-----------+-------------+
    | 5  | 6   | 13        |     11      |
    +----+-----+-----------+-------------+
    | 6  | 3   | 11        |     14      |
    +----+-----+-----------+-------------+
    | 7  | 9   | 8         |     9       |
    +----+-----+-----------+-------------+
    | 8  | 10  | 12        |     10      |
    +----+-----+-----------+-------------+
    

    在这里查看:http ://rextester.com/WSPLO95303

    • 4
  3. Roman Tkachuk
    2017-02-11T15:28:39+08:002017-02-11T15:28:39+08:00

    不是窗口化的,而是纯 SQL 版本:

    WITH x AS (
        SELECT TOP 1 id,
               val,
               reset_val,
               val AS running_total,
               1 AS level 
          FROM reset_runn_total
        UNION ALL
        SELECT r.id,
               r.val,
               r.reset_val,
               CASE WHEN x.running_total < x.reset_val THEN x.running_total + r.val ELSE r.val END,
               level = level + 1
          FROM x JOIN reset_runn_total AS r ON (r.id > x.id)
    ) SELECT
      *
    FROM x
    WHERE NOT EXISTS (
            SELECT 1
            FROM x AS x2
            WHERE x2.id = x.id
            AND x2.level > x.level
        )
    ORDER BY id, level DESC
    ;
    

    我不是 SQL Server 方言方面的专家。这是 PostrgreSQL 的初始版本(如果我理解正确,我不能在 SQL Server 的递归部分中使用 LIMIT 1 / TOP 1):

    WITH RECURSIVE x AS (
        (SELECT id, val, reset_val, val AS running_total
           FROM reset_runn_total
          ORDER BY id
          LIMIT 1)
        UNION
        (SELECT r.id, r.val, r.reset_val,
                CASE WHEN x.running_total < x.reset_val THEN x.running_total + r.val ELSE r.val END
           FROM x JOIN reset_runn_total AS r ON (r.id > x.id)
          ORDER BY id
          LIMIT 1)
    ) SELECT * FROM x;
    
    • 3
  4. ypercubeᵀᴹ
    2017-02-12T11:20:38+08:002017-02-12T11:20:38+08:00

    似乎您有几个查询/方法来解决问题,但您没有提供给我们 - 甚至没有考虑过?- 表上的索引。

    表中有哪些索引?它是堆还是有聚集索引?

    添加此索引后,我会尝试建议的各种解决方案:

    (grp, id) INCLUDE (val, reset_val)
    

    或者只是将聚集索引更改(或制作)为(grp, id).

    拥有一个针对特定查询的索引应该可以提高大多数方法的效率——如果不是所有方法的话。

    • 1

相关问题

  • SQL Server - 使用聚集索引时如何存储数据页

  • 我需要为每种类型的查询使用单独的索引,还是一个多列索引可以工作?

  • 什么时候应该使用唯一约束而不是唯一索引?

  • 死锁的主要原因是什么,可以预防吗?

  • 如何确定是否需要或需要索引

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目

    • 12 个回答
  • Marko Smith

    如何让sqlplus的输出出现在一行中?

    • 3 个回答
  • Marko Smith

    选择具有最大日期或最晚日期的日期

    • 3 个回答
  • Marko Smith

    如何列出 PostgreSQL 中的所有模式?

    • 4 个回答
  • Marko Smith

    列出指定表的所有列

    • 5 个回答
  • Marko Smith

    如何在不修改我自己的 tnsnames.ora 的情况下使用 sqlplus 连接到位于另一台主机上的 Oracle 数据库

    • 4 个回答
  • Marko Smith

    你如何mysqldump特定的表?

    • 4 个回答
  • Marko Smith

    使用 psql 列出数据库权限

    • 10 个回答
  • Marko Smith

    如何从 PostgreSQL 中的选择查询中将值插入表中?

    • 4 个回答
  • Marko Smith

    如何使用 psql 列出所有数据库和表?

    • 7 个回答
  • Martin Hope
    Jin 连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane 如何列出 PostgreSQL 中的所有模式? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh 为什么事务日志不断增长或空间不足? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland 列出指定表的所有列 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney MySQL 能否合理地对数十亿行执行查询? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx 如何监控大型 .sql 文件的导入进度? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison 你如何mysqldump特定的表? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 对 SQL 查询进行计时? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas 如何从 PostgreSQL 中的选择查询中将值插入表中? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 列出所有数据库和表? 2011-02-18 00:45:49 +0800 CST

热门标签

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve