Prática recomendada de inserção do SQL Server se não existir

152

Eu tenho uma Competitionstabela de resultados que contém os nomes dos membros da equipe e sua classificação, por um lado.

Por outro lado, preciso manter uma tabela com nomes exclusivos de concorrentes :

CREATE TABLE Competitors (cName nvarchar(64) primary key)

Agora, tenho cerca de 200.000 resultados na 1ª mesa e quando a tabela de concorrentes está vazia , posso fazer isso:

INSERT INTO Competitors SELECT DISTINCT Name FROM CompResults

E a consulta leva apenas 5 segundos para inserir cerca de 11.000 nomes.

Até o momento, este não é um aplicativo crítico, portanto, posso considerar truncar a tabela Concorrentes uma vez por mês, quando recebo os novos resultados da competição com cerca de 10.000 linhas.

Mas qual é a melhor prática quando novos resultados são adicionados, com novos concorrentes existentes E? Não quero truncar a tabela de concorrentes existentes

Preciso executar a declaração INSERT apenas para novos concorrentes e não fazer nada se eles existirem.

Didier Levy
fonte
70
Por favor, não faça de uma NVARCHAR(64)coluna sua chave primária (e, portanto: cluster) !! Primeiro de tudo - é uma chave muito ampla - até 128 bytes; e segundo: o tamanho variável - novamente: não é o ideal ... Essa é a pior escolha que você pode ter - seu desempenho será um inferno, e a fragmentação de tabela e índice estará em 99,9% o tempo todo .....
marc_s
4
Marc tem um bom argumento. Não use o nome como seu pk. Use um ID, de preferência int ou algo leve.
Richard
6
Veja a postagem do blog de Kimberly Tripp sobre o que faz uma boa chave de agrupamento: único, estreito, estático, sempre crescente. Sua cNamefalha em três das quatro categorias .... (não é estreitar, ele provavelmente não é estática, e definitivamente não é sempre crescente)
marc_s
Não consigo entender o motivo de adicionar uma chave primária INT à tabela Nome de um concorrente, onde TODAS as consultas estarão no nome, como 'WHERE name like'% xxxxx% '', portanto, sempre preciso de um índice exclusivo no nome. Mas sim, eu posso ver o ponto em não torná-lo comprimento variável ..
Didier Levy
3
a) evitar a fragmentação e b) se é a chave estrangeira em outras tabelas os dados duplicado é maior do que neccesary (que é uma consideração velocidade)
JamesRyan

Respostas:

214

Semântica, você está perguntando "inserir concorrentes onde ainda não existe":

INSERT Competitors (cName)
SELECT DISTINCT Name
FROM CompResults cr
WHERE
   NOT EXISTS (SELECT * FROM Competitors c
              WHERE cr.Name = c.cName)
gbn
fonte
2
Bem, isso é o que eu faria antes de colocar a pergunta no SO. Mas o núcleo do meu pensamento é: quão bem isso funcionará contra a reconstrução da tabela de nomes do zero uma vez por semana, aproximadamente? (lembre-se esta demora apenas alguns segundos)
Didier Levy
3
@Didier Levy: Eficiência? Por que truncar, recrie quando você pode atualizar apenas com as diferenças. Ou seja: BEGIN TRAN DELETE CompResults INSERT CompResults .. COMMIT TRAN = mais trabalho.
GBN
@gbn - Existe uma maneira de usar a lógica if-else com segurança aqui em vez da sua resposta? Eu tenho uma questão relacionada. Pode me ajudar com isso? stackoverflow.com/questions/21889843/…
Steam
53

Outra opção é deixar a tabela de resultados unida à tabela de concorrentes existentes e encontrar os novos concorrentes filtrando os registros distintos que não correspondem na associação:

INSERT Competitors (cName)
SELECT  DISTINCT cr.Name
FROM    CompResults cr left join
        Competitors c on cr.Name = c.cName
where   c.cName is null

A nova sintaxe MERGE também oferece uma maneira compacta, elegante e eficiente de fazer isso:

MERGE INTO Competitors AS Target
USING (SELECT DISTINCT Name FROM CompResults) AS Source ON Target.Name = Source.Name
WHEN NOT MATCHED THEN
    INSERT (Name) VALUES (Source.Name);
