Comment supprimer un nombre fixe de lignes avec tri dans PostgreSQL?

107

J'essaie de porter certaines anciennes requêtes MySQL vers PostgreSQL, mais j'ai des problèmes avec celle-ci:

DELETE FROM logtable ORDER BY timestamp LIMIT 10;

PostgreSQL n'autorise pas l'ordre ou les limites dans sa syntaxe de suppression, et la table n'a pas de clé primaire, je ne peux donc pas utiliser de sous-requête. De plus, je souhaite conserver le comportement où la requête supprime exactement le nombre ou les enregistrements donnés - par exemple, si la table contient 30 lignes mais qu'elles ont toutes le même horodatage, je souhaite toujours supprimer 10, même si cela n'a pas d'importance dont 10.

Alors; comment supprimer un nombre fixe de lignes avec tri dans PostgreSQL?

Modifier: aucune clé primaire signifie qu'il n'y a pas de log_idcolonne ou similaire. Ah, les joies des systèmes hérités!

Qu'est ce que c'est
la source
1
Pourquoi ne pas ajouter la clé primaire? Piece o » gâteau dans postgresql: alter table foo add column id serial primary key.
Wayne Conrad
C'était ma démarche initiale, mais d'autres exigences l'empêchent.
Whatsit

Réponses:

159

Vous pouvez essayer d'utiliser le ctid:

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

Le ctidest:

L'emplacement physique de la version de ligne dans sa table. Notez que même si le ctidpeut être utilisé pour localiser la version de ligne très rapidement, une ligne ctidchangera si elle est mise à jour ou déplacée par VACUUM FULL. C'est donc ctidinutile comme identifiant de ligne à long terme.

Il y a aussi oidmais cela n'existe que si vous le demandez spécifiquement lorsque vous créez la table.

mu est trop court
la source
Cela fonctionne, mais quelle est sa fiabilité? Y a-t-il des «pièges» dont je dois faire attention? Est-il possible que VACUUM FULLou autovacuum pose des problèmes s'ils modifient les ctidvaleurs de la table pendant l'exécution de la requête?
Whatsit
2
Les VACUUM incrémentaux ne changeront pas les ctids, je ne pense pas. Puisque cela se compacte juste dans chaque page, et le ctid est juste le numéro de ligne et non un décalage de page. Un VACUUM FULL ou une opération de CLUSTER serait changer la ctid, mais ces opérations ont un accès verrou exclusif sur la première table.
araqnid
@Whatsit: Mon impression de la ctiddocumentation est qu'elle ctidest suffisamment stable pour que ce DELETE fonctionne correctement mais pas assez stable pour, par exemple, mettre dans une autre table en tant que ghetto-FK. Vraisemblablement, vous ne METTEZ PAS À JOUR le, logtabledonc vous n'avez pas à vous soucier de ce changement de ctids et VACUUM FULLverrouillez la table ( postgresql.org/docs/current/static/routine-vacuuming.html ) donc vous n'avez pas à vous soucier de l’autre façon qui ctidpeut changer. PostgreSQL-Fu de @ araqnid est assez fort et la documentation est d'accord avec lui pour démarrer.
mu est trop court
Merci à vous deux pour la clarification. J'ai regardé les documents mais je n'étais pas certain de les interpréter correctement. Je n'avais jamais rencontré de ctids avant cela.
Whatsit
C'est en fait une très mauvaise solution car Postgres ne peut pas utiliser le scan TID dans les jointures (IN en est un cas particulier). Si vous regardez le plan, cela devrait être assez terrible. Donc, "très rapidement" s'applique uniquement lorsque vous spécifiez explicitement CTID. Le dit est à partir de la version 10.
greatvovan
53

La documentation PostgreSQL recommande d'utiliser un tableau au lieu de IN et de sous-requête. Cela devrait fonctionner beaucoup plus rapidement

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

Ceci et quelques autres astuces peuvent être trouvés ici

critique
la source
@Konrad Garus Ici vous allez lien , `` Suppression rapide des n premières lignes ''
critique
1
@BlakeRegalia Non, car il n'y a pas de clé primaire dans la table spécifiée. Cela supprimera toutes les lignes avec un "ID" trouvé dans les 10. Si toutes les lignes ont le même ID, toutes les lignes seront supprimées.
Philip Whitehouse
6
Si any (array( ... ));c'est plus rapide que in ( ... )cela ressemble à un bogue dans l'optimiseur de requêtes - il devrait être capable de repérer cette transformation et de faire la même chose avec les données elles-mêmes.
rjmunro
1
J'ai trouvé que cette méthode était beaucoup plus lente que l'utilisation INsur un UPDATE(ce qui pourrait être la différence).
jmervine
1
Mesure sur une table de 12 Go: première requête 450..1000 ms, deuxième requête 5..7 secondes: Rapide: supprimer de cs_logging où id = any (array (sélectionnez id dans cs_logging où date_created <now () - interval '1 days '* 30 et partition_key comme'% I 'order by id limit 500)) Slow one: supprimer de cs_logging où id in (sélectionnez id dans cs_logging where date_created <now () - interval' 1 days '* 30 et partition_key comme'% Je commande par ID limite 500). L'utilisation de ctid était beaucoup plus lente (minutes).
Guido Leenders
14
delete from logtable where log_id in (
    select log_id from logtable order by timestamp limit 10);
Konrad Garus
la source
2

En supposant que vous souhaitiez supprimer 10 enregistrements (sans la commande), vous pouvez le faire:

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

Pour mon cas d'utilisation, la suppression de 10 millions d'enregistrements s'est avérée plus rapide.

Patrick Hüsler
la source
1

Vous pouvez écrire une procédure qui boucle sur la suppression pour des lignes individuelles, la procédure peut prendre un paramètre pour spécifier le nombre d'éléments que vous souhaitez supprimer. Mais c'est un peu exagéré par rapport à MySQL.

Bernhard
la source
0

Si vous n'avez pas de clé primaire, vous pouvez utiliser la syntaxe du tableau Where IN avec une clé composite.

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

Cela a fonctionné pour moi.

user2449151
la source