尝试在不使用游标的情况下执行SQL查询

时间:2011-09-22 21:32:16

标签: sql-server

所以我有一个数据库,它在历史数据库中维护它的所有数据,这样我们就可以更改历史日期,然后返回查看旧数据。我需要编写一个查询来调整每个表的历史表中的日期。现在我已经将它作为游标工作了,但它需要几分钟才能运行,我想看看我是否可以在没有光标的情况下完成它。

编辑:要清楚,我所提取的主键是非历史记录表的主键。历史表可以具有单个主键的多个条目。 (这就是内部sql正在进行连接的原因)

这是光标:

DECLARE tableID CURSOR FOR
SELECT
OBJECT_NAME(ic.OBJECT_ID) AS TableName,
COL_NAME(ic.OBJECT_ID,ic.column_id) AS ColumnName
FROM sys.indexes AS i
INNER JOIN sys.index_columns AS ic
ON i.OBJECT_ID = ic.OBJECT_ID
AND i.index_id = ic.index_id
WHERE i.is_primary_key = 1
and COL_NAME(ic.OBJECT_ID, ic.column_id) != 'RecordID'

DECLARE @currentTable varchar(100)
DECLARE @currentID varchar(100)
DECLARE @currSql varchar(max)
OPEN tableID

FETCH FROM tableID
INTO @currentTable, @currentID
WHILE @@FETCH_STATUS = 0
BEGIN
SELECT @currSql = 
'update t1
set t1.EndDate = t2.BeginDate
from hist.' + @currentTable + ' t1 inner join hist.' + @currentTable + ' t2
on t1.' + @currentID + ' = t2.' + @currentID + '
and t2.BeginDate = (select MIN(BeginDate) from hist.' + @currentTable + ' t
where t.BeginDate >= t1.EndDate and t.' + @currentID + ' = t1.' + @currentID + ')'
EXEC(@currSql)
FETCH FROM tableID
INTO @currentTable, @currentID
END
CLOSE tableID
DEALLOCATE tableID

2 个答案:

答案 0 :(得分:2)

我发现很难相信这会慢慢运行,因为它是一个光标。您可以通过以下方式使光标更有效:

DECLARE CURSOR tableID LOCAL STATIC READ_ONLY FORWARD_ONLY FOR ...

...但我敢打赌,如果你只是打印所有这些SQL命令,将它们复制并粘贴到一个新窗口,并手动执行它们,它仍然会比你想要的时间长很多。速度可能与您正在更新(或至少是扫描)的数据量有关,而不是因为您使用光标生成命令。

您可以在不显式使用游标的情况下生成这些命令,而是使用元数据表来构建字符串,但这仍然会在引擎中使用游标......代码更加整洁。我很快就会发布一个例子。

首先,只需添加查询输出当前的样本,例如table1上的id列。为了帮助说明我的评论以及此更新可能会如何影响任何行:

update t1
set t1.EndDate = t2.BeginDate
from hist.table1 t1 
inner join hist.table1 t2
on t1.id = t2.id
and t2.BeginDate = (select MIN(BeginDate) from hist.table1 t
where t.BeginDate >= t1.EndDate and t.id = t1.id);

也许你的意思是一个更简单的查询,例如:

update hist.table1 
set EndDate = BeginDate
where BeginDate >= EndDate;

或许你想在子查询中引用其他一些表?

无论如何,假设上述查询之一确实是您要执行的内容,生成您可以尝试的第一个查询:

DECLARE @sql NVARCHAR(MAX) = N'';

SELECT @sql += CHAR(13) + CHAR(10)
+ N'update t1
    set t1.EndDate = t2.BeginDate
    from hist.' + QUOTENAME(t.name) + ' AS t1 
    inner join hist.' + QUOTENAME(t.name) + ' AS t2
    on t1.' + QUOTENAME(c.name) + ' = t2.' + QUOTENAME(c.name) 
    + 'and t2.BeginDate = (select MIN(BeginDate) from hist.' 
    + QUOTENAME(t.name) + ' AS t where t.BeginDate > t1.EndDate and 
    t.' + QUOTENAME(c.name) + ' = t1.' + QUOTENAME(c.name) + ');'
FROM sys.tables AS t
INNER JOIN sys.indexes AS i
ON t.[object_id] = i.[object_id]
AND i.is_primary_key = 1
INNER JOIN sys.index_columns AS ic
ON t.[object_id] = ic.[object_id]
INNER JOIN sys.columns AS c
ON c.column_id = ic.column_id
AND c.[object_id] = ic.[object_id]
WHERE c.name <> 'RecordID'
AND t.[schema_id] = SCHEMA_ID('hist');

PRINT @sql;
-- EXEC sp_executesql @sql;

对于第二个,它更简单:

DECLARE @sql NVARCHAR(MAX) = N'';

SELECT @sql += CHAR(13) + CHAR(10) 
    + N'UPDATE hist.' + QUOTENAME(t.name) 
    + ' SET EndDate = BeginDate
    WHERE BeginDate > EndDate;' 
FROM sys.tables AS t
WHERE t.schema_id = SCHEMA_ID('hist');

PRINT @sql;
-- EXEC sp_executesql @sql;

请注意,我将&gt; =更改为&gt;因为如果它已经=没有理由更新。而且,这些假设所有内容都在hist模式中,并且所有主键都是单列主键。虽然我将再次声明查询的第一个更长的版本要贵得多(两个额外的聚簇索引搜索和一个非常昂贵的表假脱机操作符) - 虽然没有实现任何不同的结果,无论如何,从我发布的较短版本

答案 1 :(得分:1)

第1步:在Manageent Studio中运行此查询并激活Result to Text选项(默认选项为Default to Grid):

SELECT

 'update t1' + CHAR(13)
+'set t1.EndDate = t2.BeginDate' + CHAR(13)
+'from hist.' + q.TableName + ' t1 inner join hist.' + q.TableName + ' t2' + CHAR(13)
+'on t1.' + q.ColumnName + ' = t2.' + q.ColumnName + ' and t2.BeginDate =' + CHAR(13)
+'('+CHAR(13)
+'    select MIN(BeginDate)' + CHAR(13)
+'    from hist.' + q.TableName + ' t' + CHAR(13)
+'    where t.BeginDate >= t1.EndDate and t.' + q.ColumnName + ' = t1.' + q.ColumnName + CHAR(13)
+')'

FROM    
(
    SELECT
    OBJECT_NAME(ic.OBJECT_ID) AS TableName,
    COL_NAME(ic.OBJECT_ID,ic.column_id) AS ColumnName
    FROM sys.indexes AS i
    INNER JOIN sys.index_columns AS ic
    ON i.OBJECT_ID = ic.OBJECT_ID
    AND i.index_id = ic.index_id
    WHERE i.is_primary_key = 1
    and COL_NAME(ic.OBJECT_ID, ic.column_id) != 'RecordID'
) q

第2步:将结果复制到新的查询窗口中...... F5 / Ctrl+E