Adicionar incremento automático à PK existente

13

Criei uma tabela em um banco de dados que já existe em outro banco de dados. Foi preenchido inicialmente com os dados antigos do banco de dados. A PK da tabela precisava receber os valores que já existem nesses registros, para que não pudessem ser incrementados automaticamente.

Agora preciso que a nova tabela tenha seu PK como incremento automático. Mas como posso fazer isso depois que o PK já existir e tiver dados?

Hikari
fonte
3
Quando você diz "incremento automático", a que exatamente você está se referindo? No SQL Server, não existe essa propriedade para uma coluna. Você quer dizer IDENTITY?
Max Vernon
Sim, é assim que é chamado no MSSQL. No banco de dados em geral, é um PK de incremento automático.
Hikari

Respostas:

13

A maneira como entendo sua pergunta é que você tem uma tabela existente com uma coluna que até agora foi preenchida com valores manuais e agora deseja (1) tornar essa coluna uma IDENTITYcoluna e (2) garantir que o IDENTITYinício do valor mais recente nas linhas existentes.

Primeiro, alguns dados de teste para jogar:

CREATE TABLE dbo.ident_test (
    id    int NOT NULL,
    xyz   varchar(10) NOT NULL,
    CONSTRAINT PK_ident_test PRIMARY KEY CLUSTERED (id)
);

INSERT INTO dbo.ident_test (id, xyz)
VALUES (1, 'test'),
       (2, 'test'),
       (5, 'test'),
       (6, 'test'),
       (10, 'test'),
       (18, 'test'),
       (19, 'test'),
       (20, 'test');

O objetivo é criar a coluna de chave primária da tabela id, uma IDENTITYcoluna que começará às 21 para o próximo registro que será inserido. Neste exemplo, a coluna xyzrepresenta todas as outras colunas da tabela.

Antes de fazer qualquer coisa, leia os avisos na parte inferior desta postagem.

Primeiro, caso algo dê errado:

BEGIN TRANSACTION;

Agora, vamos adicionar uma coluna de trabalho temporária id_tempe definir essa coluna com os idvalores da coluna existente :

ALTER TABLE dbo.ident_test ADD id_temp int NULL;
UPDATE dbo.ident_test SET id_temp=id;

Em seguida, precisamos soltar a idcoluna existente (você não pode simplesmente "adicionar" um IDENTITYa uma coluna existente, é necessário criar a coluna como um IDENTITY). A chave primária também tem que ir, porque a coluna depende dela.

ALTER TABLE dbo.ident_test DROP CONSTRAINT PK_ident_test;
ALTER TABLE dbo.ident_test DROP COLUMN id;

... e adicione a coluna novamente, desta vez como uma IDENTITY, junto com a chave primária:

ALTER TABLE dbo.ident_test ADD id int IDENTITY(1, 1) NOT NULL;
ALTER TABLE dbo.ident_test ADD CONSTRAINT PK_ident_test PRIMARY KEY CLUSTERED (id);

Aqui é onde fica interessante. Você pode ativar IDENTITY_INSERTa tabela, o que significa que você pode definir manualmente os valores de uma IDENTITYcoluna ao inserir novas linhas (embora não atualize as linhas existentes).

SET IDENTITY_INSERT dbo.ident_test ON;

Com esse conjunto, DELETEtodas as linhas da tabela, mas as linhas que você está excluindo, estão OUTPUTdiretamente na mesma tabela - mas com valores específicos para a idcoluna (na coluna de backup).

DELETE FROM dbo.ident_test
OUTPUT deleted.id_temp AS id, deleted.xyz
INTO dbo.ident_test (id, xyz);

Feito isso, desligue IDENTITY_INSERTnovamente.

SET IDENTITY_INSERT dbo.ident_test OFF;

Solte a coluna temporária que adicionamos:

ALTER TABLE dbo.ident_test DROP COLUMN id_temp;

E, finalmente, redefina a IDENTITYcoluna, para que os próximos registros idsejam retomados após o maior número existente na idcoluna:

DECLARE @maxid int;
SELECT @maxid=MAX(id) FROM dbo.ident_test;
DBCC CHECKIDENT ("dbo.ident_test", RESEED, @maxid)

Verificando a tabela de exemplo, o idnúmero mais alto é 20.

SELECT * FROM dbo.ident_test;

Adicione outra linha e verifique a nova IDENTITY:

