Перенесення всіх View в іншу базу

Скрипт проходить по всіх View бази TCC і генерує скрипт для створення всіх View, які є в цій базі.

SQL
/*
    Base2Base VIEW deployment script generator.

    Запускати в ДЖЕРЕЛЬНІЙ базі даних з довідниками.

    Потрібно вказати лише ім'я цільової бази.

    Наприклад:

        Source database:        CRM_MHP
        Source archive:         CRM_MHP_Archive

        Target database:        TradeControlCenter.Db11743
        Target archive:         TradeControlCenter.Db11743Archive

    Генератор нічого не створює в цільовій базі.
    Він лише формує deployment-скрипт у вкладці Messages.

    Під час виконання deployment-скрипта:
        - кожна VIEW створюється окремо;
        - помилка створення VIEW не зупиняє процес;
        - проблемна VIEW пропускається;
        - наприкінці виводиться перелік VIEW, які не вдалося створити.
*/

-------------------------------------------------------------------------------
-- Прибираємо temp-таблиці від можливого попереднього запуску.
--
-- GO тут важливий: наступний batch буде компілюватися вже без старих
-- визначень temp-таблиць.
-------------------------------------------------------------------------------

IF OBJECT_ID('tempdb..#ViewScripts') IS NOT NULL
    DROP TABLE #ViewScripts;

IF OBJECT_ID('tempdb..#OutputLines') IS NOT NULL
    DROP TABLE #OutputLines;

GO

SET NOCOUNT ON;

-------------------------------------------------------------------------------
-- Єдиний параметр, який потрібно змінювати.
-------------------------------------------------------------------------------

DECLARE @TargetDatabase sysname = N'TradeControlCenter.Db11743';

-------------------------------------------------------------------------------
-- Імена джерельних та цільових баз.
-------------------------------------------------------------------------------

DECLARE @SourceDatabase sysname = DB_NAME();

-- Джерельна Archive-база:
-- CRM_MHP -> CRM_MHP_Archive
DECLARE @SourceArchiveDatabase sysname = @SourceDatabase + N'_Archive';

-- Цільова Archive-база:
-- TradeControlCenter.Db11743 -> TradeControlCenter.Db11743Archive
DECLARE @TargetArchiveDatabase sysname = @TargetDatabase + N'Archive';

-------------------------------------------------------------------------------
-- Таблиця VIEW, які потрібно перенести.
-------------------------------------------------------------------------------

CREATE TABLE #ViewScripts
(
    ObjectId int NOT NULL,
    SchemaName sysname NOT NULL,
    ViewName sysname NOT NULL,
    DependencyLevel int NOT NULL,
    UsesAnsiNulls bit NOT NULL,
    UsesQuotedIdentifier bit NOT NULL,
    Definition nvarchar(max) NOT NULL
);

-------------------------------------------------------------------------------
-- Визначаємо залежності VIEW між собою.
--
-- Якщо ViewA використовує ViewB, то ViewB намагаємося створити раніше.
-------------------------------------------------------------------------------

;WITH ViewDependencies AS
(
    SELECT DISTINCT
        sed.referencing_id,
        sed.referenced_id
    FROM sys.sql_expression_dependencies sed
    INNER JOIN sys.views referencingView
        ON referencingView.object_id = sed.referencing_id
    INNER JOIN sys.views referencedView
        ON referencedView.object_id = sed.referenced_id
    WHERE
        referencingView.is_ms_shipped = 0
        AND referencedView.is_ms_shipped = 0
),
ViewLevels AS
(
    ---------------------------------------------------------------------------
    -- VIEW, які не залежать від інших VIEW цієї бази.
    ---------------------------------------------------------------------------

    SELECT
        v.object_id,
        0 AS DependencyLevel,
        CAST(
            N'|' + CONVERT(nvarchar(20), v.object_id) + N'|'
            AS nvarchar(max)
        ) AS DependencyPath
    FROM sys.views v
    WHERE
        v.is_ms_shipped = 0
        AND NOT EXISTS
        (
            SELECT 1
            FROM ViewDependencies vd
            WHERE vd.referencing_id = v.object_id
        )

    UNION ALL

    ---------------------------------------------------------------------------
    -- VIEW наступних рівнів залежності.
    ---------------------------------------------------------------------------

    SELECT
        vd.referencing_id,
        vl.DependencyLevel + 1,
        CAST(
            vl.DependencyPath
            + CONVERT(nvarchar(20), vd.referencing_id)
            + N'|'
            AS nvarchar(max)
        ) AS DependencyPath
    FROM ViewLevels vl
    INNER JOIN ViewDependencies vd
        ON vd.referenced_id = vl.object_id
    WHERE
        vl.DependencyPath NOT LIKE
            N'%|' + CONVERT(nvarchar(20), vd.referencing_id) + N'|%'
),
FinalViewLevels AS
(
    SELECT
        v.object_id,
        ISNULL(MAX(vl.DependencyLevel), 0) AS DependencyLevel
    FROM sys.views v
    LEFT OUTER JOIN ViewLevels vl
        ON vl.object_id = v.object_id
    WHERE
        v.is_ms_shipped = 0
    GROUP BY
        v.object_id
)

