Какой самый безопасный способ объединить 2 строки таблицы в SQL Server? - PullRequest
1 голос
/ 30 июля 2010

У меня есть таблица [Sectors], в которой хранятся сектора промышленности. [SectorId] определяется как INT и является первичным ключом этой таблицы. На эти сектора ссылаются по всей базе данных, используя первичный ключ, но в других таблицах нет ограничений внешнего ключа для этого первичного ключа.

Теперь в этой таблице 2 сектора, которые теперь нужно объединить в один. У нас есть Сектор X и Сектор Y. Сектор Y необходимо объединить с Сектором X. Поэтому в основном мне нужно заменить все ссылки на Сектор Y в других таблицах ссылкой на Сектор X, а затем удалить Сектор Y из [Секторов ] таблица.

Проблема в том, что без ограничений внешнего ключа я мог бы в конечном итоге пропустить некоторые таблицы, которые все еще ссылаются на Сектор Y.

Какой лучший способ сделать это?

Ответы [ 4 ]

3 голосов
/ 30 июля 2010
SET NOCOUNT ON

DECLARE 
  @SQL AS NVARCHAR(MAX),
  @name AS NVARCHAR(128)

SELECT name
    INTO #tables
    FROM sys.sysobjects AS O
    WHERE EXISTS (SELECT *
                    FROM sys.syscolumns
                    WHERE id = O.id
                      AND name = 'SectorID')