INSERT INTO dbo.ident_test (xyz) VALUES ('New row');
SELECT * FROM dbo.ident_test;

No exemplo, a nova linha terá id=21. Por fim, se você estiver feliz, confirme a transação:

COMMIT TRANSACTION;

Importante

Esta não é uma operação trivial e traz muitos riscos dos quais você deve estar ciente.

  • Faça isso em um ambiente de teste dedicado. Tenha backups. :)

  • Eu gosto de usar BEGIN/COMMIT TRANSACTIONporque evita que outros processos mexam com a tabela enquanto você está no meio da alteração e oferece a possibilidade de reverter tudo se algo der errado. No entanto, qualquer outro processo que tente acessar sua tabela antes de você confirmar sua transação acabará aguardando. Isso pode ser muito ruim se você tiver uma tabela grande e / ou estiver em um ambiente de produção.

  • OUTPUT .. INTOnão funcionará se sua tabela de destino tiver restrições de chave estrangeira ou qualquer um de vários outros recursos que não me lembro de nada. Em vez disso, você pode descarregar os dados em uma tabela temporária e depois inseri-los novamente na tabela original. Você poderá usar a alternância de partições (mesmo que não use partições).

  • Execute essas instruções uma por uma, não como um lote ou em um procedimento armazenado.

  • Tente pensar em outras coisas que podem depender da idcoluna que você está soltando e recriando. Quaisquer índices terão que ser eliminados e recriados (como fizemos com a chave primária). Lembre-se de criar um script para todos os índices e restrições que você precisará recriar antes.

  • Desative any INSERTe DELETEgatilhos na mesa.

Se recriar a tabela for uma opção:

Se recriar a tabela é uma opção para você, tudo é muito mais simples:

  • Crie a tabela vazia, com a idcoluna como IDENTITY,
  • Conjunto IDENTITY_INSERT ONpara a mesa,
  • Preencher a tabela,
  • Definir IDENTITY_INSERT OFFe
  • Reseed a identidade.
Daniel Hutmacher
fonte
Ótima resposta, muito obrigado! De fato, no meu caso, eu posso apenas configurá-lo IDENTITY_INSERT ON, preenchê-lo e desativá-lo. Era isso que eu queria fazer, mas não sabia que o MSSQL o suportava.
Hikari
5

O uso de UPDATE, DELETE ou INSERT para mover dados pode levar bastante tempo e usar recursos (IO) nos arquivos / discos de dados e de log. É possível evitar o preenchimento do log de transações com potencialmente muitos registros enquanto trabalha em uma tabela grande: Usando a alternância de partições, apenas os metadados são alterados.

Não há movimentação de dados envolvidos e, portanto, isso é realizado muito rapidamente (quase instantaneamente).

Tabela de amostra

A pergunta não mostra a tabela original DDL. O seguinte DDL será usado como exemplo nesta resposta:

CREATE TABLE dbo.idT(
    id int not null
    , uid uniqueidentifier not null
    , name varchar(50)
);
ALTER TABLE dbo.idT ADD CONSTRAINT PK_idT PRIMARY KEY CLUSTERED(id);

Meia dúzia de IDs aleatórios fictícios de 0 a 15 são adicionados a esta consulta:

WITH ids(n) AS(
    SELECT x1.n+x2.n*4
    FROM (values(0), (3)) as x1(n)
    CROSS JOIN (values(0), (2), (3)) as x2(n)
)
INSERT INTO idt(id, uid, name)
SELECT n, NEWID(), NEWID() 
FROM ids

Dados de exemplo em IdT

id  uid                                     name
0   65533096-5007-43EA-88AD-D6776B3B94FA    6A69D4F2-D682-4168-A92F-4CD2E2DBC21D
3   CE87F1ED-BE1A-4F2D-8D62-E1ECA822D35B    AF0524D9-0DBB-41E1-883B-003CB4E4F012
8   34A1DBFD-4F92-4F34-9F04-4CDC824AB15A    02B4BDA4-D515-4262-9031-0BE496AC24CE
11  51606C95-9DE8-4C30-B23B-F915EEA41156    93258103-9C22-4F9C-85CF-712ED0FB3CE6
12  CEC80431-0513-4751-A250-0EB3390DACAB    2DA6B8AF-3EBC-42B3-A76C-028716E24661
15  5037EA83-286F-4EBC-AD7C-E237B570C1FF    095E51E9-8C38-4104-858F-D14AA810A550

