Як видалити фіксовану кількість рядків із сортуванням у PostgreSQL?


107

Я намагаюся перенести деякі старі запити MySQL до PostgreSQL, але у мене виникають проблеми з цим:

DELETE FROM logtable ORDER BY timestamp LIMIT 10;

PostgreSQL не дозволяє замовляти чи обмежувати у своєму синтаксисі видалення, а у таблиці немає первинного ключа, тому я не можу використовувати підзапит. Крім того, я хочу зберегти поведінку, коли запит видаляє точно вказане число або записи - наприклад, якщо таблиця містить 30 рядків, але всі вони мають однакову часову позначку, я все одно хочу видалити 10, хоча це не має значення яких 10.

Так; як видалити фіксовану кількість рядків із сортуванням у PostgreSQL?

Редагувати: жоден первинний ключ означає, що немає log_idстовпця чи подібного. Ах, радощі застарілих систем!


1
Чому б не додати первинний ключ? Шматок пирога в postgresql : alter table foo add column id serial primary key.
Уейн Конрад

Це був мій початковий підхід, але інші вимоги йому заважають.
Whatsit

Відповіді:


159

Ви можете спробувати скористатися ctid:

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

Це ctid:

Фізичне розташування рядкової версії в межах її таблиці. Зауважте, що, хоча ви ctidможете використовувати для пошуку версії рядка дуже швидко, рядки ctidзміниться, якщо вона буде оновлена ​​або переміщена VACUUM FULL. Тому ctidмарно як довгостроковий ідентифікатор рядків.

Існує також, oidале це існує лише в тому випадку, якщо ви спеціально запитаєте про це під час створення таблиці.


Це працює, але наскільки це надійно? Чи є якісь "готчі", на які я повинен звернути увагу? Чи можливо для VACUUM FULLабо autovacuum викликати проблеми, якщо вони змінюють ctidзначення в таблиці під час запуску запиту?
Whatsit

2
Зростання VACUUM не змінить ctids, я не думаю. Оскільки це просто ущільнюється у межах кожної сторінки, а ctid - це лише номер рядка, а не зміщення сторінки. Вакуумному ПОВНИЙ або операція Кластер б змінити CTID, але ці операції беруть доступу ексклюзивну блокування на таблицю першої.
araqnid

@Whatsit: Моє враження від ctidдокументації полягає в тому, що ctidце досить стабільно, щоб зробити це DELETE нормально, але недостатньо стабільним, щоб, наприклад, помістити в іншу таблицю як FK-гетто. Імовірно, ви не ОНОВЛЮЄТЬСЯ, logtableтому вам не доведеться турбуватися про цю зміну ctids і VACUUM FULLзаблокує таблицю ( postgresql.org/docs/current/static/routine-vacuuming.html ), тому вам не доведеться турбуватися про інший спосіб, який ctidможна змінити. PostgreSQL-Fu @ araqnid досить сильний, і документи згодні з ним завантажуватися.
mu занадто короткий

Дякую обом за роз’яснення. Я заглянув у документи, але не був певен, чи правильно їх інтерпретую. Я ніколи до цього не стикався з ctids.
Whatsit

Це насправді є досить поганим рішенням, оскільки Postgres не в змозі використовувати TID-сканування для приєднання (IN - це окремий випадок). Якщо подивитися на план, він повинен бути досить жахливим. Тож "дуже швидко" застосовується лише тоді, коли ви чітко вказали CTID. Сказано, що версії 10.
greatvovan

53

Документи Postgres рекомендують використовувати масив замість IN та підзапит. Це повинно працювати набагато швидше

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

Цей та деякі інші хитрощі можна знайти тут


@Konrad Garus Перейдіть за посиланням "Швидке видалення перших n рядків"
критик

1
@BlakeRegalia Ні, оскільки в таблиці не вказаний первинний ключ. Це видалить усі рядки з "ідентифікатором", знайденим у першій 10. Якщо всі рядки мають однаковий ідентифікатор, усі рядки будуть видалені.
Філіп Уайтхаус

6
Якщо any (array( ... ));швидше in ( ... )це звучить як помилка в оптимізаторі запитів - він повинен мати можливість помітити цю трансформацію і зробити те саме з самими даними.
rjmunro

1
Я знайшов цей метод значно повільніше, ніж використання INна UPDATE(що може бути різницею).
jmervine

1
Вимірювання в таблиці 12 ГБ: перший запит 450..1000 мс, другий 5..7 секунд: швидкий: видалення з cs_logging, де id = будь-який (масив (виберіть ідентифікатор з cs_logging, де date_create <зараз () - інтервал '1 день '* 30 та partition_key на зразок'% I 'упорядковуємо за лімітом ідентифікатора 500)) Повільний: видаліть із cs_logging, де id (виберіть ідентифікатор з cs_logging, де date_create <зараз () - інтервал' 1 дні '* 30 та partition_key, як'% Я замовляю за лімітом ідентифікатора 500). Використання ctid було набагато повільніше (хвилин).
Гвідо Ліндерс


2

Припускаючи, що ви хочете видалити БУДЬ-10 записів (без замовлення), ви можете це зробити:

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

У моєму випадку використання, видаливши записи 10М, це виявилося швидше.


1

Ви можете написати процедуру, яка перетворює цикл на видалення для окремих рядків, процедура може прийняти параметр, щоб вказати кількість елементів, які потрібно видалити. Але це трохи непосильне порівняно з MySQL.


0

Якщо у вас немає первинного ключа, ви можете використовувати масив синтаксису Where IN із складеним ключем.

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

Це працювало для мене.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.