전체 데이터베이스에서 GETDATE () 사용 변경


27

온-프레미스 SQL Server 2017 데이터베이스를 Azure SQL 데이터베이스로 마이그레이션해야하며 약간의 제한이 있기 때문에 몇 가지 문제에 직면하고 있습니다.

특히 Azure SQL 데이터베이스는 UTC 시간대 (시간대 없음)로만 작동하고 현지 시간이 필요 하므로 데이터베이스 의 GETDATE() 모든 곳 에서 사용을 변경해야하므로 예상보다 많은 작업이 수행되었습니다.

내 시간대에 올바르게 작동하는 현지 시간을 얻기 위해 사용자 정의 함수를 만들었습니다.

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

내가 문제가되는 문제는 실제로 GETDATE()모든보기, 저장 프로 시저, 계산 열, 기본값, 기타 제약 조건 등에서이 기능으로 변경하는 것 입니다.

이 변경을 구현하는 가장 좋은 방법은 무엇입니까?

우리는 관리 형 인스턴스 의 공개 미리보기에 있습니다. 여전히와 동일한 문제가 있으므로이 문제에 GETDATE()도움이되지 않습니다. Azure로 이동해야합니다. 이 데이터베이스는 항상이 시간대에서 사용됩니다 (사용됩니다).

답변:


17
  1. SQL Server 도구를 사용하여 데이터베이스 오브젝트 정의를 테이블,보기, 트리거, SP, 함수 등을 포함하는 SQL 파일로 내보내십시오.

  2. 텍스트를 찾아서 "GETDATE()"바꿀 수있는 텍스트 편집기를 사용하여 SQL 파일을 편집 (먼저 백업)"[dbo].[getlocaldate]()"

  3. Azure SQL에서 편집 된 SQL 파일을 실행하여 데이터베이스 개체를 만듭니다.

  4. 데이터 마이그레이션을 실행하십시오.

다음은 Azure 설명서의 참조 입니다. SQL Azure 용 스크립트 생성


실제로이 방법은 생각보다 복잡하지만, 아마도 정답 일 것입니다. 나는이 많은 수의 시간과 비슷한 작업을 수행해야했으며 가능한 모든 접근 방식을 시도했지만 더 나은 것을 발견하지 못했습니다. 다른 접근 방식 처음에는 훌륭해 보이지만 신속하게 감독과 문제에 대한 악몽이되었습니다.
RBarryYoung

15

이 변경을 구현하는 가장 좋은 방법은 무엇입니까?

나는 다른 방법으로 일할 것입니다. 데이터베이스의 모든 타임 스탬프를 UTC로 변환하고 UTC를 사용하고 흐름과 함께하십시오. 다른 tz의 타임 스탬프가 필요한 경우 AT TIME ZONE(위와 같이) 지정된 TZ (앱의 경우)에서 타임 스탬프를 렌더링 하는 생성 된 열을 만들 수 있습니다 . 그러나 UTC를 앱으로 반환하고 앱에서 해당 로직 (디스플레이 로직)을 작성하는 것을 진지하게 고려할 것입니다.


데이터베이스 일뿐이라면 이것을 고려할 수도 있지만 그 변경은 심각한 리팩토링이 필요한 다른 많은 앱과 소프트웨어에 영향을 미칩니다. 슬프게도, 그것은 나를위한 선택이 아닙니다
Lamak

5
"앱과 소프트웨어"가 getdate ()를 사용하지 않는다는 보장은 무엇입니까? 즉, 앱에 포함 된 SQL 코드입니다. 보장 할 수없는 경우 다른 기능을 사용하도록 데이터베이스를 리팩토링하면 불일치가 발생합니다.
Mister Magoo

@MisterMagoo 그것은 가게의 관행에 달려 있습니다. 솔직히 이것은 매우 사소한 문제라고 생각하며 문제를 해결하기 위해 질문을하고 실제로 i를 고치기 위해 많은 시간이 걸리는 것을 볼 수 없습니다. 이 질문 Azure 가 아닌 경우 흥미로울 것입니다. 해킹하고 더 많은 피드백을 줄 수 있기 때문입니다. 클라우드는 짜증나 기 때문에 지원하지 않으므로 측면에서 무언가를 변경해야합니다. 나는 대답에 제공된 길을 가고 그것을 올바르게하는 데 시간을 보내고 싶습니다. 또한 항상 티아처럼 Azure로 이동할 때 아무것도 작동하지 않을 수도 있습니다.
Evan Carroll

