4

今天和昨天的大部分时间我都在尝试决定是否在 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

如您所知,循环块实现起来相对简单,游标/循环迭代也是如此。但是,执行时间非常缓慢且笨拙,我的问题是:是否有一种非迭代的方式来执行这样的任务,或者这是我只需要“吸干”并使用光标的罕见例外之一?

关于这个话题已经有一些讨论,其中一些似乎相似,但由于 if/else 语句和对另一个表的操作而无法使用,例如:

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

SQL Looping似乎有一些关于使用几个 case 语句进行选择的想法,这似乎是可能的,但是我需要根据另一个表的状态完成两个操作,所以这个解决方案似乎不适合。

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

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

笔记

我正在使用 SQL Server 2008

架构设置如下:

总计:(id int pk,totals_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 十进制)

总数应按商店、机器和日期计算,并且应将所有 IN 交易汇总到 total_in 并将 OUT 交易汇总到 total_out。目标是让一个伪数据立方体运行。

4

2 回答 2

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;

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

您首先进行更新,这样您就不会插入新行然后更新它们,执行两次工作并可能重复计算。您可以在这两种情况下使用输出到临时表,然后从 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

现在,如果您想编写获取总数的查询,您可以直接引用视图,或者 - 根据查询和版本 - 即使您引用基表,视图也可能会自动匹配。

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

于 2013-03-12T15:21:56.917 回答
0

我认为您不需要循环。

你可以

  • 更新与您的过滤器/组匹配的所有行/总和存档/删除以前的。
  • 插入与您的过滤器/组存档/删除以前不匹配的所有行。

SQL 应该使用大量数据而不是逐行使用。

于 2013-03-12T15:23:29.117 回答