SQL Server查询优化示例更新查询

时间:2017-09-30 06:20:58

标签: sql-server tsql sql-server-2008-r2 sql-server-2016

在我的交易表中,我有大约90百万的记录。一列"时间"格式如下。没有订单保持这种格式,并且它在这个"时间"之间非常随机。列。

 Time
----------
23:44:33
12:17 09
20 00 20
  :  :  
111913

我想将此时间格式制作/更新为:

    Time
   --------
   23:44:33
   12:17:09
   20:00:20
   21:12:00  
   11:19:13

我写了下面的查询来更新"时间"得到我想要的结果。

    Update [dbo].[table] 
   set [TIME] = '21:14:00'
   WHERE [TIME] = '  :  :  '


   Update [dbo].[table]
   set [time] = replace([TIME], ' ','')  
   WHERE [time]   like '[0-9][0-9] [0-9][0-9] [0-9][0-9]'

      Update [dbo].[table]
      set [time] = STUFF(STUFF([TIME],3,0,':'),6,0,':')
      WHERE [time] like '[0-9][0-9][0-9][0-9][0-9][0-9]'

Update [dbo].[table]
    set [time] = replace([time], ' ', ':')
    WHERE [time] like '[0-9][0-9]:[0-9][0-9] [0-9][0-9]'

上述查询不断执行,需要很长时间。

有没有办法优化它以获得所需的结果?

请提出更好的主意。感谢。

3 个答案:

答案 0 :(得分:1)

我不建议一次运行,而是:

DECLARE @r INT;

WHILE @r > 0
BEGIN
   Update TOP (50000) [dbo].[table]
   set [time] = replace([TIME], ' ','')  
   WHERE [time]   like '[0-9][0-9] [0-9][0-9] [0-9][0-9]';

   SET @r = @@ROWCOUNT;
   -- when there is no rows left @@ROWCOUNT will be 0
END;

另外,我会检查数据库recovery model并控制事务日志增长。

在更新前添加索引可能会有所帮助:

CREATE NONCLUSTERED INDEX IDX_dbo_table_time
  ON [dbo].[table] ([time] ASC) 
  WHERE [time] like '[0-9][0-9] [0-9][0-9] [0-9][0-9]';

答案 1 :(得分:1)

不要试图弄清楚所有不同的变化......只需取出所有不符合所需模式的值并删除所有非数字字符,并在适当的位置留下6个数字位置。在那里形成它只是使用STUFF功能将冒号放在它们所属的位置。

SET NOCOUNT ON;

IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL 
DROP TABLE #TestData;

CREATE TABLE #TestData (
    TimeVal VARCHAR(20)
    );
INSERT #TestData (TimeVal) VALUES
    ('23:44:33'),
    ('12:17 09'),
    ('20 00 20'),
    ('  :  :  '),
    ('111913'),
    ('12:17'),
    ('12:   09'),
    ('  :17 09');

-- before values...
SELECT [Before] = td.TimeVal FROM #TestData td;

-- update problem values...
UPDATE td SET  
    td.TimeVal = CASE LEN(rr.TimeVal)
                    WHEN 6 THEN STUFF(STUFF(rr.TimeVal, 5, 0, ':'), 3, 0, ':')
                    ELSE '21:12:00'
                END
FROM
    #TestData td
    CROSS APPLY ( VALUES (REPLACE(REPLACE(td.TimeVal, ' ', ''), ':', '')) ) rr (TimeVal)
WHERE 
    td.TimeVal NOT LIKE '[0-9][0-9]:[0-9][0-9]:[0-9][0-9]';

-- after values...
SELECT [After] = td.TimeVal FROM #TestData td;

之前&结果......

Before
--------------------
23:44:33
12:17 09
20 00 20
  :  :  
111913
12:17
12:   09
  :17 09

After
--------------------
23:44:33
12:17:09
20:00:20
21:12:00
11:19:13
21:12:00
21:12:00
21:12:00

至于如何最好地针对90M行执行此操作......很难说不了解您的环境。什么是恢复模式?你有什么服务器规格? man nonclustered index如何包含此列? 和你一起DBA,他/她将比任何人都更清楚,数据库将如何处理更新...另外,如果你通过填充日志文件和/或tempdb来锁定实例,他们会为你的头脑而来驱动器。你想要他们参与。