-------------------------------------------------------------------------------
-- Отримуємо definition кожної VIEW та адаптуємо її до цільових баз.
-------------------------------------------------------------------------------

INSERT INTO #ViewScripts
(
    ObjectId,
    SchemaName,
    ViewName,
    DependencyLevel,
    UsesAnsiNulls,
    UsesQuotedIdentifier,
    Definition
)
SELECT
    v.object_id,
    s.name,
    v.name,
    vl.DependencyLevel,
    sm.uses_ansi_nulls,
    sm.uses_quoted_identifier,

    REPLACE
    (
        REPLACE
        (
            REPLACE
            (
                REPLACE
                (
                    -------------------------------------------------------------------
                    -- CREATE VIEW / ALTER VIEW -> CREATE OR ALTER VIEW.
                    -------------------------------------------------------------------

                    CASE
                        WHEN UPPER(LEFT(LTRIM(sm.definition), 20)) = N'CREATE OR ALTER VIEW'
                            THEN LTRIM(sm.definition)

                        WHEN UPPER(LEFT(LTRIM(sm.definition), 11)) = N'CREATE VIEW'
                            THEN
                                N'CREATE OR ALTER VIEW'
                                + SUBSTRING(
                                    LTRIM(sm.definition),
                                    12,
                                    LEN(LTRIM(sm.definition))
                                )

                        WHEN UPPER(LEFT(LTRIM(sm.definition), 10)) = N'ALTER VIEW'
                            THEN
                                N'CREATE OR ALTER VIEW'
                                + SUBSTRING(
                                    LTRIM(sm.definition),
                                    11,
                                    LEN(LTRIM(sm.definition))
                                )

                        ELSE
                            LTRIM(sm.definition)
                    END,

                    -------------------------------------------------------------------
                    -- [CRM_MHP_Archive].
                    --          ->
                    -- [TradeControlCenter.Db11743Archive].
                    -------------------------------------------------------------------

                    QUOTENAME(@SourceArchiveDatabase) + N'.',
                    QUOTENAME(@TargetArchiveDatabase) + N'.'
                ),

                -----------------------------------------------------------------------
                -- CRM_MHP_Archive.
                --          ->
                -- [TradeControlCenter.Db11743Archive].
                -----------------------------------------------------------------------

                @SourceArchiveDatabase + N'.',
                QUOTENAME(@TargetArchiveDatabase) + N'.'
            ),

            ---------------------------------------------------------------------------
            -- [CRM_MHP].
            --      ->
            -- [TradeControlCenter.Db11743].
            ---------------------------------------------------------------------------

            QUOTENAME(@SourceDatabase) + N'.',
            QUOTENAME(@TargetDatabase) + N'.'
        ),

        -----------------------------------------------------------------------------
        -- CRM_MHP.
        --      ->
        -- [TradeControlCenter.Db11743].
        -----------------------------------------------------------------------------

        @SourceDatabase + N'.',
        QUOTENAME(@TargetDatabase) + N'.'
    )

FROM sys.views v
INNER JOIN sys.schemas s
    ON s.schema_id = v.schema_id
INNER JOIN sys.sql_modules sm
    ON sm.object_id = v.object_id
INNER JOIN FinalViewLevels vl
    ON vl.object_id = v.object_id
WHERE
    v.is_ms_shipped = 0
    AND sm.definition IS NOT NULL
OPTION (MAXRECURSION 32767);

-------------------------------------------------------------------------------
-- Рядки готового deployment-скрипта.
--
-- Кожний елемент навмисно робимо коротшим за максимальний розмір PRINT
-- для nvarchar.
-------------------------------------------------------------------------------

CREATE TABLE #OutputLines
(
    LineNumber bigint IDENTITY(1, 1) NOT NULL,
    ScriptLine nvarchar(max) NOT NULL
);

-------------------------------------------------------------------------------
-- Заголовок deployment-скрипта.
-------------------------------------------------------------------------------

