LINUX.ORG.RU

Как удалить дубликаты из таблицы Postgresql?

 


0

1

Таблица:

CREATE TABLE public.order_clients (
  order_id int8 NOT NULL,
  client_id int8 NOT NULL,
  linked_at timestamptz DEFAULT now() NOT NULL,
);

В таблице есть дубликаты, и я хочу удалить не уникальные строки. Связка order_id и client_id должна быть уникальной.

Но это еще не все:

  • ~43 миллиона записей в таблице. В таблицу постоянно что-то записывается

  • я не могу добавить флаг не уникальности в таблицу (таблица используется все время)

  • я также не могу записать уникальные записи в другую таблицу

  • я также не могу использовать ctid (добавляются новые записи, наверное это будет не лучшая идея)

  • я должен использовать limit и where с order_id для постепенного удаления.

Я попытался использовать этот запрос:

DELETE FROM "order_clients"
WHERE ("order_id", "client_id") IN (
 SELECT "order_id", "client_id"
 FROM (
  SELECT "order_id", "client_id", ROW_NUMBER() OVER (PARTITION BY "order_id", "client_id" ORDER BY "order_id" ASC) AS row_num
   FROM "order_clients"
   WHERE "order_id" > 0
  ) AS subquery
WHERE row_num > 1

Однако подзапрос возвращает 315 строк, а весь запрос удаляет 324 строки (остается только один order_id с одним client_id). На бэке Node.JS, можно попробовать как-то на JS порешать.

Есть идеи?

UPD: записей в базе не 43k, а ~43 миллиона

★★★★★

Последнее исправление: CryNet (всего исправлений: 4)

Ответ на: комментарий от asdpm

Ага, а потом изобретать временные айдишники, чтобы сложные запросы связывать. Лучше пусть ненужный PK будет, чем страдать потом. Да тупо, по айди видно сходу временной промежуток вставки, что намного упрощает анализ глазами.

Loki13 ★★★★★
()
Ответ на: комментарий от Loki13

А насколько безопасно использовать ctid в запросе на удаление, например:

DELETE FROM order_clients
  WHERE ctid IN (
    SELECT DISTINCT o.ctid 
      FROM order_clients AS o
      JOIN order_clients AS t
        ON t.client_id = o.client_id AND t.order_id = o.order_id
          AND t.linked_at < o.linked_at
      LIMIT 1000
  );

Чем это чревато?

CryNet ★★★★★
() автор топика
Ответ на: комментарий от CryNet

Если верить статье с хабра(я не специалист по pg), то вроде безопасно

ctid — «физический» адрес записи в формате (<страница>,<смещение>)

Например, ctid можно использовать при операциях с таблицей без первичного ключа

Кстати, ИИ предлагает что-то такое для удаления

WITH cte AS (
   SELECT ctid
   FROM   calendar_event
   WHERE  user_id = 5
   LIMIT  100
   FOR    UPDATE SKIP LOCKED
   )
DELETE FROM calendar_event WHERE ctid IN (TABLE cte);
Loki13 ★★★★★
()
Ответ на: комментарий от Loki13

тупо, по айди видно сходу временной промежуток

PK не обязаны нарастать монотонно

потом изобретать временные айдишники, чтобы сложные запросы связывать

в запросах строки можно ассоциировать колонками естественного PK или колонками имеющими unique констрейнт. суррогатный PK id int для этого не нужен

asdpm
()
Ответ на: комментарий от asdpm

PK не обязаны нарастать монотонно

А где так сделано? Ну и монотонно да, но нарастать всё же будут. И когда работаешь с таблицей пол года, уже наизусть знаешь какие индексы к какому году относятся.

суррогатный PK id int для этого не нужен

Да, но юник должен всё же быть. Если ты делаешь без id таблицу, то озаботься, чтобы был другой уникальный ключ. А не как у ТС ситуация, где в таблицу просто насрано.

Loki13 ★★★★★
()
Последнее исправление: Loki13 (всего исправлений: 1)
Ответ на: комментарий от Toxo2

Если бы такое происходило […] - это невероятно тупейший баг

Почти невозможно себе представить

99 к 100

я не знаю, как это комментировать.

следуя этой логике программисту чтобы изготовить корректный код, достаточно выдумать набор каких-нибудь удобных допущений и сделать эти допущения

asdpm
()
Ответ на: комментарий от asdpm

из create table скорее можно предполагать обратное

это же ваши слова - вот на них я и отвечаю, что «скорее можно предполагать» вовсе наоборот. На одно ваше «скорее», 99 более понятных в жизни «скор».

Да, шут с этим ) Нет повода спорить до хрипоты, до драки.