那就是说,我会同意那些建议将其分解成块的人。当我必须对大型表进行批量更新时,我使用类似的循环过程并将进度记录到一个简单的日志表中,以便1)我可以跟踪正在进行的进度2)我知道最后一个如果我需要将其关闭并稍后关闭,则设置已提交的事务。

以下内容......

-- create a log table to make it easy to know where you arw in the update process.
CREATE TABLE dbo.TimeValUpdate_LOG (
    BegID INT,
    EndID INT,
    RowsUpdated INT,
    BegTime DATETIME,
    EndTime DATETIME,
    SecsToComplete AS DATEDIFF(SECOND, BegTime, EndTime)
    );

-- update script...
DECLARE 
    @BegID INT = 0,
    @EndID INT = 500000,
    @BegTime DATETIME,
    @EndTime DATETIME;


WHILE EXISTS (SELECT 1 FROM dbo.RealTable rt WHERE rt.PrimaryKey  > @BegID)
BEGIN
    BEGIN TRY
        BEGIN TRANSACTION; 
        --===================================
        SET @BegTime = CURRENT_TIMESTAMP;
        INSERT dbo.TimeValUpdate_LOG (BegID, EndID, BegTime) VALUES (@BegID, @EndID, @BegTime);
        --=============================================================================

            -- update problem values...
            UPDATE rt SET  
                rt.TimeVal = CASE LEN(rr.TimeVal)
                                WHEN 6 THEN STUFF(STUFF(rr.TimeVal, 5, 0, ':'), 3, 0, ':')
                                ELSE '21:12:00'
                            END
            FROM
                dbo.RealTable rt
                CROSS APPLY ( VALUES (REPLACE(REPLACE(rt.TimeVal, ' ', ''), ':', '')) ) rr (TimeVal)
            WHERE 
                rt.PrimaryKey >= @BegID
                AND rt.PrimaryKey < @EndID
                AND rt.TimeVal NOT LIKE '[0-9][0-9]:[0-9][0-9]:[0-9][0-9]';

        --=============================================================================
        UPDATE tul SET
            tul.RowsUpdated = @@ROWCOUNT,
            tul.EndTime = CURRENT_TIMESTAMP
        FROM 
            dbo.TimeValUpdate_LOG tul
        WHERE 
            tul.BegID = @BegID
            AND tul.EndID = @EndID;
        --===================================
        SET @BegID = @EndID + 1;
        SET @EndID = @BegID + 500000;
        --===================================
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0
        ROLLBACK TRANSACTION;

            DECLARE @ErrorNumber INT = ERROR_NUMBER();
            DECLARE @ErrorLine INT = ERROR_LINE();
            DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
            DECLARE @ErrorSeverity INT = ERROR_SEVERITY();
            DECLARE @ErrorState INT = ERROR_STATE();

            PRINT 'Actual error number: ' + CAST(@ErrorNumber AS VARCHAR(10));
            PRINT 'Actual line number: ' + CAST(@ErrorLine AS VARCHAR(10));

            RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState);
    END CATCH;
END;

答案 2 :(得分:0)

你提到查询是持续执行的,所以我知道你正在纠正从另一个你无法改变的应用程序中插入的数据(如果它只是一次性的工作,那么请跟随其他任何一个答案,他们对此提出了很好的建议。)

在这种情况下,您可以使用触发器,以便以正确的格式插入记录,我认为该表具有主/唯一键,对于仅记录事务的表可能不是这样:

create trigger trCorrectTime on [table] after insert
as
    update [table] set [time] = '21:14:00'
    from [table]
    join inserted on inserted.id=[table].id
    where inserted.[TIME] = '  :  :  '

    update [table] set [time] = STUFF(STUFF([table].[time],3,0,':'),6,0,':')
    from [table]
    join inserted on inserted.id=[table].id
    where inserted.[time] like '[0-9][0-9][0-9][0-9][0-9][0-9]'

    update [table] set [time] = replace([table].[time], ' ',':')  
    from [table]
    join inserted on inserted.id=[table].id
    where inserted.[time] like '[0-9][0-9]_[0-9][0-9]_[0-9][0-9]'
go

id上使用正确的索引(并假设密钥是一个简单的索引,如int值),这几乎不会增加插入操作的时间,因为您只使用插入操作记录,但当然你应该测试它,特别是如果插入事务是时间关键的。