WHILE EXISTS (SELECT * FROM #tables)
BEGIN
    SELECT TOP 1
      @name = name
        FROM #tables

    SET @SQL = 'IF EXISTS (SELECT * FROM ' + @name + ' WHERE SectorID = 2)' + CHAR(13) + CHAR(10)
    SET @SQL = @SQL + 'BEGIN' + CHAR(13) + CHAR(10)
    SET @SQL = @SQL + ' UPDATE ' + @name + ' SET SectorID = 1 WHERE SectorID = 2' + CHAR(13) + CHAR(10)
    SET @SQL = @SQL + 'END' + CHAR(13) + CHAR(10)
    PRINT @SQL

    DELETE
        FROM #tables
        WHERE name = @name
END

DROP TABLE #tables
1 голос
/ 30 июля 2010

Если вы не вызвали поле SectorID во всех таблицах, вы можете просмотреть все таблицы с целочисленным полем и проверить, существуют ли какие-либо записи "Сектора Y".

Вы можете сделать это, объединив syscolumns с sysobjects (WHERE xtype = 'U' для пользовательской таблицы).

0 голосов
/ 16 июня 2011

Я проделал определенную работу, чтобы найти решение для очень похожего вопроса.Результатом является хранимая процедура, которая принимает идентификаторы сливающихся записей, оставляет первую из них, обновляет все внешние ключи в связанных таблицах и удаляет остальные.

Например, о вопросе themestarter.Предположим, у нас есть эти таблицы:

[Sectors]
ID Name
10  'SectorA'
20  'Sector A'
30  'Sector B'
40  'sector a'

[RelatedRecords]
ID, SectorID, SomeField
1,  10        'value 1'
2,  20        'value 2'
3,  30        'value 3'
4,  40        'value 4'

(ID должен быть первичным ключом, SectorID должен быть внешним ключом), и мы хотим объединить записи 10, 20, 40, оставив запись 20. Для этого мыдолжен вызвать:

dbo.MergeRecords '20, 10, 40', 'Sectors'

, и результат будет:

[Sectors]
ID Name
20  'Sector A'
30  'Sector B'

[RelatedRecords]
ID, SectorID, SomeField
1,  20        'value 1'
2,  20        'value 2'
3,  30        'value 3'
4,  20        'value 4'

Если нет связанных таблиц, то будет выполняться только удаление.Это решение покрывает случай, когда у вас есть однозначный первичный ключ (3NF, как я помню).

Итак, вот код хранимой процедуры:

-- =============================================
-- Description: Merging table records.
-- First record will be leaved, other will be deleted.
-- Depended foreign keys in all tables will be updated.
-- Example:
-- exec MergeRecords '1, 2, 3', 'SomeRecords'
-- =============================================
CREATE PROCEDURE [dbo].[MergeRecords]
    @Id nvarchar(max),      -- Comma-separated IDs
    @PKTable nvarchar(50)   -- Name of a table where merge records in
AS
BEGIN
    SET NOCOUNT ON; 

    declare @PKField nvarchar(50),
            @FKTable nvarchar(50),
            @FKField nvarchar(50)

    declare @updateSql nvarchar(max),
            @deleteSql nvarchar(max)

    declare @firstId nvarchar(max),
            @otherId nvarchar(max)

    set @firstId = LEFT(@Id, CHARINDEX(',', @Id) - 1)
    set @otherId = RIGHT(@Id, LEN(@Id) - CHARINDEX(',', @Id))

    -- Primary key name
    select @PKField = ccu.COLUMN_NAME 
        from INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
        join INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu on ccu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
        where tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
        and tc.TABLE_NAME = @PKTable

    -- Loop foreign keys
    declare constraints_cursor cursor local fast_forward
    for select 
            --tc.CONSTRAINT_NAME, 
            --ccu_pk.TABLE_NAME PK_TABLE_NAME, 
            --ccu_pk.COLUMN_NAME PK_COLUMN_NAME, 
            ccu_fk.TABLE_NAME FK_TABLE_NAME, 
            ccu_fk.COLUMN_NAME FK_COLUMN_NAME

        from INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc 
        join INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc on rc.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
        join INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu_fk on ccu_fk.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
        join INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu_pk on ccu_pk.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME

        where ccu_pk.TABLE_NAME = @PKTable
        and tc.CONSTRAINT_TYPE = 'FOREIGN KEY'

        --Example, @PKTable = 'SomeRecords'
        --CONSTRAINT_NAME                           PK_TABLE_NAME   PK_COLUMN_NAME  FK_TABLE_NAME               FK_COLUMN_NAME
        --FK_SomeRecords_SomeRelatedRecords1        SomeRecords     Id              SomeRelatedRecords          FirstSomeRecordId
        --FK_SomeRecords_SomeRelatedRecords2        SomeRecords     Id              SomeRelatedRecords          SecondSomeRecordId
        --FK_SomeRecords_AnotherRelatedRecords      SomeRecords     Id              AnotherRelatedRecords       SomeRecordId

    open constraints_cursor 
    fetch next from constraints_cursor 
    into @FKTable, @FKField

    while @@fetch_status = 0
    begin
        -- Update foreign keys
        set @updateSql = '
            update @FKTable
            set @FKField = @firstId
            where @FKField in (@otherId)'

        set @updateSql = replace(@updateSql, '@FKTable', @FKTable)
        set @updateSql = replace(@updateSql, '@FKField', @FKField)
        set @updateSql = replace(@updateSql, '@firstId', @firstId)
        set @updateSql = replace(@updateSql, '@otherId', @otherId)
        exec sp_executesql @updateSql

        fetch next from constraints_cursor 
        into @FKTable, @FKField
    end

    close constraints_cursor
    deallocate constraints_cursor 

    -- Delete other records 
    set @deleteSql = 
        'delete from @PKTable
        where @PKField in (@otherId)'

    set @deleteSql = replace(@deleteSql, '@PKTable', @PKTable)  
    set @deleteSql = replace(@deleteSql, '@PKField', @PKField)
    set @deleteSql = replace(@deleteSql, '@otherId', @otherId)
    exec sp_executesql @deleteSql

    select 0    
END
0 голосов
/ 30 июля 2010

Можете ли вы добавить внешние ключи с семантикой on update cascade?

Относительно вашей точки зрения

Я мог бы пропустить некоторые таблицы которые все еще ссылаются на сектор Y.

Нет никакого волшебного способа, которым SQL Server мог бы знать это либо в отсутствие ограничений FK. Вы можете искать столбцы с одинаковыми именами в представлениях Information_Schema или в определении зависимостей базы данных (хранимые процедуры, представления) таблицы Sector, но ни один из подходов не является удаленно безошибочным.

...