Thay đổi việc sử dụng GETDATE () trong toàn bộ cơ sở dữ liệu


27

Tôi cần di chuyển cơ sở dữ liệu SQL Server 2017 tại chỗ sang cơ sở dữ liệu Azure SQL và tôi đang đối mặt với một số thách thức do có khá nhiều hạn chế phải trải qua.

Đặc biệt, vì cơ sở dữ liệu Azure SQL chỉ hoạt động trong thời gian UTC (không có múi giờ) và chúng tôi cần giờ địa phương, chúng tôi phải thay đổi việc sử dụng GETDATE() mọi nơi trong cơ sở dữ liệu, điều này đã được chứng minh là hoạt động nhiều hơn tôi dự đoán.

Tôi đã tạo một hàm do người dùng xác định để có được giờ địa phương hoạt động chính xác cho múi giờ của mình:

CREATE FUNCTION [dbo].[getlocaldate]()
RETURNS datetime
AS
BEGIN
    DECLARE @D datetimeoffset;
    SET @D = CONVERT(datetimeoffset, SYSDATETIMEOFFSET()) AT TIME ZONE 'Pacific SA Standard Time';
    RETURN(CONVERT(datetime,@D));
END

Vấn đề tôi gặp vấn đề là thực sự thay đổi GETDATE()với chức năng này trong mọi chế độ xem, quy trình được lưu trữ, cột được tính toán, giá trị mặc định, các ràng buộc khác, v.v.

Điều gì sẽ là cách tốt nhất để thực hiện thay đổi này?

Chúng tôi đang ở trong bản xem trước công khai của Trường hợp được quản lý . Nó vẫn có cùng một vấn đề GETDATE(), vì vậy nó không giúp gì cho vấn đề này. Di chuyển đến Azure là một yêu cầu. Cơ sở dữ liệu này được sử dụng (và sẽ được sử dụng) luôn trong múi giờ này.

Câu trả lời:


17
  1. Sử dụng công cụ SQL Server để xuất định nghĩa đối tượng cơ sở dữ liệu sang tệp SQL bao gồm: bảng, dạng xem, trình kích hoạt, SP, hàm, v.v.

  2. Chỉnh sửa tệp SQL (tạo bản sao lưu trước) bằng bất kỳ trình soạn thảo văn bản nào cho phép bạn tìm văn bản "GETDATE()"và thay thế bằng"[dbo].[getlocaldate]()"

  3. Chạy tệp SQL đã chỉnh sửa trong Azure SQL để tạo các đối tượng cơ sở dữ liệu của bạn ...

  4. Thực hiện việc di chuyển dữ liệu.

Tại đây bạn có một tài liệu tham khảo từ tài liệu azure: Tạo tập lệnh cho SQL Azure


Mặc dù trong thực tế phương pháp này phức tạp hơn âm thanh, nhưng đây có lẽ là câu trả lời đúng và tốt nhất. Tôi đã phải thực hiện các nhiệm vụ tương tự như nhiều lần và tôi đã thử mọi cách tiếp cận có sẵn và tôi không tìm thấy điều gì tốt hơn (hoặc thậm chí gần, thực sự). Các cách tiếp cận khác ban đầu có vẻ tuyệt vời, nhưng chúng nhanh chóng trở thành một vũng lầy ác mộng của những cuộc vượt cạn và gotchas.
RBarryYoung

15

Điều gì sẽ là cách tốt nhất để thực hiện thay đổi này?

Tôi sẽ làm việc theo cách khác. Chuyển đổi tất cả dấu thời gian của bạn trong cơ sở dữ liệu sang UTC và chỉ cần sử dụng UTC và đi theo luồng. Nếu bạn cần dấu thời gian trong một tz khác, bạn có thể tạo cột được tạo bằng cách sử dụng AT TIME ZONE(như bạn đã làm ở trên) để hiển thị dấu thời gian trong TZ được chỉ định đó (cho ứng dụng). Nhưng, tôi sẽ nghiêm túc xem xét việc UTC quay trở lại ứng dụng và viết logic đó - logic hiển thị - trong ứng dụng.


nếu đó chỉ là một cơ sở dữ liệu, tôi có thể xem xét điều này, nhưng sự thay đổi đó ảnh hưởng đến rất nhiều ứng dụng và phần mềm khác cần tái cấu trúc nghiêm trọng. Vì vậy, thật đáng buồn, đó không phải là một lựa chọn cho tôi
Lamak

5
Bạn có đảm bảo gì khi không có "ứng dụng và phần mềm" nào sử dụng getdate () không? tức là mã sql được nhúng trong các ứng dụng. Nếu bạn không thể đảm bảo điều đó, việc cấu trúc lại cơ sở dữ liệu để sử dụng một chức năng khác sẽ chỉ dẫn đến sự không nhất quán.
Ông Magoo