pcofre
fonte
1
A mesclagem é impressionante nesse caso, ele faz exatamente o que diz.
precisa saber é o seguinte
Definitivamente, acredito que esse é o caminho certo, oferecendo ao SQL Server as melhores dicas possíveis para otimizar, em contraste com a abordagem de subconsulta.
Mads Nielsen
4
A declaração MERGE ainda tem muitos problemas. Basta pesquisar no Google "SQL Merge Problems" - muitos blogueiros discutiram isso em detalhes.
David Wilson
por que existe como destino na instrução MERGE, mas não há destino na instrução INSERT? Existem mais diferenças que dificultam a compreensão da equivalência.
Peter
32

Não sei por que mais alguém não disse isso ainda;

NORMALIZAR.

Você tem uma mesa que modela competições? As competições são compostas por concorrentes? Você precisa de uma lista distinta de concorrentes em uma ou mais competições ......

Você deve ter as seguintes tabelas .....

CREATE TABLE Competitor (
    [CompetitorID] INT IDENTITY(1,1) PRIMARY KEY
    , [CompetitorName] NVARCHAR(255)
    )

CREATE TABLE Competition (
    [CompetitionID] INT IDENTITY(1,1) PRIMARY KEY
    , [CompetitionName] NVARCHAR(255)
    )

CREATE TABLE CompetitionCompetitors (
    [CompetitionID] INT
    , [CompetitorID] INT
    , [Score] INT

    , PRIMARY KEY (
        [CompetitionID]
        , [CompetitorID]
        )
    )

Com restrições em CompetitionCompetitors.CompetitionID e CompetitorID apontando para as outras tabelas.

Com esse tipo de estrutura de tabela - suas chaves são INTS simples - não parece haver uma boa CHAVE NATURAL que se encaixaria no modelo, então acho que uma CHAVE SURROGATE é uma boa opção aqui.

Portanto, se você tiver isso, para obter a lista distinta de concorrentes em uma competição específica, você pode emitir uma consulta como esta:

DECLARE @CompetitionName VARCHAR(50) SET @CompetitionName = 'London Marathon'

    SELECT
        p.[CompetitorName] AS [CompetitorName]
    FROM
        Competitor AS p
    WHERE
        EXISTS (
            SELECT 1
            FROM
                CompetitionCompetitor AS cc
                JOIN Competition AS c ON c.[ID] = cc.[CompetitionID]
            WHERE
                cc.[CompetitorID] = p.[CompetitorID]
                AND cc.[CompetitionName] = @CompetitionNAme
        )

E se você quisesse a pontuação para cada competição em que um concorrente estivesse:

SELECT
    p.[CompetitorName]
    , c.[CompetitionName]
    , cc.[Score]
FROM
    Competitor AS p
    JOIN CompetitionCompetitor AS cc ON cc.[CompetitorID] = p.[CompetitorID]
    JOIN Competition AS c ON c.[ID] = cc.[CompetitionID]

E quando você tem uma nova competição com novos concorrentes, basta verificar quais já existem na tabela Competidores. Se eles já existem, você não insere no Concorrente para esses Concorrentes e insere nos novos.

Em seguida, você insere a nova Competição em Competição e, por fim, apenas cria todos os links em Competidores.

Transact Charlie
fonte
2
Supondo que o OP tenha a leveza neste momento para reestruturar todas as suas tabelas para obter um resultado em cache. Reescrever seu banco de dados e aplicativo, em vez de resolver o problema dentro de um escopo definido, sempre que algo não se encaixa facilmente, é uma receita para o desastre.
Jeffrey Vest
1
Talvez no caso do OP como o meu, você nem sempre tenha acesso para modificar o banco de dados. E reescrever / normalizar um banco de dados antigo nem sempre está no orçamento ou no tempo alocado.
eaglei22
10

Você precisará juntar as mesas e obter uma lista de concorrentes únicos que ainda não existem Competitors.

Isso irá inserir registros exclusivos.

INSERT Competitors (cName) 
SELECT DISTINCT Name
FROM CompResults cr LEFT JOIN Competitors c ON cr.Name = c.cName
WHERE c.Name IS NULL

Pode chegar um momento em que essa inserção precise ser feita rapidamente, sem poder aguardar a seleção de nomes exclusivos. Nesse caso, você pode inserir os nomes exclusivos em uma tabela temporária e, em seguida, usar essa tabela temporária para inserir em sua tabela real. Isso funciona bem porque todo o processamento acontece no momento em que você está inserindo uma tabela temporária, portanto não afeta sua tabela real. Então, quando todo o processamento estiver concluído, você fará uma inserção rápida na tabela real. Eu posso até envolver a última parte, onde você insere na tabela real, dentro de uma transação.

