В моей базе данных Postgres у меня есть таблица с несколькими столбцами, где один столбец B
имеет следующие значения:
A B
a1 b0, b1, b1
a2 b2, b3
a3 b4, b4, b5, b5
Мой ожидаемый результат должен быть таким:
A B
a1 b0, b1
a2 b2, b3
a3 b4, b5
Есть ли простой UPDATE
в Postgres для этого?
вы не должны хранить значения, разделенные запятыми, в первую очередь. Это действительно плохой дизайн.
Любой такой вопрос потребности для предоставления фактического определения таблицы, показывающего типы данных и ограничения, а также используемую версию Postgres. Кроме того, почему избыточный формат b0, b1, b1
вместо просто 0, 1, 1
? А почему не массив или нормализованный дизайн БД? И: складывать только последовательные дубликаты или все? Сохранить порядок элементов?
Есть два способа добиться этого:
Оператор UPDATE, который распаковывает элементы и объединяет их обратно, удаляя дубликаты:
update bad_design
set b = t.b
from (
select a, string_agg(distinct trim(t.w), ',') b
from bad_design
cross join unnest(string_to_array(b, ',')) as t(w)
group by a
) t
where t.a = bad_design.a;
Другой вариант, если вам нужно делать это очень часто, — создать функцию, удаляющую дубликаты, и использовать ее в ОБНОВЛЕНИИ:
create or replace function cleanup(p_list text)
returns text
as
$$
select string_agg(distinct trim(t.w), ',')
from unnest(string_to_array(p_list, ',')) as t(w);
$$
language SQL;
Затем вы можете использовать это следующим образом:
update bad_design
set b = cleanup(b);
Причина, по которой это так сложно, заключается в том, что вы неправильно нормализовали свою модель данных. С правильно нормализованной моделью вы могли бы просто создать индекс UNIQUE и предотвратить вставку дубликатов в первую очередь.
Предполагая: текущая версия Postgres, тип данных B
— text
, разделитель в вашем списке — ', '
, удалите дубликаты все, сохраните исходный порядок элементов, большинство или все строки имеют дубликаты:
Тогда это должно быть самым простым и быстрым:
UPDATE tbl
SET B = array_to_string( ARRAY(
SELECT elem
FROM unnest(string_to_array(B, ', ')) WITH ORDINALITY x(elem, ord)
GROUP BY 1
ORDER BY min(ord)
), ', ');
Недостаток: строки все обновляются, даже если ничего не меняется. Если это затрагивает более нескольких строк, этого можно избежать...
Если какие-либо предположения не выполняются, может быть другое / лучшее / более быстрое решение.
Связанный:
Конкретный оператор обновления зависит от типа столбца b
, но на самом деле существует только 3 различных способа хранения этих данных: в строке с разделителями, текстовом массиве или json.
Оператор обновления для текстового поля, разделенного запятыми, будет таким:
update mytable
set b = array_to_string(array(select distinct unnest(string_to_array(b, ', '))), ', ');
Если b
является текстовым массивом, то:
update mytable
set b = array(select distinct unnest(b));
Если b
является массивом json, то:
update mytable
set b = array_to_json(array(select distinct value from json_array_elements_text(b)));
Как видите, оператор самый чистый в этом случае является результатом хранения данных в виде текстового массива. Если вам необходимо сохранить массив значений в 1 столбце, сделайте это, используя тип массива.
Тем не менее, я бы также рекомендовал нормализовать ваши данные.
Эти приведенные выше операторы будут обновлять все строки в таблице, что приведет к более высокой стоимости выполнения. Я проиллюстрирую способ уменьшить количество обновлений, используя вариант текстового массива (поскольку для этого требуется самый короткий SQL-запрос):
update mytable
set b = array(select distinct unnest(b))
where array_length(b_array, 1) != (select count(distinct c) from unnest(b) c);
Какой тип колонки
B
. Это текстовое поле, json или массив чего-то? Функция обновления существует, но зависит от типаB