Toxo2 ★★★★
()
Ответ на: комментарий от asdpm

PK не обязаны нарастать монотонно

Не обязаны, но это очень полезно. Чтобы делать таблицу без серийного id, нужны очень веские аргументы. А чтобы вообще без уникального PK, это какие-то уникальные случаи. У ТСа просто какая-то говношарага.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Чтобы делать таблицу без серийного id, нужны очень веские аргументы.

нет, наличие колонок образующих естественный PK - достаточный аргумент чтобы не добавлять еще и колонку с суррогатным PK, и просто назначить таковым имеющиеся

серийного id

даже если ключ суррогатный, то он может 1) назначаться клиентом как угодно согласно любым правилам 2) он может не нарастать 3) может быть строкой или чем угодно еще 4) если его обслуживает сиквенс, то он может быть один на несколько таблиц 5) может опосредованно иметь в основе генератор случайных чисел

идея о том что PK должен обязательно быть колонкой id c нарастающими int - это глупость очень популярная среди мудаков не включающих мозг

дополнено: более того. наличие pk id c нарастающими интами, на практике часто является попыткой тупых бракоделов обеспечить иллюзию уникальности строк, иллюзию порядка в БД, иллюзию качества данных. нередко в таких базах половина таблиц содержит строки, которые по сути своей дубликаты (числа по колонке id у них конечно же уникальны, это субд обеспечит). дубликаты - это плохо: от этого возникает путаница в организации, возникают проблемы у пользователей, ненужные трения среди разработчиков. конечно, чтобы делать нормальные таблицы - надо думать, работать, а они не хотят и не умеют, вот и тащат свой тупой нарастающий суррогатный id.

asdpm
()
Последнее исправление: asdpm (всего исправлений: 1)
Ответ на: комментарий от asdpm

Удачи тебе с репликацией, архивацией, шардингом и всем вот этим с твоими PK из жопы клиента. Делать таблицу без id это как говорится, экономить на спичках и на говне сметану собирать.

no-such-file ★★★★★
()

у ТС таблица многие ко многим, там составной индекс.

почему образуются несколько одинаковых связей непонятно, видимо это должно фиксится на этапе создания связи.

zudwa
()
Ответ на: комментарий от Loki13

А где так сделано? Ну и монотонно да, но нарастать всё же будут. И когда работаешь с таблицей пол года, уже наизусть знаешь какие индексы к какому году относятся.

Много где. Очень часто рандомный UUID в качестве PK идёт.

hateyoufeel ★★★★★
()
Ответ на: комментарий от hateyoufeel

Очень часто рандомный UUID в качестве PK идёт.

Это очень неудобно. Ибо при отладке процедур и скриптов фиг удобно передашь WHERE id = 100, а приходится громоздкий UUID вписывать. Всегда старался, чтобы рядом с UUID всегда и ID шел. Просто для удобства своего и будущих программистов. Ну и таскать этот UUID в виде FK потом по всей базе, тоже такое себе удовольствие.

Loki13 ★★★★★
()
Ответ на: комментарий от Loki13

Ибо при отладке процедур и скриптов фиг удобно передашь WHERE id = 100, а приходится громоздкий UUID вписывать

Это вообще не аргумент. Разницы нет какой длины строку копировать в буфер обмена (если он в экран лезет).

А вот с монотонным числовым ID данные твоих особо важных клиентов какеры будут обычным перебором доставать.

her_s_gory
()
Ответ на: комментарий от her_s_gory

А вот с монотонным числовым ID данные твоих особо важных клиентов какеры будут обычным перебором доставать.

Если у тебя уже взломали БД и имею прямой доступ к таблицам, то уже не важно какой там UUID. Поздно пить боржоми. Я же про внутренние id в таблицах. Если очень хочется, то всегда можно завести отдельный UUID для запросов из веба, но не нести его по всей БД ключом. Тем более в виде PK.

Loki13 ★★★★★
()
Последнее исправление: Loki13 (всего исправлений: 1)
Ответ на: комментарий от Loki13

Это очень неудобно. Ибо при отладке процедур и скриптов фиг удобно передашь WHERE id = 100, а приходится громоздкий UUID вписывать.