@MisterMagoo Nó phụ thuộc vào thực tiễn tại cửa hàng, thật lòng mà nói tôi nghĩ đây là một vấn đề rất nhỏ và tôi không thể thấy mất nhiều thời gian để đặt câu hỏi để giải quyết vấn đề sau đó để thực sự khắc phục tôi. Sẽ rất thú vị nếu câu hỏi này không phải là Azure, vì tôi có thể hack nó và cung cấp cho bạn nhiều phản hồi hơn. Mặc dù công cụ đám mây rất tệ: họ không hỗ trợ nó, vì vậy bạn phải thay đổi thứ gì đó về phía bạn. Tôi muốn đi theo con đường được cung cấp trong câu trả lời của tôi và dành thời gian để làm điều đó đúng. Ngoài ra, bạn không có gì đảm bảo mọi thứ sẽ hoạt động khi bạn chuyển sang Azure, như mọi khi.
Evan Carroll

@EvanCarroll, xin lỗi tôi vừa đọc lại bình luận của mình và tôi đã không thể hiện tốt ý định của mình! Tôi có nghĩa là ủng hộ câu trả lời của bạn (được nêu lên) và nêu lên quan điểm rằng các đề xuất chỉ thay đổi việc sử dụng getdate () thành getlocaldate () trong cơ sở dữ liệu sẽ khiến chúng mở ra sự không nhất quán từ phía ứng dụng và hơn nữa chỉ là một dán thạch cao vào một vấn đề lớn hơn. 100% đồng ý với câu trả lời của bạn, khắc phục vấn đề cốt lõi là cách tiếp cận đúng đắn.
Magoo

@MisterMagoo Tôi hiểu mối quan tâm của bạn, nhưng trong trường hợp này, tôi có thể đảm bảo rằng các ứng dụng và phần mềm chỉ tương tác với cơ sở dữ liệu thông qua các thủ tục được lưu trữ
Lamak

6

Thay vì xuất, chỉnh sửa thủ công và chạy lại, bạn có thể thử thực hiện công việc trực tiếp trong cơ sở dữ liệu bằng một cái gì đó như:

DECLARE C CURSOR FOR
        SELECT sm.definition, so.type
        FROM   sys.objects so
        JOIN   sys.all_sql_modules sm ON sm.object_id = so.object_id
        WHERE  so.type IN ('P', 'V')
        ORDER BY so.name
DECLARE @SQL NVARCHAR(MAX), @ojtype NVARCHAR(MAX)
OPEN C
FETCH NEXT FROM C INTO @SQL, @ojtype
WHILE @@FETCH_STATUS = 0 BEGIN
    IF @objtype = 'P' SET @SQL = REPLACE(@SQL, 'CREATE PROCEDURE', 'ALTER PROCEDURE') 
    IF @objtype = 'V' SET @SQL = REPLACE(@SQL, 'CREATE VIEW'     , 'ALTER VIEW'     ) 
    SET @SQL = REPLACE(@SQL, 'GETDATE()', '[dbo].[getlocaldate]()') 
    --PRINT @SQL
    EXEC (@SQL)
    FETCH NEXT FROM C INTO @SQL, @ojtype
END
CLOSE C
DEALLOCATE C

tất nhiên mở rộng nó để đối phó với các chức năng, kích hoạt, vv.

Có một vài cảnh báo:

  • Bạn có thể cần phải sáng hơn một chút và xử lý khoảng trắng / khác nhau giữa CREATEPROCEDURE/ VIEW/ <other>. Thay vì REPLACEđể thay vào đó, bạn có thể muốn thay thế CREATEvị trí và thực hiện DROPlần đầu tiên, nhưng điều này có nguy cơ khiến sys.dependsbạn bè rời khỏi nơi mà ALTERkhông thể, ALTERít nhất là nếu bạn thất bại thì ít nhất bạn vẫn còn đối tượng hiện có với DROP+ CREATEbạn có thể không phải.

  • Nếu mã của bạn có bất kỳ mùi "thông minh" nào như sửa đổi lược đồ riêng của nó bằng TSQL đặc biệt thì bạn cần đảm bảo tìm kiếm và thay thế cho CREATE-> ALTERkhông can thiệp vào điều đó.

  • Bạn sẽ muốn kiểm tra hồi quy toàn bộ (các) ứng dụng sau thao tác, cho dù bạn sử dụng con trỏ hay xuất + chỉnh sửa + phương thức chạy.

