Insertar SQL Server si no existe la mejor práctica

152

Tengo una Competitionstabla de resultados que contiene los nombres de los miembros del equipo y su clasificación por un lado.

Por otro lado, necesito mantener una tabla de nombres de competidores únicos :

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

Ahora tengo unos 200,000 resultados en la primera tabla y cuando la tabla de competidores está vacía puedo realizar esto:

INSERT INTO Competitors SELECT DISTINCT Name FROM CompResults

Y la consulta solo lleva unos 5 segundos para insertar unos 11,000 nombres.

Hasta ahora, esta no es una aplicación crítica, por lo que puedo considerar truncar la tabla Competidores una vez al mes, cuando reciba los nuevos resultados de la competencia con unas 10,000 filas.

Pero, ¿cuál es la mejor práctica cuando se agregan nuevos resultados, con competidores nuevos Y existentes? No quiero truncar la tabla de competidores existentes

Necesito realizar la declaración INSERT solo para nuevos competidores y no hacer nada si existen.

Didier Levy
fuente
70
¡Por favor, no haga de una NVARCHAR(64)columna su clave principal (y por lo tanto: agrupación)! En primer lugar, es una clave muy amplia , hasta 128 bytes; y en segundo lugar, es de tamaño variable, de nuevo: no es óptimo ... Esta es la peor opción que puede tener: su rendimiento será un infierno, y la fragmentación de tablas e índices estará en 99.9% todo el tiempo .....
marc_s
44
Marc tiene un buen punto. No uses el nombre como tu pk. Use una identificación, preferiblemente int o algo liviano.
Richard
66
Vea la publicación del blog de Kimberly Tripp sobre lo que hace una buena clave de agrupación: única, estrecha, estática, en constante aumento. Su cNamefalla en tres de cuatro categorías .... (no es estrecho, es probable que no es estático, y es definitivamente no siempre creciente)
marc_s
No puedo ver el punto de agregar una clave primaria INT a una tabla de nombres de competidores donde TODAS las consultas estarán en el nombre, como 'DONDE nombre como'% xxxxx% '', por lo que siempre necesito un índice único en el nombre. Pero sí, puedo ver el punto de NO hacerlo de longitud variable ...
Didier Levy
3
a) evitando la fragmentación yb) si es la clave externa en otras tablas, los datos duplicados son más grandes que los necesarios (lo cual es una consideración de velocidad)
JamesRyan

Respuestas:

214

Semánticamente está preguntando "insertar competidores donde aún no existe":

INSERT Competitors (cName)
SELECT DISTINCT Name
FROM CompResults cr
WHERE
   NOT EXISTS (SELECT * FROM Competitors c
              WHERE cr.Name = c.cName)
gbn
fuente
2
Bueno, esto es lo que haría antes de plantear la pregunta en SO. Pero el núcleo de mi pensamiento es: ¿Qué tan bien funcionará esto contra la reconstrucción de la tabla de nombres desde cero una vez por semana más o menos? (recuerde que esto solo toma unos segundos)
Didier Levy
3
@Didier Levy: ¿Eficiencia? Por qué truncar, vuelva a crear cuando solo puede actualizar con las diferencias. Es decir: COMIENCE TRAN ELIMINAR CompResults INSERT COMPResults .. COMMIT TRAN = más trabajo.
gbn
@gbn: ¿hay alguna manera de usar la lógica if-else de forma segura aquí en lugar de su respuesta? Tengo una pregunta relacionada. ¿Me pueden ayudar con eso? stackoverflow.com/questions/21889843/…
Steam
53

Otra opción es unirse a la tabla de resultados con la tabla de competidores existentes y encontrar a los nuevos competidores filtrando los registros distintos que no coinciden en la combinación:

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

La nueva sintaxis MERGE también ofrece una forma compacta, elegante y eficiente de hacer eso:

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
fuente
1
La fusión es increíble en este caso, hace exactamente lo que dice.
VorobeY1326
Definitivamente creo que este es el camino correcto, dando a SQL Server los mejores consejos posibles para la optimización, en contraste con el enfoque de subconsulta.
Mads Nielsen
44
La declaración de MERGE todavía tiene muchos problemas. Simplemente busque en Google "Problemas de combinación de SQL": muchos bloggers lo han discutido en profundidad.
David Wilson
¿por qué hay As Target en la declaración MERGE, pero no Target en la declaración INSERT? Hay más diferencias que dificultan la comprensión de la equivalencia.
Peter
32

No sé por qué nadie más ha dicho esto todavía;

NORMALIZAR.

¿Tienes una mesa que modela competiciones? Las competiciones están formadas por competidores? Necesita una lista distinta de competidores en una o más competiciones ......