Richard
fonte
4

As respostas acima, que falam sobre normalização, são ótimas! Mas e se você se encontrar em uma posição como eu, onde não poderá tocar no esquema ou na estrutura do banco de dados como está? Por exemplo, os DBAs são 'deuses' e todas as revisões sugeridas vão para / dev / null?

A esse respeito, acho que isso também foi respondido com esta postagem do Stack Overflow em relação a todos os usuários acima, fornecendo exemplos de código.

Estou reposicionando o código de INSERT VALUES WHERE NOT EXISTS que mais me ajudou, pois não posso alterar nenhuma tabela de banco de dados subjacente:

INSERT INTO #table1 (Id, guidd, TimeAdded, ExtraData)
SELECT Id, guidd, TimeAdded, ExtraData
FROM #table2
WHERE NOT EXISTS (Select Id, guidd From #table1 WHERE #table1.id = #table2.id)
-----------------------------------
MERGE #table1 as [Target]
USING  (select Id, guidd, TimeAdded, ExtraData from #table2) as [Source]
(id, guidd, TimeAdded, ExtraData)
    on [Target].id =[Source].id
WHEN NOT MATCHED THEN
    INSERT (id, guidd, TimeAdded, ExtraData)
    VALUES ([Source].id, [Source].guidd, [Source].TimeAdded, [Source].ExtraData);
------------------------------
INSERT INTO #table1 (id, guidd, TimeAdded, ExtraData)
SELECT id, guidd, TimeAdded, ExtraData from #table2
EXCEPT
SELECT id, guidd, TimeAdded, ExtraData from #table1
------------------------------
INSERT INTO #table1 (id, guidd, TimeAdded, ExtraData)
SELECT #table2.id, #table2.guidd, #table2.TimeAdded, #table2.ExtraData
FROM #table2
LEFT JOIN #table1 on #table1.id = #table2.id
WHERE #table1.id is null

O código acima usa campos diferentes dos que você possui, mas você obtém a essência geral das várias técnicas.

Observe que, conforme a resposta original no Stack Overflow, esse código foi copiado daqui .

De qualquer forma, meu argumento é "melhores práticas" muitas vezes se resume ao que você pode ou não fazer, assim como à teoria.

  • Se você é capaz de normalizar e gerar índices / chaves - ótimo!
  • Se não, e você tem o recurso de codificar hacks como eu, espero que o acima seja útil.

Boa sorte!


fonte
Caso não esteja claro, existem quatro abordagens diferentes para o problema, então escolha uma.
Nasch
3

Normalizar suas tabelas operacionais, conforme sugerido pelo Transact Charlie, é uma boa idéia e economizará muitas dores de cabeça e problemas ao longo do tempo - mas existem coisas como tabelas de interface , que suportam integração com sistemas externos, e tabelas de relatório , que suportam coisas como analíticas em processamento; e esses tipos de tabelas não devem necessariamente ser normalizados - na verdade, muitas vezes é muito, muito mais conveniente e com desempenho para eles não serem .

Nesse caso, acho que a proposta do Transact Charlie para suas tabelas operacionais é boa.

Mas eu adicionaria um índice (não necessariamente exclusivo) ao CompetitorName na tabela Competitors para oferecer suporte a junções eficientes no CompetitorName para fins de integração (carregamento de dados de fontes externas) e colocaria uma tabela de interface na combinação: CompetitionResults.

Os resultados da competição devem conter quaisquer dados que os resultados da competição contenham. O objetivo de uma tabela de interface como esta é tornar o mais rápido e fácil possível truncá-lo e recarregá-lo de uma planilha do Excel ou de um arquivo CSV, ou de qualquer forma em que você tenha esses dados.

Essa tabela de interface não deve ser considerada parte do conjunto normalizado de tabelas operacionais. Em seguida, você pode ingressar no CompetitionResults, conforme sugerido por Richard, para inserir registros nos concorrentes que ainda não existem e atualizar os que existem (por exemplo, se você realmente tiver mais informações sobre os concorrentes, como o número de telefone ou endereço de email).

Uma coisa que eu observaria - na realidade, o nome do concorrente, parece-me, é muito improvável que seja único em seus dados . Em 200.000 concorrentes, você pode muito bem ter 2 ou mais David Smiths, por exemplo. Por isso, recomendo que você colete mais informações dos concorrentes, como número de telefone ou endereço de e-mail ou algo com maior probabilidade de ser único.

Sua tabela operacional, Concorrentes, deve ter apenas uma coluna para cada item de dados que contribui para uma chave natural composta; por exemplo, ele deve ter uma coluna para um endereço de email principal. Mas a tabela de interface deve ter um slot para antigos e novos valores para um endereço de email principal, para que o valor antigo possa ser usado para procurar o registro nos Concorrentes e atualizar essa parte para o novo valor.

Portanto, CompetitionResults deve ter alguns campos "antigos" e "novos" - oldEmail, newEmail, oldPhone, newPhone, etc. Dessa forma, você pode formar uma chave composta, em Concorrentes, em Nome do concorrente, E-mail e Telefone.

Então, quando você tiver alguns resultados de competição, poderá truncar e recarregar sua tabela CompetitionResults da planilha do Excel ou o que tiver, e executar uma inserção única e eficiente para inserir todos os novos concorrentes na tabela Competidores e atualizar uma atualização eficiente para atualizar todas as informações sobre os concorrentes existentes nos resultados da competição. E você pode fazer uma única inserção para inserir novas linhas na tabela CompetitionCompetitors. Essas coisas podem ser feitas em um procedimento armazenado ProcessCompetitionResults, que pode ser executado após o carregamento da tabela CompetitionResults.

Essa é uma espécie de descrição rudimentar do que eu vi repetidamente no mundo real com Oracle Applications, SAP, PeopleSoft e uma lista completa de outros pacotes de software corporativo.

Um último comentário que eu faria é o que fiz antes no SO: Se você criar uma chave estrangeira que garanta a existência de um Concorrente na tabela Concorrentes antes de poder adicionar uma linha com esse Concorrente a CompetitionCompetitors, verifique se chave estrangeira está definida para cascata atualizações e exclusões . Dessa forma, se você precisar excluir um concorrente, poderá fazê-lo e todas as linhas associadas a esse concorrente serão excluídas automaticamente. Caso contrário, por padrão, a chave estrangeira exigirá que você exclua todas as linhas relacionadas dos CompetitionCompetitors antes de permitir que você exclua um Concorrente.

(Algumas pessoas pensam que chaves estrangeiras não em cascata são uma boa precaução de segurança, mas minha experiência é que elas são apenas uma dor no traseiro que, na maioria das vezes, são simplesmente resultado de uma supervisão e criam um monte de trabalho Para lidar com pessoas que excluem acidentalmente coisas, é por isso que você tem coisas como "tem certeza" e vários tipos de backups regulares e fontes de dados redundantes.É muito, muito mais comum querer excluir um concorrente, cujos dados são todos errei por exemplo, do que excluir acidentalmente um e depois dizer "Ah, não! Eu não pretendia fazer isso! E agora não tenho os resultados de suas competições! Aaaahh!" Este último certamente é bastante comum, então , você precisa estar preparado para isso, mas o primeiro é muito mais comum,portanto, a melhor e mais fácil maneira de se preparar para a primeira, imo, é apenas fazer com que as chaves estrangeiras façam atualizações e exclusões em cascata.)

Shavais
fonte
1

Ok, isso foi solicitado há 7 anos, mas acho que a melhor solução aqui é renunciar inteiramente à nova tabela e fazer isso como uma exibição personalizada. Dessa forma, você não está duplicando dados, não há preocupação com dados exclusivos e não toca na estrutura real do banco de dados. Algo assim:

CREATE VIEW vw_competitions
  AS
  SELECT
   Id int
   CompetitionName nvarchar(75)
   CompetitionType nvarchar(50)
   OtherField1 int
   OtherField2 nvarchar(64)  --add the fields you want viewed from the Competition table
  FROM Competitions
GO

Outros itens podem ser adicionados aqui, como junções em outras tabelas, cláusulas WHERE etc. Essa é provavelmente a solução mais elegante para esse problema, pois agora você pode apenas consultar a exibição:

SELECT *
FROM vw_competitions

... e adicione cláusulas WHERE, IN ou EXISTS à consulta de exibição.

Beervenger
fonte