Tôi đã sử dụng phương pháp này để thực hiện các cập nhật toàn lược đồ tương tự trong quá khứ. Đó là một chút hack, và cảm thấy khá xấu xí, nhưng đôi khi nó là cách dễ nhất / nhanh nhất.

Mặc định và các ràng buộc khác cũng có thể được sửa đổi tương tự, mặc dù chúng chỉ có thể được loại bỏ và tạo lại chứ không phải thay đổi. Cái gì đó như:

DECLARE C CURSOR FOR
        SELECT AlterDefaultSQL = 'ALTER TABLE [' +st.name+ '] DROP CONSTRAINT [' + si.name + '];'
                               + CHAR(10)
                               + 'ALTER TABLE [' +st.name+ '] ADD CONSTRAINT [' + si.name + '] DEFAULT '+REPLACE(si.definition, 'GETDATE()', '[dbo].[getlocaldate]()')+' FOR '+sc.name+';'
        FROM   sys.tables st
        JOIN   sys.default_constraints si ON si.parent_object_id = st.object_id
        JOIN   sys.columns sc ON sc.default_object_id = si.object_id
DECLARE @SQL NVARCHAR(MAX)
OPEN C
FETCH NEXT FROM C INTO @SQL
WHILE @@FETCH_STATUS = 0 BEGIN
    --PRINT @SQL
    EXEC (@SQL)
    FETCH NEXT FROM C INTO @SQL
END
CLOSE C
DEALLOCATE C

Một số điều thú vị nữa mà bạn có thể cần phải tranh luận: nếu bạn phân vùng theo thời gian thì những phần đó cũng có thể cần thay đổi. Mặc dù phân vùng đúng giờ chi tiết hơn sau đó rất hiếm khi bạn có thể gặp vấn đề trong đó DATETIMEchức năng phân vùng được hiểu là ngày trước hoặc ngày hôm sau tùy thuộc vào múi giờ, khiến phân vùng của bạn không được phân bổ theo các truy vấn thông thường.


yeah, hãy cẩn thận là những gì làm cho điều này khó khăn. Ngoài ra, điều này không tính đến các giá trị mặc định của cột. Dù sao cũng cảm ơn
Lamak

Các giá trị mặc định của cột và các ràng buộc khác cũng có thể được quét trong syslược đồ và được sửa đổi theo chương trình.
David Spillett

Có thể thay thế bằng ví dụ CREATE OR ALTER PROCEDUREgiúp xung quanh một số vấn đề tạo mã; vẫn có thể có vấn đề vì định nghĩa được lưu trữ sẽ đọc CREATE PROCEDURE(ba! dấu cách) và điều này không phù hợp với CREATE PROCEDUREcũng không phải là CREATE OR ALTER PROCEDURE._.
TheConstructor

@TheConstructor - đó là những gì tôi đã đề cập đến wrt "khoảng trắng thêm". Bạn có thể khắc phục điều này bằng cách viết một hàm quét đầu tiên CREATEkhông nằm trong một bình luận và thay thế nó. Trước đây tôi không có cái này / tương tự nhưng không có mã chức năng tiện dụng để đăng. Hoặc nếu bạn có thể đảm bảo không có định nghĩa đối tượng nào có ý kiến ​​trước đó CREATE, hãy bỏ qua vấn đề bình luận và chỉ tìm và thay thế phiên bản đầu tiên của CREATE.
David Spillett

Tôi đã thử cách tiếp cận này nhiều lần trong quá khứ và cân bằng, cách tiếp cận Generate-ScScript tốt hơn và hầu như luôn là thứ tôi sử dụng ngày nay trừ khi số lượng đối tượng được thay đổi tương đối nhỏ.
RBarryYoung

5

Tôi thực sự thích câu trả lời của David và ủng hộ rằng cách làm việc có lập trình.

Nhưng bạn có thể thử điều này ngay hôm nay để chạy thử trong Azure thông qua SSMS:

Nhấp chuột phải vào cơ sở dữ liệu của bạn -> Nhiệm vụ -> Tạo tập lệnh ..

[Back Story] chúng tôi đã có một DBA cấp dưới, người đã nâng cấp tất cả các môi trường thử nghiệm của chúng tôi lên SQL 2008 R2 trong khi môi trường sản xuất của chúng tôi ở SQL 2008. Đó là một thay đổi khiến tôi chùn bước cho đến ngày nay. Để di chuyển sang sản xuất, từ kiểm tra, chúng tôi phải tạo tập lệnh trong SQL, sử dụng tập lệnh tạo và trong các tùy chọn nâng cao, chúng tôi đã sử dụng tùy chọn 'Loại dữ liệu thành tập lệnh: Lược đồ và dữ liệu' để tạo tệp văn bản lớn. Chúng tôi đã có thể di chuyển thành công cơ sở dữ liệu R2 thử nghiệm của mình sang các máy chủ SQL 2008 cũ - nơi khôi phục cơ sở dữ liệu sang phiên bản thấp hơn sẽ không hoạt động. Chúng tôi đã sử dụng sqlcmd để nhập tệp lớn - vì các tệp thường quá lớn đối với bộ đệm văn bản SSMS.

