LINUX.ORG.RU

Удалить из массива элементы

 ,


0

1

Удалить из массива элементы

Использую СУБД Postgresql. Есть таблица вида:

CREATE TABLE docs (
   id int PRIMARY KEY,
   account bigint,
   contact TEXT []
);
INSERT INTO docs VALUES(1, 22, ARRAY ['Иван', 'Петр']);
INSERT INTO docs VALUES(2, 22, ARRAY ['Иван']);
INSERT INTO docs VALUES(3, 22, ARRAY['Иван', 'Виктор']);
INSERT INTO docs values(4, 22, ARRAY['Иван', 'Роман', 'Сергей']);
Есть массив строк контактов(например ['Иван', 'Петр']) и аккаунт, нужно из поля contact удалить этих пользователей. Если после этого массив пустой, то нужно удалить запись целиком. Например из набора:
1, 22, ARRAY ['Иван', 'Петр']
2, 22, ARRAY ['Иван', 'Петр']
3, 22, ARRAY['Иван', 'Виктор']
4, 22, ARRAY['Иван', 'Роман', 'Сергей']
После обновления таблица должна иметь вида:
3, 22, ARRAY['Виктор']
4, 22, ARRAY['Роман', 'Сергей']
Просьба помочь это сделать одним запросом

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

Вынеси contacts в отдельную таблицу, сделай таблицу связи docs_contacts(doc_id, contact_id), потом удаляй из этой таблицы просто нужные строки.

Можно и без отдельной таблицы contacts обойтись, но в реальном мире скорее всего она пригодится.

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

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

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

В вашей прошлой теме буквально и дословно это делали. Что не устроило в том варианте?

Toxo2 ★★★★
()

Да, никогда не пиши СУБД-специфичный код, даже автоикремент не употребляй - триггеры есть везде. Делай свои SQL-запросы максимально переносимыми.

LongLiveUbuntu ★★★★★
()

Задачу невозможно решить одним запросом. Для удаления контакта из массивов нужен запрос типа update. Для удаления документов с пустым массивом контактов нужен запрос типа delete. Разве это не очевидно?

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

CTE (comon table expression) это select. insert, update и delete позволяют использовать cte, это позволяет комбинировать их с select: select+insert, select+update, select+delete. Но как ты собираешься использовать cte для комбинирования update+delete?

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

использовать cte для комбинирования update+delete?

Может вы поясните, чем вот то, что я писал плохо?

WITH cte_user AS (
	SELECT DISTINCT UNNEST(ARRAY['27672','6145']) AS "Id"
)
,cte_sel AS (
	SELECT
		 d."@Id" 
		,d."Account" 
		,(
			SELECT array_agg("Id") 
			FROM UNNEST(d."User") ou("Id")
			WHERE NOT EXISTS (SELECT FROM  cte_user cu WHERE cu."Id" = ou."Id")
		) AS "NewUser"
	FROM
		"Documents" d
	WHERE 
		d."Account" = 4777912
)
,cte_upd AS (
	UPDATE "Documents" d
	SET "User" = s."NewUser"
	FROM cte_sel s
	WHERE
		d."@Id" = s."@Id"
		AND s."NewUser" IS NOT NULL
)
DELETE FROM "Documents" d
USING cte_sel s
WHERE
	d."@Id" = s."@Id"
	AND s."NewUser" IS NULL
Вроде нормальный рабочий проверенный вариант. CTE с update+delete.

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

Во первых, такой запрос вредит ментальному здоровью разработчика (большинство, к сожалению, даже не смогут его прочитать). Во вторых, он плохо читается и содержит сложные условия. В третьих, нормализация данных позволяет избежать первых двух проблем.

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

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

А почему для вас проблема что в таблице много записей? Ведь очевидно что проблема была не в занимаемом месте (т.к. в таком виде места жрет куда больше).

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

не задачка из учебника как задрочиться в SQL, а практическая в работающей системе

Я вас уверяю, практические задачи в работающей системе, которую до тебя разрабатывали с десяток студентов, или даже с десяток Гуру (но каждый - со своим видением) - ни одному учебнику не снились по «задрочится так, чтобы всё работало как раньше, останавливать нельзя, менять структуру нельзя».

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

Да, никогда не пиши СУБД-специфичный код, даже автоикремент не употребляй - триггеры есть везде. Делай свои SQL-запросы максимально переносимыми.

Зачем? У него и так postgres уже.

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

Это здесь и сейчас postgres, а ну сменят СУБД на другую? Или место работы сменит или еще что. А еще бывает, что поведение sql-СУБД специфического кода при смене версии ломается, сам видел. Поэтому скрипт должен быть максимально логически простым.

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