@EvanCarroll, 죄송합니다. 내 의견을 다시 읽고 내 의도를 잘 표현하지 못했습니다! 나는 당신의 대답을지지하고 (증거 된) 데이터베이스에서 getdate () 사용을 getlocaldate ()로 변경하는 제안이 앱 측의 불일치에 노출 될 수 있다는 점을 제기하고, 게다가 더 큰 문제에 석고를 고집. 핵심 문제를 해결하는 것이 올바른 접근법입니다.
Mister Magoo

@MisterMagoo 본인의 우려를 이해하지만이 경우 앱과 소프트웨어가 저장 프로 시저를 통해서만 데이터베이스와 상호 작용하도록 보장 할 수 있습니다
Lamak

6

내보내기, 수동 편집 및 재실행 대신 다음과 같은 방법으로 데이터베이스에서 직접 작업을 시도 할 수 있습니다.

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

물론 함수, 트리거 등을 처리하도록 확장했습니다.

몇 가지주의 사항이 있습니다.

  • 조금 더 밝아 야 CREATE하고 PROCEDURE/ VIEW/ 사이의 다른 / 추가 공백을 처리해야 할 수도 있습니다 <other>. 오히려보다는 REPLACE대신 탈퇴 선호 할 수 그것을 위해 CREATE장소와 실행 DROP첫째,하지만이 위험 떠나 sys.depends호조에서 친구들 곳 ALTER않을 수없는, 또한 경우에 ALTER아직도 그 자리에 기존 개체를이 곳으로 적어도 당신을 실패 DROP+ CREATE당신은 할 수있다 아니.

  • 당신이 코드가 어떤 "영리"가 경우에 당신이 있는지 검색을하고 위해 교체해야합니다 임시 TSQL과 함께 자신의 스키마를 수정 냄새 CREATE> - ALTER그 방해하지 않습니다.

  • 커서를 사용하든 export + edit + run 메소드를 사용하든 조작 후 전체 응용 프로그램을 회귀 테스트하려고합니다.

이 방법을 사용하여 과거에 비슷한 스키마 전체 업데이트를 수행했습니다. 그것은 약간의 해킹이며, 추악한 느낌이 들지만 때로는 가장 쉽고 빠른 방법입니다.

기본값과 다른 구속 조건도 비슷하게 수정할 수 있지만 변경하지 않고 삭제하고 다시 만들 수 있습니다. 다음과 같은 것 :

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

시간이 지남에 따라 파티션을 나누는 경우 해당 부분도 변경해야 할 수도 있습니다. 시간 단위로 시간을 좀 더 세분화하여 분할하는 경우는 드물지만 시간대 DATETIME함수에 따라 파티션 기능이 이전 또는 다음 날로 해석되어 일반적인 쿼리와 파티션이 정렬되지 않은 문제가 발생할 수 있습니다 .


네, 경고는 이것을 어렵게 만듭니다. 또한 이것은 열 기본값을 고려하지 않습니다. 어쨌든 감사합니다
Lamak

sys스키마 에서 열 기본값 및 기타 제약 조건을 검색 하고 프로그래밍 방식으로 수정할 수도 있습니다.
David Spillett

예를 들어 대체 CREATE OR ALTER PROCEDURE하면 코드 생성 문제를 해결하는 데 도움이 될 수 있습니다. 저장된 정의가 읽을 때 CREATE PROCEDURE(3! 공백) 여전히 일치 CREATE PROCEDURE하지도 않고 CREATE OR ALTER PROCEDURE… ._.
생성자

