Эффективное объединение (удаление дубликатов) массивов

10

У меня есть две таблицы, left2и right2. Обе таблицы будут большими (1-10 миллионов строк).

CREATE TABLE left2(id INTEGER, t1 INTEGER, d INTEGER);
ALTER TABLE left2 ADD PRIMARY KEY (id,t1);

CREATE TABLE right2( t1 INTEGER, d INTEGER, arr INTEGER[] );
ALTER TABLE right2 ADD PRIMARY KEY(t1,d);

Я выполню этот тип запроса:

SELECT l.d + r.d,
       UNIQ(SORT((array_agg_mult(r.arr)))
FROM left2 l,
     right2 r
WHERE l.t1 = r.t1
GROUP BY l.d + r.d
ORDER BY l.d + r.d;

Где для агрегации массивов я использую функцию:

CREATE AGGREGATE array_agg_mult(anyarray) (
SFUNC=array_cat,
STYPE=anyarray,
INITCOND='{}');

После объединения массивов я использую UNIQфункцию intarrayмодуля. Есть ли более эффективный способ сделать это? Есть ли на arrполе какой-либо индекс для ускорения слияния (с удалением дубликатов)? Может ли агрегатная функция удалять дубликаты напрямую? Оригинальные массивы можно считать отсортированными (и они уникальны), если это помогает.

Скрипка SQL здесь :

Александрос
источник
Собираетесь ли вы запрашивать миллионы строк одновременно? Что вы делаете с результатом? Или будут предикаты, чтобы выбрать несколько? Может right2.arr быть NULL, как предполагает ваша демонстрационная схема? Вам нужны отсортированные массивы в результате?
Эрвин Брандштеттер

Ответы:

9

Правильные результаты?

Прежде всего: правильность. Вы хотите создать массив уникальных элементов? Ваш текущий запрос не делает этого. Функция uniq()из модуля intarray обещает только:

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

Как указано в руководстве , вам потребуется:

SELECT l.d + r.d, uniq(sort(array_agg_mult(r.arr)))
FROM   ...

Также дает вам отсортированные массивы - предполагая, что вы хотите, вы не уточнили.

Я вижу , у вас есть sort() в вашей скрипке , так что это может быть просто опечатка в вашем вопросе.

Postgres 9,5

В любом случае, вам понравится новый Postgres 9.5 (в настоящее время бета). Это обеспечивает возможности array_agg_mult()из коробки и намного быстрее:

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

запрос

Основная цель array_agg_mult()состоит в агрегировании многомерных массивов, но в любом случае вы производите только одномерные массивы. Так что я бы хотя бы попробовал этот альтернативный запрос:

SELECT l.d + r.d AS d_sum, array_agg(DISTINCT elem) AS result_arr
FROM   left2  l
JOIN   right2 r USING (t1)
     , unnest(r.arr) elem
GROUP  BY 1
ORDER  BY 1;

Который также отвечает на ваш вопрос:

Может ли агрегатная функция удалять дубликаты напрямую?

Да, может, с DISTINCT. Но это не быстрее, чем uniq()для целочисленных массивов, которые были оптимизированы для целочисленных массивов, и в то же время DISTINCTявляются общими для всех подходящих типов данных.

Не требует intarrayмодуля. Однако результат не обязательно отсортирован. Postgres использует различные алгоритмы для DISTINCT(IIRC), большие наборы обычно хэшируются, тогда результат не сортируется, если вы не добавите явное ORDER BY. Если вам нужны отсортированные массивы, вы можете добавить ORDER BYк агрегатной функции напрямую:

array_agg(DISTINCT elem ORDER BY elem)

Но это обычно медленнее, чем подача предварительно отсортированных данных array_agg()(одна большая сортировка против множества мелких сортировок). Поэтому я бы отсортировал подзапрос, а затем агрегировал:

SELECT d_sum, uniq(array_agg(elem)) AS result_arr
FROM  (
   SELECT l.d + r.d AS d_sum, elem
   FROM   left2  l
   JOIN   right2 r USING (t1)
        , unnest(r.arr) elem
   ORDER  BY 1, 2
   ) sub
GROUP  BY 1
ORDER  BY 1;

Это был самый быстрый вариант в моем беглом тесте на Postgres 9.4.

SQL Fiddle на основе того, который вы предоставили.

Индекс

Я не вижу большого потенциала для какого-либо индекса здесь. Единственный вариант будет:

CREATE INDEX ON right2 (t1, arr);

Это имеет смысл только в том случае, если вы получаете из этого только сканирование по индексу - что произойдет, если базовая таблица right2будет значительно шире, чем только эти два столбца, и ваша установка будет соответствовать сканированию только по индексу. Подробности в Postgres Wiki.

Эрвин Брандштеттер
источник
Спасибо +1. В любом случае мне придется позже UNNEST, но я хочу проверить, удаляются ли дубликаты в массивах, а затем UNNEST быстрее.
Александрос
0

Я очень разочарован, это легко сделать в Microsoft Access. Вы можете создать запрос «удалить дубликаты», а затем посмотреть на SQL, чтобы увидеть, как он это делает. Мне придется запустить машину Windows, чтобы посмотреть. Они различаются, мастер запросов делает это.

Я думаю, что работает одна вещь - загрузить все ваши данные в одну таблицу, а затем выполнить SELECT DISTINCT в новой таблице. Вы также можете придерживаться порядка по пункту, пока вы на нем. Я сделал это как-то год назад, это должно быть.

Я объединяю данные о температуре за 2 года, датчик отправляет 2 копии одной и той же точки данных каждую минуту в качестве избыточной защиты. Иногда его разбивают, но я хочу оставить его. У меня также есть совпадения между файлами.

Если данные в течение всего цикла имеют одинаковый формат, на Unix-машине вы можете сделать что-то вроде

cat *.tab > points.txt
sort -n < points.txt > sorted.txt
uniq -u sorted.txt unique.txt

Но uniq сравнивает строки как строки и, например, 18.7000 отличается от 18.7. Я изменил свое программное обеспечение в течение 2 лет, поэтому у меня есть оба формата.

Алан Кори
источник
Разочарован от Postgres? У Access даже есть массивы?
ypercubeᵀᴹ
Я не знаю, но он может удалить дубликаты, это достаточно распространенная проблема при очистке данных. Выбрать отличное достаточно близко. Вы не всегда можете контролировать свои необработанные данные из реального мира.
Алан Кори