Nova tabela com IDENTITY(0, 1)

O único problema idTé com a falta da IDENTITY(0, 1)propriedade no id. Uma nova tabela com uma estrutura semelhante e IDENTITY(0, 1)é criada:

CREATE TABLE dbo.idT_Switch(
    id int identity(0, 1) not null
    , uid uniqueidentifier not null
    , name varchar(50)
);
ALTER TABLE dbo.idT_Switch ADD CONSTRAINT PK_idT_Switch PRIMARY KEY CLUSTERED(id);

Além de IDENTITY(0, 1), idT_Switché idêntico a idT.

Chaves estrangeiras

As chaves estrangeiras ativadas idTdevem ser removidas para permitir que essa técnica seja usada.

Interruptor de Partição

As tabelas idTe idT_Switchtêm uma estrutura compatível. Em vez de usar DELETE, UPDATEe INSERTdeclarações para mover as linhas de idTa idT_Switchou em idTsi mesmo, ALTER TABLE ... SWITCHpode ser usado:

ALTER TABLE dbo.idT
SWITCH TO dbo.idT_Switch;

A única 'partição' de PK_idT(a tabela inteira) é movida para PK_idT_Switch(e vice-versa). idTagora contém 0 linhas e idT_Switchcontém 6 linhas.

Você pode encontrar a lista completa dos requisitos de compatibilidade de origem e destino aqui:

Transferindo dados com eficiência usando a alternância de partições

Observe que esse uso de SWITCHnão requer Enterprise Edition, porque não há particionamento explícito. Uma tabela não particionada é considerada uma tabela com uma única partição do SQL Server 2005 em diante.

Substituir idT

idT agora está vazio e inútil e pode ser descartado:

DROP TABLE idT;

idT_Switchpode ser renomeado e substituirá a idTtabela antiga :

EXECUTE sys.sp_rename
    @objname = N'dbo.idT_Switch',
    @newname = N'idT', -- note lack of schema prefix
    @objtype = 'OBJECT';

Chaves estrangeiras

Chaves estrangeiras podem ser adicionadas novamente à nova idTtabela. Qualquer outra coisa removida anteriormente idTpara tornar as tabelas compatíveis para alternar também precisará ser refeita.

Reseed

SELECT IDENT_CURRENT( 'dbo.idT');

Este comando retorna 0. A tabela idT contém 6 linhas com MAX (id) = 15. DBCC CHECKIDENT (table_name) pode ser usado:

DBCC CHECKIDENT ('dbo.idT');

Como 15 é maior que 0, ele será propagado novamente automaticamente sem procurar MAX (id):

Se o valor atual da identidade de uma tabela for menor que o valor máximo da identidade armazenado na coluna identidade, ele será redefinido usando o valor máximo na coluna identidade. Consulte a seção 'Exceções' a seguir.

IDENT_CURRENT agora retorna 15 .

Teste e adicione dados

Uma INSERTdeclaração simples :

INSERT INTO idT(uid, name) SELECT NEWID(), NEWID();

Adiciona esta linha:

id  uid                                     name
16  B395D692-5D7B-4DFA-9971-A1497B8357A1    FF210D9E-4027-479C-B5D8-057E77FAF378

A idcoluna agora está usando a identidade e o valor recém-inserido é realmente 16 (15 + 1).

Mais Informações

Há uma pergunta e resposta relacionadas com mais informações sobre a SWITCHtécnica aqui:

Por que a remoção da propriedade Identity em uma coluna não é suportada

Julien Vavasseur
fonte
4

Se você deseja começar com um novo valor de identidade, precisará reenviar sua identidade. Veja a documentação paraCHECKIDENT

DBCC CHECKIDENT (yourtable, reseed, starting point)
Tom V - tente topanswers.xyz
fonte
0

ENABLE e DISABLE IDENTITY_INSERT

Se sua tabela for TABLE_A, então

  1. CREATE TABLE TABLE_B semelhante a TABLE_A com a coluna de identidade
  2. DEFINIR IDENTITY_INSERT TABLE_B ATIVADO
  3. INSERT em TABLE_B de TABLE_A
  4. DESLIGAR IDENTITY_INSERT TABLE_B DESLIGADO
  5. DROP TABLE TABLE_A e renomeie a tabela B Exec sp_rename 'TABLE_B', 'TABLE_A'
user4321
fonte