Параллельный unnest() и порядок сортировки в PostgreSQL

Я понимаю, что с помощью

SELECT unnest(ARRAY[5,3,9]) as id

без ORDER BY предложение, порядок результирующего набора не гарантируется. Я мог бы, например, получить:

id
--
3
5
9

но как насчет следующего запроса:

SELECT
  unnest(ARRAY[5,3,9]) as id,
  unnest(ARRAY(select generate_series(1, array_length(ARRAY[5,3,9], 1)))) as idx
ORDER BY idx ASC

гарантируется ли, что 2 unnest() вызовы (которые имеют одинаковую длину) будут разворачиваться параллельно и что индекс idx действительно будет соответствовать положению элемента в массиве?

Я использую PostgreSQL 9.3.3.

2 ответов


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

порядок результирующих строк не гарантируется. На самом деле, с таким простым утверждением as:

SELECT unnest(ARRAY[5,3,9]) AS id

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

если второй запрос в вашем вопросе-это то, что вы на самом деле хотите (добавьте номер индекса к элементам unnested array), есть лучший способ с generate_subscripts ():

SELECT unnest(ARRAY[5,3,9]) AS id
     , generate_subscripts(ARRAY[5,3,9], 1) AS idx
ORDER  BY idx;

подробности в этом соответствующем ответе:

вам будет интересно WITH ORDINALITY в Postgres 9.4:

затем вы можете использовать:

SELECT * FROM unnest(ARRAY[5,3,9]) WITH ORDINALITY tbl(id, idx);

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

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

CREATE FUNCTION unnest_random(anyarray)  RETURNS setof anyelement
language sql as
$$ select unnest() order by random() $$;

затем проверьте несколько исполнений вашего запроса с помощью unnest заменить unnest_random:

SELECT
  unnest_random(ARRAY[5,3,9]) as id,
  unnest_random(ARRAY(select generate_series(1, array_length(ARRAY[5,3,9], 1)))) as idx
ORDER BY idx ASC

пример вывода:

 id | idx 
----+-----
  3 |   1
  9 |   2
  5 |   3

id=3 связан с idx=1 но 3 в 2-й позиции в массиве. Это все неправильно.

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

об этом вопросе:

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

на select unnest(...) X1, unnest(...) X2 С X1 и X2 тип SETOF something и имея такое же количество строк,X1 и X2 будет спарен в окончательном выходе так, что X1 значение на row N ждет X2 значение в той же строке N. (это своего рода объединение для столбцов, в отличие от декартового произведения).

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

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

альтернатива: In этот нить из списка рассылки pgsql-sql предлагается следующая функция:

CREATE OR REPLACE FUNCTION unnest_with_ordinality(anyarray, OUT value
anyelement, OUT ordinality integer)
  RETURNS SETOF record AS
$$
SELECT [i], i FROM
    generate_series(array_lower(,1),
                    array_upper(,1)) i;
$$
LANGUAGE sql IMMUTABLE; 

основываясь на этом, мы можем заказать второй выходной столбец:

select * from unnest_with_ordinality(array[5,3,9]) order by 2;
 value | ordinality 
-------+------------
     5 |          1
     3 |          2
     9 |          3

С postgres 9.4 и выше: элемент WITH ORDINALITY предложение, которое может следовать за вызовами возвращающей функции SET, обеспечит эту функциональность общим способом.