Tengo que agregar una restricción única a una tabla existente. Esto está bien, excepto que la tabla ya tiene millones de filas y muchas de las filas violan la restricción única que necesito agregar.
¿Cuál es el método más rápido para eliminar las filas ofensivas? Tengo una declaración SQL que encuentra los duplicados y los elimina, pero tarda una eternidad en ejecutarse. ¿Existe otra forma de solucionar este problema? ¿Quizás hacer una copia de seguridad de la tabla y luego restaurar después de agregar la restricción?
CREATE TABLE tmp AS SELECT ...;
. Entonces ni siquiera necesitas averiguar cuál es el diseñotmp
. :)Algunos de estos enfoques parecen un poco complicados y generalmente hago esto como:
Dada la tabla
table
, quiero que sea única en (campo1, campo2) manteniendo la fila con el campo máximo3:Por ejemplo, tengo una tabla,
user_accounts
y quiero agregar una restricción única en el correo electrónico, pero tengo algunos duplicados. Diga también que quiero mantener el creado más recientemente (ID máximo entre duplicados).USING
no es SQL estándar, es una extensión de PostgreSQL (pero muy útil), pero la pregunta original menciona específicamente a PostgreSQL.fuente
USING
en postgresql?WHERE table1.ctid<table2.ctid
- no es necesario agregar una columna de serieEn lugar de crear una nueva tabla, también puede volver a insertar filas únicas en la misma tabla después de truncarla. Hágalo todo en una sola transacción . Opcionalmente, puede eliminar la tabla temporal al final de la transacción automáticamente con
ON COMMIT DROP
. Vea abajo.Este enfoque solo es útil cuando hay muchas filas para eliminar de toda la tabla. Para unos pocos duplicados, use un archivo
DELETE
.Mencionaste millones de filas. Para que la operación sea más rápida , debe asignar suficientes búferes temporales para la sesión. La configuración debe ajustarse antes de que se use cualquier búfer temporal en su sesión actual. Descubra el tamaño de su mesa:
Establecer en
temp_buffers
consecuencia. Redondee generosamente porque la representación en memoria necesita un poco más de RAM.Este método puede ser superior a la creación de una nueva tabla si existen objetos dependientes. Vistas, índices, claves externas u otros objetos que hagan referencia a la tabla.
TRUNCATE
te hace comenzar con una pizarra limpia de todos modos (nuevo archivo en segundo plano) y es mucho más rápido queDELETE FROM tbl
con tablas grandes (enDELETE
realidad, puede ser más rápido con tablas pequeñas).Para tablas grandes, normalmente es más rápido eliminar índices y claves externas, rellenar la tabla y volver a crear estos objetos. En lo que respecta a las restricciones de fk, debe estar seguro de que los nuevos datos son válidos, por supuesto, o se encontrará con una excepción al intentar crear el fk.
Tenga en cuenta que
TRUNCATE
requiere un bloqueo más agresivo queDELETE
. Esto puede ser un problema para tablas con una carga concurrente y pesada.Si
TRUNCATE
no es una opción o en general para tablas pequeñas a medianas, existe una técnica similar con un CTE de modificación de datos (Postgres 9.1 +):Más lento para mesas grandes, porque
TRUNCATE
allí es más rápido. Pero puede ser más rápido (¡y más simple!) Para mesas pequeñas.Si no tiene ningún objeto dependiente, puede crear una nueva tabla y eliminar la anterior, pero apenas obtiene nada con este enfoque universal.
Para tablas muy grandes que no cabrían en la RAM disponible , crear una nueva tabla será considerablemente más rápido. Tendrá que sopesar esto contra posibles problemas / gastos generales con objetos dependientes.
fuente
TRUNCATE
. Como dijo Erwin, asegúrese de asegurarse de que exista antes de truncar su tabla. Vea la respuesta de @ codebykatON COMMIT DROP
, para que las personas que se pierdan la parte donde escribí "en una transacción" no pierdan datos. Y agregué BEGIN / COMMIT para aclarar "una transacción".Puede utilizar oid o ctid, que normalmente son columnas "no visibles" en la tabla:
fuente
NOT EXISTS
debería ser considerablemente más rápido :DELETE FROM tbl t WHERE EXISTS (SELECT 1 FROM tbl t1 WHERE t1.dist_col = t.dist_col AND t1.ctid > t.ctid)
- o usar cualquier otra columna o conjunto de columnas para clasificar para elegir un sobreviviente.NOT EXISTS
?EXISTS
aquí. Léalo así: "Elimine todas las filas donde exista cualquier otra fila con el mismo valordist_col
pero una más grandectid
". El único superviviente por grupo de incautos será el que tenga el mayorctid
.LIMIT
si conoce el número de duplicados.La función de ventana de PostgreSQL es útil para este problema.
Consulte Eliminar duplicados .
fuente
De una antigua lista de correo de postgresql.org :
Valores únicos
Valores duplicados
Un doble duplicado más
Seleccionar filas duplicadas
Eliminar filas duplicadas
Nota: PostgreSQL no admite alias en la tabla mencionada en la
from
cláusula de eliminación.fuente
Consulta generalizada para eliminar duplicados:
La columna
ctid
es una columna especial disponible para cada tabla, pero no visible a menos que se mencione específicamente. Elctid
valor de la columna se considera único para cada fila de una tabla.fuente
GROUP BY
cláusula: este debería ser el 'criterio de unicidad' que se viola ahora o si desea que la clave detecte duplicados. Si se especifica mal que no funcionará correctamenteAcabo de usar la respuesta de Erwin Brandstetter con éxito para eliminar duplicados en una tabla de combinación (una tabla que carece de sus propios ID principales), pero descubrí que hay una advertencia importante.
Incluir
ON COMMIT DROP
significa que la tabla temporal se eliminará al final de la transacción. Para mí, eso significaba que la tabla temporal ya no estaba disponible cuando fui a insertarla.Simplemente lo hice
CREATE TEMPORARY TABLE t_tmp AS SELECT DISTINCT * FROM tbl;
y todo funcionó bien.La tabla temporal se elimina al final de la sesión.
fuente
Esta función elimina duplicados sin eliminar índices y lo hace en cualquier tabla.
Uso:
select remove_duplicates('mytable');
fuente
fuente
Si solo tiene una o unas pocas entradas duplicadas, y de hecho están duplicadas (es decir, aparecen dos veces), puede usar la
ctid
columna "oculta" , como se propuso anteriormente, junto conLIMIT
:Esto eliminará solo la primera de las filas seleccionadas.
fuente
En primer lugar, debe decidir cuál de sus "duplicados" conservará. Si todas las columnas son iguales, está bien, puede eliminar cualquiera de ellas ... ¿Pero tal vez desee mantener solo la más reciente o algún otro criterio?
La forma más rápida depende de su respuesta a la pregunta anterior y también del% de duplicados en la mesa. Si tira el 50% de sus filas, es mejor que lo haga
CREATE TABLE ... AS SELECT DISTINCT ... FROM ... ;
, y si elimina el 1% de las filas, usar DELETE es mejor.También para operaciones de mantenimiento como esta, generalmente es bueno configurar
work_mem
una buena parte de su RAM: ejecute EXPLAIN, verifique el número N de ordenamientos / hashes y configure work_mem en su RAM / 2 / N. Use mucha RAM; es bueno para la velocidad. Siempre que solo tenga una conexión simultánea ...fuente
Estoy trabajando con PostgreSQL 8.4. Cuando ejecuté el código propuesto, descubrí que en realidad no estaba eliminando los duplicados. Al ejecutar algunas pruebas, descubrí que agregar "DISTINCT ON (duplicate_column_name)" y "ORDER BY duplicate_column_name" hizo el truco. No soy un gurú de SQL, encontré esto en el documento PostgreSQL 8.4 SELECT ... DISTINCT.
fuente
Esto funciona muy bien y es muy rápido:
fuente
Elimine los duplicados por columna (s) y mantenga la fila con la identificación más baja. El patrón está tomado de la wiki de postgres.
Al usar CTE, puede lograr una versión más legible de lo anterior a través de este
fuente
fuente