Переместить дочерние строки в новый родительский идентификатор FK? - PullRequest
0 голосов
/ 12 июля 2010

SQL Server 2008.

У меня есть родительская строка с идентификатором pk 1. При блокировании всех остальных пользователей БД (это операция очистки, поэтому проблема с ресурсами не является проблемой), я хотел бы вставитьновой строки, затем возьмите все дочерние строки и измените их столбец fk на новую строку.Для приведенного ниже DDL, например, я хотел бы вставить новую строку и присвоить всем значениям # chi.parid значение «3», чтобы они по существу теперь принадлежали новой строке, поэтому старую можно удалить.

Помогите!

create table #par ( parid int identity(1,1) , note varchar(8) )
create table #chi ( chiid int identity(1,1) , parid int , thing varchar(8) )
insert into #par values ( 'note1' )
insert into #par values ( 'note2' )
insert into #chi values ( 1 , 'a' )
insert into #chi values ( 1 , 'b' )
insert into #chi values ( 1 , 'c' )

Ответы [ 5 ]

3 голосов
/ 12 июля 2010

Я склонен избегать суррогатных ключей в пользу натуральных ключей или ФК;также, я бы избегал IDENTITY для искусственных идентификаторов.Честно говоря, я нахожусь в меньшинстве и часто задавался вопросом, как добиться массовых вставок с IDENTITY ФК.

Согласно ответу Алана Баркера, вы можете использовать SCOPE_IDENTITY(), но только если вы хотитесделать этот RBAR (ряд мучительным рядом).Вы говорите: «это операция очистки», поэтому, возможно, приемлемо процедурное решение.

Я сам обошёл эту проблему, вручную сгенерировав последовательность потенциальных IDENTITY значений (например, впромежуточная таблица), затем используйте SET IDENTITY_INSERT TargetTable ON для принудительного ввода значений. Очевидно, мне нужно убедиться, что предложенные значения не будут фактически использоваться к тому времени, когда произойдет INSERT, так что все другие пользователи все равно должны будут быть заблокированы.

Несколько вещей, чтобы посмотреть.Иногда обязательное ограничение UNIQUE для столбца IDENTITY отсутствует, поэтому вам может потребоваться проверить, нет ли столкновений самостоятельно.Кроме того, я обнаружил, что люди, которым нравятся суррогаты, могут немного «расстроиться», когда значения не последовательны (и находятся в положительном диапазоне!) Или, что намного хуже, есть логика приложения, которая опирается на идеальныйпоследовательность или раскрыла бизнесу значения IDENTITY (в этом случае «ложные» значения ключей предприятия, такие как номера заказов, могут оказаться не такими реальными аудиторами).

РЕДАКТИРОВАТЬ: чтение ответа на другой вопрос SOсегодня утром напомнил мне о предложении OUTPUT SQL Server 2008 для захвата всех автоматически сгенерированных значений IDENTITY в таблице, например,

CREATE TABLE #InsertedBooks
(
 ID INTEGER NOT NULL UNIQUE, -- surrogate
 isbn_13 CHAR(13) NOT NULL UNIQUE -- natural key
);

WITH InsertingBooks (isbn_13)
AS 
(
 SELECT '9781590597453'
 UNION ALL
 SELECT '9780596523060'
 UNION ALL
 SELECT '9780192801425'
)
INSERT INTO Books (isbn_13)
  OUTPUT inserted.ID, inserted.isbn_13   -- <--
  INTO #InsertedBooks (ID, isbn_13)      -- <--
SELECT isbn_13
  FROM InsertingBooks;

INSERT INTO AnotherTable...
SELECT T1.ID, ...
  FROM #InsertedBooks AS T1...;

DROP TABLE #InsertedBooks
1 голос
/ 12 июля 2010

Я думаю, вы просто хотите обновить, например:

UPDATE chi SET parid = 2 WHERE parid = 1

Клавиши F не должны быть проблемой здесь.

0 голосов
/ 20 июля 2010

Спасибо всем за вклад. Похоже, я "не могу" выполнить операцию на основе набора с SQL Svr 2008, поэтому я сделал решение RBAR с циклом (думаю, он работает лучше, чем курсор). Любой, кто может прокомментировать, как сделать это безопаснее с помощью try..catch или рассказать мне больше о том, как сделать это в наборе, прокомментируйте. :)

Спасибо.

    Select 
                [parid]
            ,   [name]
    Into    #redo
    From    partable 
    Where   DateDiff( Hour , donewhen ,SysDateTimeOffset() ) > 23
Begin Transaction
Declare @rows int  = ( Select COUNT(*) From #redo )
Declare @parid int 
Create Clustered Index redoix on #redo([parid]) With FillFactor = 100
While @rows > 0
Begin
    Select Top 1 @parid = [parid] from #redo Order By parid Asc

        Insert partable 
            (
                [name]
            )
        Select 
                [name]      
        From #redo 
        Where parid = @parid
        Update chitable
            Set parid = Scope_Identity()
            Where   parid = @parid
        Delete From partable
            Where   parid = @parid

    Delete from #redo where [parid] = @parid 
    Set @rows  = ( Select COUNT(*) From #redo )
End
Commit Transaction
0 голосов
/ 13 июля 2010

Ну, в этом случае вы можете просто использовать курсор.Не самая лучшая для производительности, но, похоже, это работа по уборке простоев:

CREATE PROCEDURE CleanUp
AS
BEGIN

    -- BUILD YOUR TEMP TABLE(S) HERE:
    --
    --

    DECLARE @delete_parent_id int;

    -- SELECT ON TEMP TABLE (AS A CURSOR):
    -- Put your specific Select statement here:
    DECLARE delete_cursor CURSOR FOR 
    SELECT parid
    FROM #TEMPTABLE
    WHERE <...> ;


    OPEN delete_cursor;

    BEGIN TRAN
        -- Loop round each selected parent, create new parent, update children and delete old parent.
        FETCH NEXT FROM delete_cursor 
        INTO @delete_parent_id;

        WHILE @@FETCH_STATUS = 0
        BEGIN

            INSERT INTO #par VALUES ('Some new Text') --New Parent Row     
            UPDATE #chi SET parid= SCOPE_IDENTITY() where parid = @delete_parent_id   -- Adjust FK ref on child
            DELETE FROM #par WHERE parid = @delete_parent_id -- delete old parent.

            FETCH NEXT FROM delete_cursor 
            INTO @delete_parent_id;
        END

    COMMIT

    CLOSE delete_cursor;
    DEALLOCATE delete_cursor;
END
0 голосов
/ 12 июля 2010

В дополнение к решению Kalium, вы можете использовать функцию SCOPE_IDENTITY () для получения значения IDENTITY последней вставки таблицы.

begin tran    

   insert into #par values ('New Parent')  

   update #chi set parid= SCOPE_IDENTITY()

   delete from #par where parid = <OLD_ID>    

commit

Таким образом, вы можете кодировать это как хранимую процедуру, чтобы сделать все это:

CREATE PROCEDURE CleanUp @newNote varchar(8), @IDToDelete int 
AS
BEGIN

    BEGIN TRAN

    INSERT INTO #par VALUES (@newNote)      
    UPDATE #chi SET parid= SCOPE_IDENTITY()    
    DELETE FROM #par WHERE parid = @IDToDelete

    COMMIT
END

А потом просто:

exec CleanUp 'Alan',1
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...