Deberías tener las siguientes tablas .....

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]
        )
    )

Con Restricciones en CompetitionCompetitors.CompetitionID y CompetitorID apuntando a las otras tablas.

Con este tipo de estructura de tabla, sus teclas son simples INTS, no parece haber una buena CLAVE NATURAL que se ajuste al modelo, por lo que creo que una CLAVE SURROGATE encaja bien aquí.

Entonces, si tenía esto, para obtener la lista distintiva de competidores en una competencia en particular, puede emitir una 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
        )

Y si quería el puntaje para cada competencia en la que se encuentra un competidor:

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]

Y cuando tiene una nueva competencia con nuevos competidores, simplemente verifique cuáles ya existen en la tabla Competidores. Si ya existen, no se inserta en el competidor para esos competidores y sí se inserta para los nuevos.

Luego inserta la nueva Competencia en Competencia y finalmente crea todos los enlaces en Competidores.

Transact Charlie
fuente
2
Suponiendo que el OP tiene la ligereza en este momento para reestructurar todas sus tablas para obtener un resultado en caché. Reescribir su base de datos y su aplicación, en lugar de resolver el problema dentro de un alcance definido, cada vez que algo no encaja fácilmente, es una receta para el desastre.
Jeffrey Vest
1
Tal vez en el caso del OP como el mío, no siempre tiene acceso para modificar la base de datos ... Y reescribir / normalizar una base de datos antigua no siempre está en el presupuesto o en el tiempo asignado.
eaglei22
10

Deberá unirse a las tablas y obtener una lista de competidores únicos que aún no existen Competitors.

Esto insertará registros únicos.

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

Puede llegar un momento en que esta inserción deba realizarse rápidamente sin poder esperar la selección de nombres únicos. En ese caso, puede insertar los nombres únicos en una tabla temporal y luego usar esa tabla temporal para insertar en su tabla real. Esto funciona bien porque todo el procesamiento ocurre en el momento en que se inserta en una tabla temporal, por lo que no afecta a su tabla real. Luego, cuando haya terminado todo el procesamiento, realice una inserción rápida en la tabla real. Incluso podría envolver la última parte, donde se inserta en la tabla real, dentro de una transacción.

Ricardo
fuente
4

¡Las respuestas anteriores que hablan sobre normalización son geniales! Pero, ¿qué pasa si te encuentras en una posición como yo donde no puedes tocar el esquema o la estructura de la base de datos tal como está? Por ejemplo, los DBA son 'dioses' y todas las revisiones sugeridas van a / dev / null?

En ese sentido, siento que esto también se ha respondido con esta publicación de Stack Overflow en lo que respecta a todos los usuarios anteriores que dan ejemplos de código.

Estoy volviendo a publicar el código de INSERTAR VALORES DONDE NO EXISTE, lo que me ayudó más, ya que no puedo alterar ninguna tabla de base de datos subyacente:

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

El código anterior usa campos diferentes de los que tiene, pero obtiene la esencia general con las diversas técnicas.

Tenga en cuenta que según la respuesta original en Stack Overflow, este código se copió desde aquí .

De todos modos, mi punto es que la "mejor práctica" a menudo se reduce a lo que puedes y no puedes hacer tan bien como a la teoría.

  • Si puede normalizar y generar índices / claves, ¡excelente!
  • Si no es así y tienes el recurso de piratear códigos como yo, espero que lo anterior ayude.

¡Buena suerte!


fuente
En caso de que no esté claro, se trata de cuatro enfoques diferentes para el problema, así que elija uno.
nasch
3

Normalizar sus tablas operativas como lo sugiere Transact Charlie, es una buena idea, y ahorrará muchos dolores de cabeza y problemas con el tiempo, pero existen tablas de interfaz , que admiten la integración con sistemas externos, y tablas de informes , que admiten cosas como análisis Procesando; y esos tipos de tablas no necesariamente deben normalizarse ; de hecho, muy a menudo es mucho, mucho más conveniente y eficaz para que no lo sean .

En este caso, creo que la propuesta de Transact Charlie para sus tablas operativas es buena.

Pero agregaría un índice (no necesariamente único) a CompetitorName en la tabla Competidores para admitir uniones eficientes en CompetitorName con fines de integración (carga de datos de fuentes externas), y pondría una tabla de interfaz en la mezcla: CompeticiónResultados.

Los resultados de la competencia deben contener los datos que contengan los resultados de su competencia. El objetivo de una tabla de interfaz como esta es hacer que sea lo más rápido y fácil posible truncar y volver a cargarlo desde una hoja de Excel o un archivo CSV, o cualquier forma en la que tenga esos datos.

