如何将SQL中的循环转换为基于集合的逻辑

Chu*_*ell 3 sql t-sql sql-server stored-procedures sql-server-2008

我花了很多时间今天和昨天试图决定是否在SQL中使用循环或游标,或者弄清楚如何使用基于集合的逻辑来解决问题.我不是新手设置逻辑,但这个问题似乎特别复杂.

问题

我的想法是,如果我有一个所有交易的列表(10个,100个百万个)和它们发生的日期,我可以开始将一些数据组合到每日总计表中,以便通过报告和分析更快地查看它系统.伪代码就是这样的:

foreach( row in transactions_table )
    if( row in totals_table already exists )
        update totals_table, add my totals to the totals row
    else
        insert into totals_table with my row as the base values
    delete ( or archive ) row
Run Code Online (Sandbox Code Playgroud)

正如您所知,循环块实现起来相对简单,并且光标/循环迭代也是如此.但是,执行时间非常慢且不实用,我的问题是:是否有一种非迭代的方式来执行这样的任务,或者这是少数例外情况,我只需要"吮吸它"并使用游标?

关于该主题进行了一些讨论,其中一些似乎是相似的,但由于if/else语句和另一个表上的操作而无法使用,例如:

如何在基于列的逻辑上合并SQL数据行? 这个问题似乎不适用,因为它只返回所有总和的视图,并且实际上并未做出关于对另一个表的添加或更新的逻辑决策

SQL循环似乎有几个关于选择的想法,有几个case语句似乎可能,但我需要完成两个操作依赖于另一个表的状态,所以这个解决方案似乎不合适.

不使用游标的每一行的SQL调用存储过程 这个解决方案似乎最接近我需要做的事情,因为它可以处理每一行上的任意数量的操作,但似乎没有达成共识组.

有什么建议如何解决这个令人沮丧的问题?

笔记

我正在使用SQL Server 2008

架构设置如下:

总计:(id int pk,totals_date date,store_id int fk,machine_id int fk,total_in,total_out)

事务:(transaction_id int pk,transaction_date datetime,store_id int fk,machine_id int fk,transaction_type(IN或OUT),transaction_amount decimal)

总计应按商店,机器和日期计算,并应将所有IN交易总计为total_in,将OUT交易计入total_out.目标是获得一个伪数据立方体.

Aar*_*and 5

您可以在两个基于集合的语句中执行此操作:

BEGIN TRANSACTION;

DECLARE @keys TABLE(some_key INT);

UPDATE tot
  SET totals += tx.amount
OUTPUT inserted.some_key -- key values updated
INTO @keys
FROM dbo.totals_table AS tot WITH (UPDLOCK, HOLDLOCK)
INNER JOIN 
(
  SELECT t.some_key, amount = SUM(amount)
  FROM dbo.transactions_table AS t WITH (HOLDLOCK)
  INNER JOIN dbo.totals_table AS tot
  ON t.some_key = tot.some_key
  GROUP BY t.some_key
) AS tx
ON tot.some_key = tx.some_key;

INSERT dbo.totals_table(some_key, amount)
  OUTPUT inserted.some_key INTO @keys
  SELECT some_key, SUM(amount)
  FROM dbo.transactions_table AS tx
  WHERE NOT EXISTS 
  (
    SELECT 1 FROM dbo.totals_table
    WHERE some_key = tx.some_key
  )
  GROUP BY some_key;

DELETE dbo.transactions_table
  WHERE some_key IN (SELECT some_key FROM @keys);

COMMIT TRANSACTION;
Run Code Online (Sandbox Code Playgroud)

(为简洁起见,省略了错误处理,适用的隔离级别,回滚条件等.)

您首先执行更新,因此不要插入新行然后更新它们,执行两次工作并可能重复计算.您可以在两种情况下使用输出到临时表,然后从tx表中归档/删除行.

我要提醒你不要太兴奋,MERGE直到他们解决了一些这些错误并且你已经阅读了足够多的知识,以确保你不会因为并发性和"并发性"有多么"错误"而变得虚伪.没有额外提示的原子性.你可以解决的竞争条件; 你不能的错误.

另一种选择,来自尼古拉的评论

CREATE VIEW dbo.TotalsView
WITH SCHEMABINDING
AS
   SELECT some_key_column(s), SUM(amount), COUNT_BIG(*)
    FROM dbo.Transaction_Table
    GROUP BY some_key_column(s);
GO
CREATE UNIQUE CLUSTERED INDEX some_key ON dbo.TotalsView(some_key_column(s));
GO
Run Code Online (Sandbox Code Playgroud)

现在,如果要编写获取总计的查询,可以直接引用视图,或者 - 根据查询和编辑 - 即使引用基表,视图也可以自动匹配.

注意:如果您不在Enterprise Edition上,则可能必须使用NOEXPAND提示来利用视图实现的预聚合值.