Ваще насрать.

Всегда старался, чтобы рядом с UUID всегда и ID шел. Просто для удобства своего и будущих программистов.

Тут нет удобства.

Ну и таскать этот UUID в виде FK потом по всей базе, тоже такое себе удовольствие.

Да нет, проблемы в этом никакой нету. Считай, что это просто 128-битное число и всё.

hateyoufeel ★★★★★
()
Ответ на: комментарий от Loki13

Если у тебя уже взломали БД

ID часто присутствуют в get запросах. Вот ими и переберут - без взлома непосредственно базы.

Я же про внутренние id в таблицах.

Лишняя сущность чтобы что? для дебага?

her_s_gory
()
Последнее исправление: her_s_gory (всего исправлений: 2)
Ответ на: комментарий от her_s_gory

Если у тебя уже взломали БД

ID часто присутствуют в get запросах. Вот ими и переберут - без взлома непосредственно базы.

Если у тебя доступ к ресурсам осуществляется только на основании знания id ресурса, тебе надо уволиться и выйти из профессии. Другими словами, не надо так делать. Вообще никогда. Безотносительно того, какой формат id в базе ты используешь.

hateyoufeel ★★★★★
()
Ответ на: комментарий от hateyoufeel

Если у тебя доступ к ресурсам осуществляется только на основании знания id ресурса

https://habr.com/ru/articles/812975/ вот доступ к ресусрсу только по ID. Хабраписателей надо уволить?

her_s_gory
()
Последнее исправление: her_s_gory (всего исправлений: 1)
Ответ на: комментарий от hateyoufeel

Тут нет удобства.

Крикнуть через комнату, Виктор, глянь, там для айдишника 100500 глючит запрос, намного проще, чем UUID перебрасываться в мессенджере. Но дело вкуса, согласен, кому-то и UUID удобно наверное.

Loki13 ★★★★★
()
Ответ на: комментарий от Loki13

Крикнуть через комнату, Виктор, глянь, там для айдишника 100500

это да, прям почти война с людями - я им про UUIDv7, про репликации Мастер-Мастер, они мне «а как мы будем кричать через комнату?»

На откровенный конфликт идти не хочется, поэтому в БД всё перевожу на UUIDы тихонько на ключах и связях, но и эти кричалки приходится за собой таскать.

Toxo2 ★★★★
()
Последнее исправление: Toxo2 (всего исправлений: 1)
Ответ на: комментарий от Loki13

Крикнуть через комнату, Виктор, глянь, там для айдишника 100500 глючит запрос, намного проще, чем UUID перебрасываться в мессенджере. Но дело вкуса, согласен, кому-то и UUID удобно наверное.

У тебя просто чл^Wбаза маленькая. Когда айди по длине превзойдут номера телефонов, кричать перехочется.

hateyoufeel ★★★★★
()
Ответ на: комментарий от hateyoufeel

Тут нет авторизации и разделения доступа. Соответственно, вопрос безопасности не стоит.

Зато легко вычитываются тексты, на которых тупые боты учатся. Многие теперь из-за этого бабок не досчитались и логти кусают. С ID в виде UUID - уже было бы совсем непросто (пришлось бы поисковик писать).

her_s_gory
()
Ответ на: комментарий от theNamelessOne

Хотя часто удобно, если рандомные UUID всё равно генерируются упорядоченными по времени, как вUUIDv7и ещё кое-где.

ичо? Суть в том, что они генерируются случайно и тебе не надо лочить счётчик у таблицы чтобы сделать новый UUID при вставке и при этом не нарваться на коллизию.

hateyoufeel ★★★★★
()
Ответ на: комментарий от hateyoufeel

не надо лочить счётчик у таблицы

Там не счётчик лочить надо. Там всю вставку лочить надо и это совсем не про id vs uuid, а про то, чтобы Вася и Петя не создали одновременно одно и тоже с разными ключами.

Поэтому pg_try_advisory_xact_lock() в процедуре вставки записи в таблицу. И таблица не блокируется, и Петя не вставит, пока Вася вставляет.

Суть в том, что они генерируются случайно

А v7 не просто случайно, а ещё и упорядоченно по метке времени. Сразу тебе и уникальный ключ, и created_at в одном флаконе. Помимо остальных плюшек.

Toxo2 ★★★★
()
Последнее исправление: Toxo2 (всего исправлений: 2)