¿Cómo elimino un número fijo de filas con la clasificación en PostgreSQL?

107

Estoy tratando de transferir algunas consultas de MySQL antiguas a PostgreSQL, pero tengo problemas con esta:

DELETE FROM logtable ORDER BY timestamp LIMIT 10;

PostgreSQL no permite pedidos o límites en su sintaxis de eliminación, y la tabla no tiene una clave principal, por lo que no puedo usar una subconsulta. Además, quiero conservar el comportamiento en el que la consulta elimina exactamente el número o los registros dados; por ejemplo, si la tabla contiene 30 filas pero todas tienen la misma marca de tiempo, todavía quiero eliminar 10, aunque no importa cual 10.

Entonces; ¿Cómo elimino un número fijo de filas con la clasificación en PostgreSQL?

Editar: Sin clave principal significa que no hay log_idcolumna o similar. ¡Ah, el placer de los sistemas heredados!

Que es eso
fuente
1
¿Por qué no agregar la clave principal? Pieza o' pastel en PostgreSQL: alter table foo add column id serial primary key.
Wayne Conrad
Ese fue mi enfoque inicial, pero otros requisitos lo impiden.
Whatsit

Respuestas:

159

Podría intentar usar ctid:

DELETE FROM logtable
WHERE ctid IN (
    SELECT ctid
    FROM logtable
    ORDER BY timestamp
    LIMIT 10
)

El ctides:

La ubicación física de la versión de fila dentro de su tabla. Tenga en cuenta que, aunque ctidse puede utilizar para localizar la versión de la fila muy rápidamente, una fila ctidcambiará si se actualiza o se mueve VACUUM FULL. Por ctidlo tanto, es inútil como identificador de fila a largo plazo.

También existe, oidpero eso solo existe si lo solicita específicamente cuando crea la tabla.

mu es demasiado corto
fuente
Esto funciona, pero ¿qué tan confiable es? ¿Hay algún 'error' que deba tener en cuenta? ¿Es posible que VACUUM FULLo el autovacío cause problemas si cambian los ctidvalores en la tabla mientras se ejecuta la consulta?
Whatsit
2
Las VACÍAS incrementales no cambiarán las ctids, no creo. Dado que eso solo se compacta dentro de cada página, y el ctid es solo el número de línea, no un desplazamiento de página. Un vacío completo o una operación del clúster podrían cambiar la ctid, pero esas operaciones tienen un acceso a bloqueo exclusivo sobre la mesa en primer lugar.
araqnid
@Whatsit: Mi impresión de la ctiddocumentación es que ctides lo suficientemente estable como para que este DELETE funcione bien, pero no lo suficientemente estable como para, por ejemplo, poner en otra tabla como ghetto-FK. Es de suponer que no ACTUALIZA el, logtablepor lo que no tiene que preocuparse por los cambios ctidy VACUUM FULLbloquea la tabla ( postgresql.org/docs/current/static/routine-vacuuming.html ) para que no tenga que preocuparse por de la otra manera eso ctidpuede cambiar. PostgreSQL-Fu de @ araqnid es bastante fuerte y los docs están de acuerdo con él.
mu es demasiado corto
Gracias a ambos por la aclaración. Miré los documentos, pero no estaba seguro de estar interpretando correctamente. Nunca me había encontrado con ctids antes de esto.
Whatsit
En realidad, esta es una solución bastante mala, ya que Postgres no puede usar el escaneo TID en las combinaciones (IN es un caso particular). Si miras el plan, debería ser bastante terrible. Así que "muy rápidamente" se aplica solo cuando especifica CTID explícitamente. Dicho es a partir de la versión 10.
greatvovan
53

Los documentos de Postgres recomiendan usar una matriz en lugar de IN y subconsulta. Esto debería funcionar mucho más rápido

DELETE FROM logtable 
WHERE id = any (array(SELECT id FROM logtable ORDER BY timestamp LIMIT 10));

Este y otros trucos se pueden encontrar aquí.

critico
fuente
@Konrad Garus Aquí tienes el enlace , 'Eliminación rápida de las primeras n filas'
critico
1
@BlakeRegalia No, porque no hay una clave principal en la tabla especificada. Esto eliminará todas las filas con un "ID" que se encuentran en los primeros 10. Si todas las filas tienen el mismo ID, todas las filas se eliminarán.
Philip Whitehouse
6
Si any (array( ... ));es más rápido que in ( ... )eso, suena como un error en el optimizador de consultas, debería poder detectar esa transformación y hacer lo mismo con los datos en sí.
rjmunro
1
Encontré que este método es considerablemente más lento que usarlo INen un UPDATE(lo que podría ser la diferencia).
Jmervine
1
Medición en una tabla de 12 GB: primera consulta 450..1000 ms, segunda 5..7 segundos: Rápida: eliminar de cs_logging donde id = any (matriz (seleccionar id de cs_logging donde date_created <ahora () - intervalo '1 días '* 30 y la clave de partición como'% I 'orden por límite de identificación 500)) Uno lento: eliminar de cs_logging donde se encuentra la identificación (seleccione la identificación de cs_logging donde date_created <ahora () - intervalo' 1 días '* 30 y clave de partición como'% Ordeno por límite de identificación 500). Usar ctid fue mucho más lento (minutos).
Guido Leenders
14
delete from logtable where log_id in (
    select log_id from logtable order by timestamp limit 10);
Konrad Garus
fuente
2

Suponiendo que desea eliminar CUALQUIER 10 registros (sin el pedido), puede hacer esto:

DELETE FROM logtable as t1 WHERE t1.ctid < (select t2.ctid from logtable as t2  where (Select count(*) from logtable t3  where t3.ctid < t2.ctid ) = 10 LIMIT 1);

Para mi caso de uso, eliminar 10 millones de registros, resultó ser más rápido.

Patrick Hüsler
fuente
1

Puede escribir un procedimiento que recorra la eliminación de líneas individuales, el procedimiento podría tomar un parámetro para especificar la cantidad de elementos que desea eliminar. Pero eso es un poco exagerado en comparación con MySQL.

Bernhard
fuente
0

Si no tiene una clave principal, puede usar la sintaxis de la matriz Where IN con una clave compuesta.

delete from table1 where (schema,id,lac,cid) in (select schema,id,lac,cid from table1 where lac = 0 limit 1000);

Esto funcionó para mí.

usuario2449151
fuente