【问题标题】:Faster alternative to using a cursor in SQL Server在 SQL Server 中使用游标的更快替代方法
【发布时间】:2019-04-01 14:12:54
【问题描述】:

我有一个游标,它遍历数据库中的每个基表。它通过执行一条动态 SQL 将记录插入到另一个表中,但目前执行需要 20 多分钟。

DECLARE TableCursor CURSOR FOR
    SELECT TABLE_NAME
    FROM <DB>.INFORMATION_SCHEMA.TABLES
    WHERE TABLE_TYPE = 'BASE TABLE'

WHILE(@@FETCH_STATUS = 0)
BEGIN
    #Dynamic SQL Query to insert data into each table in cursor#

我做了一些研究,发现使用带有 UNION 表达式的 CTE 会更快,但我不确定如何进行转换,例如如何循环 CTE 中的每个表。

非常感谢任何帮助。

EDIT:这是动态 SQL 的示例:

SELECT @SQL = 'WITH CTE_DATES(DATE_VAL) AS (
    SELECT DISTINCT DATE_VAL
    FROM DATE_EVERY_DAY
    WHERE DATE_VAL <= GETDATE()
)
INSERT INTO COUNT_RECORDS_TABLE (DATE_VAL, TABLE_NAME, NUM_RECS_IMPORTED)
SELECT cd.DATE_VAL, ''' + @TableName + ''' AS TABLE_NAME, COUNT(CAST(tn.IMPORT_DATE AS DATE)) AS NUM_RECS_IMPORTED
FROM CTE_DATES AS cd LEFT JOIN ' + @TableName + ' AS tn 
ON CAST(tn.IMPORT_DATE AS DATE) = cd.DATE_VAL
GROUP BY cd.DATE_VAL'

【问题讨论】:

  • AFAIK 您在这里没有太多选择,因为您需要动态 SQL 来为要运行的每个查询选择目标表。但是,这里的性能是一个大问题吗?您多久运行一次此代码?
  • @AJennings1 。 . .问题可能不是游标(在这种情况下),而是动态 SQL。
  • 只注释掉执行 DynamicSQL 的行并重新计算它的时间。这将告诉您构建 DynamicSQL 是否是一个瓶颈。
  • 一种解决方案是将要执行的脚本存储在一个文本文件中,然后使用 sqlcmd 执行它,在大多数情况下,可以使用 sql join 来创建 sql 脚本,但我需要了解有关您的脚本的更多详细信息以确认
  • @GordonLinoff 查看编辑

标签: sql-server tsql database-cursor


【解决方案1】:

我会尝试使用 sys.sp_MSForeachTable。这几乎是最快的,并且? character 是 [schema].[tablename] 格式的表名。显然,如果您只需要表名,您可以进行一些文本替换。直接替换意味着您不需要弄乱 CTE,如果 DATE_EVERY_DAY.DATE_VAL 列上有索引,则此查询可能能够利用它。

EXEC sys.sp_MSforeachtable 
'INSERT INTO COUNT_RECORDS_TABLE (DATE_VAL, TABLE_NAME, NUM_RECS_IMPORED)
SELECT 
    cd.DATE_VAL,
    ''?'' AS TABLE_NAME, 
    COUNT(tn.IMPORT_DATE) AS NUM_RECS_IMPORTED 
FROM 
    DATE_EVERY_DAY AS cd
    LEFT JOIN 
    ? AS tn ON 
        CAST(tn.IMPORT_DATE AS DATE)=cd.DATE_VAL
WHERE
    DATE_VAL <= GETDATE()
GROUP BY
    cd.DATE_VAL
'

这里有一个深入的例子:SQL Server Undocumented Stored Procedures sp_MSforeachtable and sp_MSforeachdb

【讨论】:

  • 我从来不知道那个系统程序!节省制作光标的时间。不幸的是,查询运行时间仍然与我原来的游标相似,这是有道理的,因为我现在知道问题出在实际的 SQL 查询上。
  • 另外我有一些表需要从循环中缓解,所以需要一些灵活性
  • 如果问题是您的查询执行速度,那么您可能没有任何索引可以使用。 DATE_EVERY_DAY.DATE_VAL 应该被索引,但不幸的是 tn.IMPORT_DATE 的 CAST 永远不会被索引。根据允许对源表进行多少处理,您可以创建一个 DATE 类型的附加列并在其中记录 IMPORT_DATE 值。我假设 CAST 是因为 IMPORT_DATE 列是 DATETIME 或 DATETIME2,您需要删除时间部分。如果没有,请在所有这些上粘贴索引。
  • 如果您有需要跳过的表,取决于有多少,您可以在 WHERE 子句中排除它们 WHERE ''?'' NOT IN(''table1'','' table2''),或创建一个包含要排除的表的临时表,根据临时表中的值为 NULL 将其加入并排除
  • 是的,似乎缺乏索引并没有帮助。我必须强制转换为 DATE 的唯一原因是与 GROUP BY 子句有关。我的总体目标是计算每天导入了多少条记录。每天在不同时间导入多组记录,因此这些 DATETIMES 上的 GROUP BY 不会给我每日值
猜你喜欢
  • 2011-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-07
  • 1970-01-01
  • 2016-04-19
  • 1970-01-01
  • 2019-08-10
相关资源
最近更新 更多