Esa tabla de interfaz no debe considerarse parte del conjunto normalizado de tablas operativas. Luego puede unirse a CompetResults como lo sugiere Richard, para insertar registros en Competidores que aún no existen, y actualizar los que sí existen (por ejemplo, si realmente tiene más información sobre competidores, como su número de teléfono o dirección de correo electrónico).

Una cosa que señalaría: en realidad, el nombre de la competencia, me parece, es muy poco probable que sea único en sus datos . En 200,000 competidores, es muy posible que tenga 2 o más David Smiths, por ejemplo. Por lo tanto, le recomendaría que recopile más información de los competidores, como su número de teléfono o una dirección de correo electrónico, o algo que sea más probable que sea único.

Su tabla operativa, Competidores, solo debe tener una columna para cada elemento de datos que contribuya a una clave natural compuesta; por ejemplo, debe tener una columna para una dirección de correo electrónico principal. Pero la tabla de interfaz debe tener un espacio para los valores antiguos y nuevos para una dirección de correo electrónico principal, de modo que el valor anterior se pueda usar para buscar el registro en Competidores y actualizar esa parte al nuevo valor.

Por lo tanto, los resultados de la competencia deben tener algunos campos "antiguos" y "nuevos": correo electrónico antiguo, correo electrónico nuevo, teléfono antiguo, teléfono nuevo, etc. De esa manera puede formar una clave compuesta, en Competidores, desde Nombre de competidor, Correo electrónico y Teléfono.

Luego, cuando tenga algunos resultados de la competencia, puede truncar y volver a cargar su tabla de Resultados de Competencia desde su hoja de Excel o lo que sea que tenga, y ejecutar una inserción única y eficiente para insertar a todos los nuevos competidores en la tabla de Competidores, y una actualización única y eficiente para actualizar toda la información sobre los competidores existentes de los Resultados de la competencia. Y puede hacer una sola inserción para insertar nuevas filas en la tabla CompetitionCompetitors. Estas cosas se pueden hacer en un procedimiento almacenado ProcessCompetitionResults, que se puede ejecutar después de cargar la tabla CompetitionResults.

Esa es una especie de descripción rudimentaria de lo que he visto hacer una y otra vez en el mundo real con Oracle Applications, SAP, PeopleSoft y una larga lista de otras suites de software empresarial.

Un último comentario que haría es uno que he hecho antes sobre SO: si crea una clave foránea que asegura que existe un competidor en la tabla de competidores antes de poder agregar una fila con ese competidor en CompetitionCompetitors, asegúrese de que la clave externa se establece en actualizaciones y eliminaciones en cascada . De esa manera, si necesita eliminar un competidor, puede hacerlo y todas las filas asociadas con ese competidor se eliminarán automáticamente. De lo contrario, de forma predeterminada, la clave externa requerirá que elimine todas las filas relacionadas de CompetCompetitors antes de permitirle eliminar un Competidor.

(Algunas personas piensan que las claves externas no en cascada son una buena precaución de seguridad, pero mi experiencia es que solo son un dolor en el trasero que con frecuencia son simplemente el resultado de un descuido y crean un montón de trabajo). para DBA. Al tratar con personas que eliminan accidentalmente cosas, es por eso que tiene cosas como cuadros de diálogo "¿está seguro?" y varios tipos de copias de seguridad regulares y fuentes de datos redundantes. Es mucho, mucho más común querer eliminar un competidor, cuyos datos son todos en mal estado, por ejemplo, que eliminar accidentalmente uno y luego decir "¡Oh, no! ¡No quise hacer eso! ¡Y ahora no tengo los resultados de su competencia! ¡Aaaahh!" , debes estar preparado para ello, pero el primero es mucho más común,así que la manera más fácil y mejor de prepararse para la primera, imo, es simplemente realizar actualizaciones y eliminaciones en cascada de claves foráneas).

Shavais
fuente
1

Ok, esto se preguntó hace 7 años, pero creo que la mejor solución aquí es renunciar a la nueva tabla por completo y hacer esto como una vista personalizada. De esa forma no está duplicando datos, no hay que preocuparse por datos únicos y no toca la estructura de la base de datos real. Algo como esto:

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

Aquí se pueden agregar otros elementos, como combinaciones en otras tablas, cláusulas WHERE, etc. Esta es probablemente la solución más elegante para este problema, ya que ahora puede consultar la vista:

SELECT *
FROM vw_competitions

... y agregue cualquier cláusula WHERE, IN o EXISTS a la consulta de vista.

Beervenger
fuente