Điều tôi đang nói ở đây là tùy chọn này có thể cũng phù hợp với bạn. Bạn sẽ chỉ cần thực hiện thêm một bước và tìm kiếm và thay thế getdate () bằng [dbo] .getlocaldate trong tệp văn bản đã tạo. (Tôi sẽ đưa chức năng của bạn vào cơ sở dữ liệu trước khi di chuyển).

(Tôi chưa bao giờ muốn thành thạo việc hỗ trợ ban nhạc này trong việc khôi phục cơ sở dữ liệu, nhưng trong một thời gian, nó đã trở thành một cách làm việc không chính xác. Và, nó hoạt động mọi lúc.)

Nếu bạn chọn tuyến đường này, hãy chắc chắn và chọn nút Nâng cao và chọn tất cả các tùy chọn bạn cần (đọc từng tùy chọn) để chuyển từ cơ sở dữ liệu cũ sang cơ sở dữ liệu mới - như mặc định bạn đã đề cập. Nhưng hãy cho nó một vài lần chạy thử trong Azure. Tôi cá là bạn sẽ thấy rằng đây là một giải pháp hiệu quả - với một chút nỗ lực.

nhập mô tả hình ảnh ở đây


1

Tự động thay đổi tất cả các Proc và udf để thay đổi giá trị

    DECLARE @Text   NVARCHAR(max), 
        @spname NVARCHAR(max), 
        @Type   CHAR(5), 
        @Sql    NVARCHAR(max) 
DECLARE @getobject CURSOR 

SET @getobject = CURSOR 
FOR SELECT sc.text, 
           so.NAME, 
           so.type 
    FROM   sys.syscomments sc 
           INNER JOIN sysobjects so 
                   ON sc.id = so.id 
    WHERE  sc.[text] LIKE '%getdate()%' 

--and type in('P','FN') 
OPEN @getobject 

FETCH next FROM @getobject INTO @Text, @spname, @Type 

WHILE @@FETCH_STATUS = 0 
  BEGIN 
      IF ( @Type = 'P' 
            OR @Type = 'FN' ) 
        SET @Text = Replace(@Text, 'getdate', 'dbo.getlocaldate') 

      SET @Text = Replace(@Text, 'create', 'alter') 

      EXECUTE Sp_executesql 
        @Text 

      PRINT @Text 

      --,@spname,@Type 
      FETCH next FROM @getobject INTO @Text, @spname, @Type 
  END 

CLOSE @getobject 

DEALLOCATE @getobject  

 

    CREATE PROCEDURE [dbo].[Testproc1] 
AS 
    SET nocount ON; 

  BEGIN 
      DECLARE @CurDate DATETIME = Getdate() 
  END

Lưu ý nhận xét sysobjects Loại điều kiện cột. Tập lệnh của tôi sẽ chỉ thay đổi Proc và UDF.

Kịch bản này sẽ thay đổi tất cả Default Constraintnhững gì chứaGetDate()

    DECLARE @TableName      VARCHAR(300), 
        @constraintName VARCHAR(300), 
        @colName        VARCHAR(300), 
        @Sql            NVARCHAR(max) 
DECLARE @getobject CURSOR 

SET @getobject = CURSOR 
FOR SELECT ds.NAME, 
           sc.NAME AS colName, 
           so.NAME AS Tablename 
    --,ds.definition 
    FROM   sys.default_constraints ds 
           INNER JOIN sys.columns sc 
                   ON ds.object_id = sc.default_object_id 
           INNER JOIN sys.objects so 
                   ON so.object_id = ds.parent_object_id 
    WHERE  definition LIKE '%getdate()%' 

OPEN @getobject 

FETCH next FROM @getobject INTO @constraintName, @colName, @TableName 