INSERT INTO #OutputLines (ScriptLine)
VALUES
    (N'-------------------------------------------------------------------------------'),
    (N'-- Base2Base VIEW deployment script'),
    (N'--'),
    (N'-- Source database:       ' + QUOTENAME(@SourceDatabase)),
    (N'-- Source archive:        ' + QUOTENAME(@SourceArchiveDatabase)),
    (N'-- Target database:       ' + QUOTENAME(@TargetDatabase)),
    (N'-- Target archive:        ' + QUOTENAME(@TargetArchiveDatabase)),
    (N'-------------------------------------------------------------------------------'),
    (N'');

-------------------------------------------------------------------------------
-- Перевірка існування цільової бази.
-------------------------------------------------------------------------------

INSERT INTO #OutputLines (ScriptLine)
VALUES
    (
        N'IF DB_ID(N'''
        + REPLACE(@TargetDatabase, N'''', N'''''')
        + N''') IS NULL'
    ),
    (
        N'    THROW 50000, N''Target database '
        + REPLACE(@TargetDatabase, N'''', N'''''')
        + N' does not exist.'', 1;'
    ),
    (N'GO'),
    (N'');

-------------------------------------------------------------------------------
-- Перевірка Archive-бази.
-------------------------------------------------------------------------------

INSERT INTO #OutputLines (ScriptLine)
VALUES
    (
        N'IF DB_ID(N'''
        + REPLACE(@TargetArchiveDatabase, N'''', N'''''')
        + N''') IS NULL'
    ),
    (
        N'    THROW 50000, N''Target archive database '
        + REPLACE(@TargetArchiveDatabase, N'''', N'''''')
        + N' does not exist.'', 1;'
    ),
    (N'GO'),
    (N'');

-------------------------------------------------------------------------------
-- Переходимо в цільову довідникову базу.
-------------------------------------------------------------------------------

INSERT INTO #OutputLines (ScriptLine)
VALUES
    (N'USE ' + QUOTENAME(@TargetDatabase) + N';'),
    (N'GO'),
    (N''),
    (N'SET NOCOUNT ON;'),
    (N'GO'),
    (N'');

-------------------------------------------------------------------------------
-- Таблиця помилок deployment.
--
-- Temp-таблиця живе між batch-ами GO в межах тієї самої SSMS-сесії.
-------------------------------------------------------------------------------

INSERT INTO #OutputLines (ScriptLine)
VALUES
    (N'IF OBJECT_ID(''tempdb..#ViewDeploymentErrors'') IS NOT NULL'),
    (N'    DROP TABLE #ViewDeploymentErrors;'),
    (N''),
    (N'CREATE TABLE #ViewDeploymentErrors'),
    (N'('),
    (N'    SchemaName sysname NOT NULL,'),
    (N'    ViewName sysname NOT NULL,'),
    (N'    ErrorNumber int NOT NULL,'),
    (N'    ErrorMessage nvarchar(4000) NOT NULL'),
    (N');'),
    (N'GO'),
    (N'');

-------------------------------------------------------------------------------
-- Якщо є VIEW, definition яких прочитати неможливо, вони не потрапляють
-- у deployment.
-------------------------------------------------------------------------------

IF EXISTS
(
    SELECT 1
    FROM sys.views v
    LEFT OUTER JOIN sys.sql_modules sm
        ON sm.object_id = v.object_id
    WHERE
        v.is_ms_shipped = 0
        AND sm.definition IS NULL
)
BEGIN
    INSERT INTO #OutputLines (ScriptLine)
    VALUES
        (N'-------------------------------------------------------------------------------'),
        (N'-- WARNING'),
        (N'-- Some source VIEW definitions cannot be read and were not included.'),
        (N'-- Most likely they were created WITH ENCRYPTION.'),
        (N'-------------------------------------------------------------------------------'),
        (N'');
END;

-------------------------------------------------------------------------------
-- Генеруємо окремий deployment-block для кожної VIEW.
--
-- Важлива відмінність від попередньої версії:
--
-- definition НЕ вкладається одним величезним рядком у
--
--     EXEC sys.sp_executesql N'...'
--
-- Замість цього оригінальний текст VIEW спочатку ріжеться на шматки
-- по 1800 Unicode-символів, кожний шматок окремо екранується, після чого
-- у deployment-скрипті вони збираються назад у @ViewSql.
--
-- Таким чином:
--
--     'E-Commerce Mobile'
--
-- гарантовано не втратить закриваючу лапку через обмеження SSMS/PRINT.
-------------------------------------------------------------------------------

DECLARE @SchemaName sysname;
DECLARE @ViewName sysname;
DECLARE @UsesAnsiNulls bit;
DECLARE @UsesQuotedIdentifier bit;
DECLARE @Definition nvarchar(max);

DECLARE @DisplayName nvarchar(600);
DECLARE @EscapedSchemaName nvarchar(256);
DECLARE @EscapedViewName nvarchar(256);
DECLARE @EscapedDisplayName nvarchar(1200);

DECLARE @DefinitionPosition int;
DECLARE @DefinitionLength int;
DECLARE @RawChunk nvarchar(1800);
DECLARE @EscapedChunk nvarchar(max);
DECLARE @ChunkNumber int;
DECLARE @GeneratedLine nvarchar(max);

DECLARE ViewScriptCursor CURSOR LOCAL FAST_FORWARD FOR
SELECT
    SchemaName,
    ViewName,
    UsesAnsiNulls,
    UsesQuotedIdentifier,
    Definition
FROM #ViewScripts
ORDER BY
    DependencyLevel,
    SchemaName,
    ViewName;

OPEN ViewScriptCursor;

FETCH NEXT FROM ViewScriptCursor
INTO
    @SchemaName,
    @ViewName,
    @UsesAnsiNulls,
    @UsesQuotedIdentifier,
    @Definition;

WHILE @@FETCH_STATUS = 0
BEGIN
    SET @DisplayName =
        QUOTENAME(@SchemaName)
        + N'.'
        + QUOTENAME(@ViewName);

    SET @EscapedSchemaName =
        REPLACE(
            @SchemaName,
            N'''',
            N''''''
        );

    SET @EscapedViewName =
        REPLACE(
            @ViewName,
            N'''',
            N''''''
        );

    SET @EscapedDisplayName =
        REPLACE(
            @DisplayName,
            N'''',
            N''''''
        );

    ---------------------------------------------------------------------------
    -- Заголовок VIEW.
    ---------------------------------------------------------------------------

    INSERT INTO #OutputLines (ScriptLine)
    VALUES
        (N'-------------------------------------------------------------------------------'),
        (N'-- ' + @DisplayName),
        (N'-------------------------------------------------------------------------------'),
        (
            N'SET ANSI_NULLS '
            + CASE
                WHEN @UsesAnsiNulls = 1 THEN N'ON;'
                ELSE N'OFF;'
              END
        ),
        (
            N'SET QUOTED_IDENTIFIER '
            + CASE
                WHEN @UsesQuotedIdentifier = 1 THEN N'ON;'
                ELSE N'OFF;'
              END
        ),
        (N''),
        (N'DECLARE @ViewSql nvarchar(max);');

    ---------------------------------------------------------------------------
    -- Ріжемо ОРИГІНАЛЬНИЙ definition на невеликі шматки.
    --
    -- Важливо: ріжемо ДО екранування одинарних лапок.
    -- Тому ми ніколи не можемо розрізати створену нами пару '' посередині.
    --
    -- Після цього кожний шматок екранується самостійно.
    ---------------------------------------------------------------------------

    SET @DefinitionPosition = 1;
    SET @DefinitionLength = LEN(@Definition);
    SET @ChunkNumber = 0;

    WHILE @DefinitionPosition <= @DefinitionLength
    BEGIN
        SET @RawChunk =
            SUBSTRING(
                @Definition,
                @DefinitionPosition,
                1800
            );

        -----------------------------------------------------------------------
        -- Одинарна лапка всередині зовнішнього N'...' повинна бути
        -- представлена двома одинарними лапками.
        -----------------------------------------------------------------------

        SET @EscapedChunk =
            REPLACE(
                @RawChunk,
                N'''',
                N''''''
            );

        IF @ChunkNumber = 0
        BEGIN
            SET @GeneratedLine =
                N'SET @ViewSql = N'''
                + @EscapedChunk
                + N''';';
        END
        ELSE
        BEGIN
            SET @GeneratedLine =
                N'SET @ViewSql = @ViewSql + N'''
                + @EscapedChunk
                + N''';';
        END;

        INSERT INTO #OutputLines
        (
            ScriptLine
        )
        VALUES
        (
            @GeneratedLine
        );

        SET @DefinitionPosition =
            @DefinitionPosition + 1800;

        SET @ChunkNumber =
            @ChunkNumber + 1;
    END;

    ---------------------------------------------------------------------------
    -- Виконання VIEW через окремий dynamic SQL batch.
    --
    -- CREATE OR ALTER VIEW буде першим statement усередині @ViewSql.
    ---------------------------------------------------------------------------

    INSERT INTO #OutputLines (ScriptLine)
    VALUES
        (N''),
        (N'BEGIN TRY'),
        (N'    EXEC sys.sp_executesql @ViewSql;'),
        (N''),
        (
            N'    PRINT N''CREATED: '
            + @EscapedDisplayName
            + N''';'
        ),
        (N'END TRY'),
        (N'BEGIN CATCH'),
        (N'    INSERT INTO #ViewDeploymentErrors'),
        (N'    ('),
        (N'        SchemaName,'),
        (N'        ViewName,'),
        (N'        ErrorNumber,'),
        (N'        ErrorMessage'),
        (N'    )'),
        (N'    VALUES'),
        (N'    ('),
        (
            N'        N'''
            + @EscapedSchemaName
            + N''','
        ),
        (
            N'        N'''
            + @EscapedViewName
            + N''','
        ),
        (N'        ERROR_NUMBER(),'),
        (N'        ERROR_MESSAGE()'),
        (N'    );'),
        (N''),
        (
            N'    PRINT N''SKIPPED: '
            + @EscapedDisplayName
            + N' - '' + ERROR_MESSAGE();'
        ),
        (N'END CATCH;'),
        (N'GO'),
        (N'');

    FETCH NEXT FROM ViewScriptCursor
    INTO
        @SchemaName,
        @ViewName,
        @UsesAnsiNulls,
        @UsesQuotedIdentifier,
        @Definition;
END;

CLOSE ViewScriptCursor;
DEALLOCATE ViewScriptCursor;

-------------------------------------------------------------------------------
-- Фінальна статистика.
-------------------------------------------------------------------------------

INSERT INTO #OutputLines (ScriptLine)
VALUES
    (N'-------------------------------------------------------------------------------'),
    (N'-- Deployment result'),
    (N'-------------------------------------------------------------------------------'),
    (N''),
    (N'IF EXISTS (SELECT 1 FROM #ViewDeploymentErrors)'),
    (N'BEGIN'),
    (N'    PRINT N'''';'),
    (N'    PRINT N''Some VIEWs were skipped:'';'),
    (N''),
    (N'    SELECT'),
    (N'        SchemaName,'),
    (N'        ViewName,'),
    (N'        ErrorNumber,'),
    (N'        ErrorMessage'),
    (N'    FROM #ViewDeploymentErrors'),
    (N'    ORDER BY'),
    (N'        SchemaName,'),
    (N'        ViewName;'),
    (N'END'),
    (N'ELSE'),
    (N'BEGIN'),
    (N'    PRINT N''All VIEWs were created successfully.'';'),
    (N'END;'),
    (N'GO'),
    (N''),
    (N'-------------------------------------------------------------------------------'),
    (N'-- End of Base2Base VIEW deployment script'),
    (N'-------------------------------------------------------------------------------');

-------------------------------------------------------------------------------
-- Виводимо готовий deployment-скрипт у Messages.
--
-- PRINT для nvarchar має обмеження 4000 символів.
--
-- Завдяки тому, що definition VIEW був попередньо розбитий на шматки
-- по 1800 символів, навіть у найгіршому випадку, коли майже кожний символ
-- є одинарною лапкою і після escaping подвоюється, рядок залишається
-- коротшим за 4000 символів.
-------------------------------------------------------------------------------

DECLARE @OutputLine nvarchar(max);

DECLARE OutputCursor CURSOR LOCAL FAST_FORWARD FOR
SELECT
    ScriptLine
FROM #OutputLines
ORDER BY
    LineNumber;

OPEN OutputCursor;

FETCH NEXT FROM OutputCursor
INTO @OutputLine;

WHILE @@FETCH_STATUS = 0
BEGIN
    ---------------------------------------------------------------------------
    -- Додаткова страховка від випадкового обрізання PRINT.
    ---------------------------------------------------------------------------

    IF DATALENGTH(@OutputLine) / 2 > 4000
    BEGIN
        CLOSE OutputCursor;
        DEALLOCATE OutputCursor;

        THROW 50001, N'Generated deployment line exceeds the PRINT limit of 4000 Unicode characters.', 1;
    END;

    PRINT ISNULL(@OutputLine, N'');

    FETCH NEXT FROM OutputCursor
    INTO @OutputLine;
END;

CLOSE OutputCursor;
DEALLOCATE OutputCursor;

-------------------------------------------------------------------------------
-- Прибираємо temp-таблиці самого генератора.
-------------------------------------------------------------------------------

DROP TABLE #ViewScripts;
DROP TABLE #OutputLines;
Andriy Kravchenko

Andriy Kravchenko

Admin, Writer, File Uploader

Останнє оновлення:

9/22/2026 11:34:02 PM

Кількість переглядів

4