Спасибо, проверю ваш вариант, думал что можно проще написать запрос.

Предполагал, что можно найти разность текстовых массивов, по аналогии с числовыми массивами типа

select array[1,2,3] - array[2,3] = array[1]

Но к сожаление это не работает для строк.

Планировал:

  1. обновить в cte массивы, как разность двух массивов

  2. удалить записи с пустыми массивами

polin11
() автор топика
Последнее исправление: polin11 (всего исправлений: 5)

Использовать РСУБД, чтобы пихать в поля текст и изобретать велосипеды для формализации… Это какая-то спецолимпиада?

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

Советую использовать jsonb вместо [], с ним многие вещи становятся проще.

CREATE TABLE docs (
   id int PRIMARY KEY,
   account bigint,
   contact jsonb
);
INSERT INTO docs VALUES(1, 22, '["Иван", "Петр"]');
INSERT INTO docs VALUES(2, 22, '["Иван"]');
INSERT INTO docs VALUES(3, 22, '["Иван", "Виктор"]');
INSERT INTO docs values(4, 22, '["Иван", "Роман", "Сергей"]');

Вот так одним запросом, без лишних троганий записей которые не нужно менять и без повторений.

WITH changed_contacts AS (
    SELECT
        id,
        new_contact,
        new_contact = '[]'::jsonb AS should_remove
    FROM (
        SELECT
            id,
            contact AS old_contact,
            contact - ARRAY['Иван', 'Петр'] AS new_contact
        FROM docs
    ) AS tmp
    WHERE new_contact != old_contact
),
deletions AS (
    DELETE FROM docs
    WHERE id IN (
        SELECT id FROM changed_contacts WHERE should_remove
    )
)
UPDATE docs
SET contact = new_contact
FROM changed_contacts
WHERE docs.id = changed_contacts.id AND NOT should_remove; 

Вот так короче, но менее красиво: с повторением входного массива, хаком для CREATE TABLE AS которому нужен SELECT и в 2 запроса (потому что в CTE нельзя менять строки из разных запросов):

BEGIN;
CREATE TEMPORARY TABLE updated ON COMMIT DROP AS
WITH tmp AS (
    UPDATE docs
    SET 
        contact = contact - ARRAY['Иван', 'Петр']
    WHERE contact != contact - ARRAY['Иван', 'Петр']
    RETURNING id, contact = '[]'::jsonb AS should_remove
)
SELECT * FROM tmp;
DELETE FROM docs WHERE id IN (SELECT id FROM updated WHERE should_remove);
COMMIT;

Можно адаптировать это под text[] либо конвертацией в/из jsonb налету, либо этими уродставми с array_agg/unnest, если вам они нравятся.

А ещё скорее всего с новым MERGE можно написать первый запрос ещё короче и чище.

ИМХО оба запроса читабельны и эффективны. Не слушайте больных на голову SQL-религиозников, которым в голову вбили что нормализация это единственный верный путь, а оценить плюсы других решений у них мозгов не хватает.

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

В каком «таком»? Нормализация всегда жрёт а разы больше места, потому что вместо того чтобы просто хранить элемент массива вы храните заголовок тупла + id + дохлые туплы до прихода вакума + индекс, что для достаточно коротких строк как-то имена, или вообще id’шников, будет где-то на порядок больше. Плюс оверхед на лукапы и изменение этой помойки, вместо просто записи оригинальной строки с массивом.

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

Нормализация всегда жрёт а разы больше места

ога. так это ты место экономишь?

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

оба запроса читабельны и эффективны

бенчи будут?

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

Я был не в курсе, что cte в postgres могут содержать модифицирующие запросы.

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

Это здесь и сейчас postgres

Вот и славно же.

а ну сменят СУБД на другую?

Зачем менять постгрес на что-то?

Какие-то надуманные аргументы.

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

Это здесь и сейчас postgres, а ну сменят СУБД на другую?

Ни разу не приходилось во всех сколько-нибудь крупных проектах внезапно переходить с Postgres на другую СУБД. Вероятность того, что в ближайшем будущем мне предстоит этим же заниматься, крайне мала.

Или место работы сменит или еще что

…и что?

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

Я вижу, что ТС городит херню с массивами вместо нормализации таблицы. Это плохо.

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

У ж мне то рассказывать не стоит. С таким говном работал и не раз. Но тут не видно всей картины и не было сказано об ограничениях такого рода.

Noob_Linux ★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.