WHILE @@FETCH_STATUS = 0 
  BEGIN 
      SET @Sql = 'ALTER TABLE ' + @TableName 
                 + ' DROP CONSTRAINT ' + @constraintName + '; ' 
                 + Char(13) + Char(10) + '           ' + Char(13) + Char(10) + '' 
      SET @Sql = @Sql + ' ALTER TABLE ' + @TableName 
                 + ' ADD CONSTRAINT ' + @constraintName 
                 + '          DEFAULT dbo.GetLocaledate() FOR ' 
                 + @colName + ';' + Char(13) + Char(10) + '          ' + Char(13) 
                 + Char(10) + '' 

      PRINT @Sql 

      EXECUTE sys.Sp_executesql 
        @Sql 

      --,@spname,@Type 
      FETCH next FROM @getobject INTO @constraintName, @colName, @TableName 
  END 

CLOSE @getobject 

DEALLOCATE @getobject   

1

Tôi đã nâng cao câu trả lời của Evan Carrolls, vì tôi nghĩ đây là giải pháp tốt nhất . Tôi đã không thể thuyết phục các đồng nghiệp của mình rằng họ nên thay đổi nhiều mã C #, vì vậy tôi đã phải sử dụng mã mà David Spillett đã viết. Tôi đã sửa một vài vấn đề với UDF, SQL động và lược đồ (không phải tất cả mã đều sử dụng "dbo.") Như thế này:

DECLARE C CURSOR LOCAL STATIC FOR
        SELECT sm.definition, so.type
        FROM   sys.objects so
        JOIN   sys.all_sql_modules sm ON sm.object_id = so.object_id
        WHERE  so.type IN ('P', 'V')
        AND CHARINDEX('getdate()', sm.definition) > 0
        ORDER BY so.name

DECLARE @SQL NVARCHAR(MAX), @objtype NVARCHAR(MAX)
OPEN C
WHILE 1=1 BEGIN
    FETCH NEXT FROM C INTO @SQL, @objtype
    IF @@FETCH_STATUS <> 0 BREAK

    IF @objtype = 'P' SET @SQL = REPLACE(@SQL, 'CREATE PROCEDURE', 'ALTER PROCEDURE') 
    IF @objtype = 'P' SET @SQL = REPLACE(@SQL, 'CREATE   PROCEDURE', 'ALTER PROCEDURE') /* when you write "create or alter proc" */
    IF @objtype = 'V' SET @SQL = REPLACE(@SQL, 'CREATE VIEW'     , 'ALTER VIEW'     ) 
    IF CHARINDEX('getdate())''', @sql) > 0 BEGIN  /* when dynamic SQL is used */
        IF CHARINDEX('utl.getdate())''', @sql) = 0 SET @SQL = REPLACE(@SQL, 'GETDATE()', 'utl.getdate()') 
    end
    ELSE begin
        SET @SQL = REPLACE(@SQL, 'GETDATE()', 'CONVERT(DATETIME, CONVERT(datetimeoffset,  SYSDATETIME()) AT TIME ZONE ''Central Europe Standard Time'')') 
    end
    EXEC dbo.LongPrint @String = @sql    
    EXEC (@SQL)
END
CLOSE C
DEALLOCATE C

và các ràng buộc mặc định như thế này:

DECLARE C CURSOR LOCAL STATIC FOR
        SELECT AlterDefaultSQL = 'ALTER TABLE [' +sch.name+ '].[' +st.name+ '] DROP CONSTRAINT [' + si.name + '];'
                               + CHAR(10)
                               + 'ALTER TABLE [' +sch.name+ '].[' +st.name+ '] ADD CONSTRAINT [' + si.name + '] DEFAULT '+REPLACE(si.definition, 'GETDATE()', 'CONVERT(DATETIME, CONVERT(datetimeoffset,  SYSDATETIME()) AT TIME ZONE ''Central Europe Standard Time'')')+' FOR '+sc.name+';'
        FROM   sys.tables st
        JOIN   sys.default_constraints si ON si.parent_object_id = st.object_id
        JOIN   sys.columns sc ON sc.default_object_id = si.object_id
        INNER JOIN sys.schemas sch ON sch.schema_id = st.schema_id
        WHERE CHARINDEX('getdate()', si.definition) > 0
        ORDER BY st.name, sc.name

DECLARE @SQL NVARCHAR(MAX)
OPEN C
WHILE 1=1 BEGIN
    FETCH NEXT FROM C INTO @SQL
    IF @@FETCH_STATUS <> 0 BREAK

    EXEC dbo.LongPrint @String = @sql  
    EXEC (@SQL)
    FETCH NEXT FROM C INTO @SQL
END
CLOSE C
DEALLOCATE C



Các UDF Đề xuất sử dụng UDF trả về ngày và thời gian ngày nay có vẻ tốt, nhưng tôi nghĩ vẫn còn đủ vấn đề về hiệu năng với UDF, vì vậy tôi đã chọn sử dụng giải pháp AT TIME ZONE rất dài và xấu xí.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.