@TheConstructor-이것이 wrt "extra whitespace"를 언급 한 것입니다. CREATE주석 안에없는 첫 번째 를 스캔 하고 대체 하는 함수를 작성하여이 문제를 해결할 수 있습니다 . 나는 과거에 이것을 / 유사하지는 않았지만 현재 게시 할 함수 코드가 없다. 또는 객체 정의에 주석이없는 것을 보장 할 수 CREATE없는 경우 주석 문제를 무시하고의 첫 번째 인스턴스를 찾아 바꾸십시오 CREATE.
David Spillett

나는 과거 에이 방법을 여러 번 시도했지만 Generate-Scripts 접근 방식이 더 좋았으며 변경해야 할 객체의 수가 상대적으로 작지 않은 한 거의 항상 오늘날 사용하고 있습니다.
RBarryYoung

5

나는 데이비드의 대답을 정말로 좋아하고 프로그래밍 방식으로 일을하는 것에 찬성했습니다.

그러나 SSMS를 통해 Azure에서 테스트 실행을 위해 오늘 시도해 볼 수 있습니다.

데이터베이스-> 작업-> 스크립트 생성을 마우스 오른쪽 단추로 클릭하십시오.

[Back Story] 프로덕션 환경이 SQL 2008에있는 동안 모든 테스트 환경을 SQL 2008 R2로 업그레이드 한 주니어 DBA가있었습니다. 이로 인해 오늘날의 상황이 바뀌 었습니다. 테스트에서 프로덕션으로 마이그레이션하려면 generate 스크립트를 사용하여 SQL 내에서 스크립트를 생성해야했고 고급 옵션에서는 '데이터 유형 : 스크립트 : 스키마 및 데이터'옵션을 사용하여 대규모 텍스트 파일을 생성했습니다. 테스트 R2 데이터베이스를 레거시 SQL 2008 서버로 성공적으로 옮길 수있었습니다. 레거시 SQL 2008 서버는 하위 버전으로의 데이터베이스 복원이 작동하지 않았습니다. SSMS 텍스트 버퍼에 비해 파일이 너무 커서 sqlcmd를 사용하여 큰 파일을 입력했습니다.

내가 여기서 말하는 것은이 옵션이 아마도 당신에게도 효과가 있다는 것입니다. 하나의 추가 단계 만 수행하면 생성 된 텍스트 파일에서 getdate ()를 [dbo] .getlocaldate로 검색하고 바꿀 수 있습니다. (마이그레이션하기 전에 함수를 데이터베이스에 넣을 것입니다).

(나는 데이터베이스 복원의이 반창고에 능숙하기를 원치 않았지만, 한동안은 일을하는 사실상의 방법이되었다. 그리고 매번 작동했다.)

이 경로를 선택한 경우 고급 단추를 선택하고 언급 한 기본값과 같이 이전 데이터베이스에서 새 데이터베이스로 이동하는 데 필요한 모든 옵션 (각각 읽기)선택하십시오 . 그러나 Azure에서 몇 가지 테스트 실행을 제공하십시오. 이것이 약간의 노력으로 작동하는 하나의 솔루션이라는 것을 알게 될 것입니다.

여기에 이미지 설명을 입력하십시오


1

모든 proc 및 udf를 동적으로 변경하여 값 변경

    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

주의 사항은 sysobjects Type 열 조건을 주석 처리했습니다. 내 스크립트는 proc 및 UDF 만 변경합니다.

이 스크립트는 Default Constraint포함하는 모든 것을 변경 합니다GetDate()

    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

이것이 최선의 해결책 이라고 생각하기 때문에 Evan Carrolls의 대답을 찬성했습니다 . 동료들이 많은 C # 코드를 변경해야한다고 설득 할 수 없었기 때문에 David Spillett이 작성한 코드를 사용해야했습니다. UDF, 동적 SQL 및 스키마 (모든 코드가 "dbo"를 사용하는 것은 아님)와 관련된 몇 가지 문제를 다음과 같이 수정했습니다.

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

다음과 같은 기본 제약 조건 :

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


UDF
오늘 날짜와 시간을 반환하는 UDF를 사용하라는 제안은 멋지지만 UDF에는 여전히 충분한 성능 문제가 있다고 생각하므로 매우 길고 못생긴 AT TIME ZONE 솔루션을 사용하기로 선택했습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.