2015-09-04 4 views
1

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

Установка для минимального теста:

CREATE TABLE test (num int); 
INSERT INTO test (num) values (1); 
INSERT INTO test (num) values (2); 

Моя попытка запроса, который должен вернуть две строки, "1" и "2":

SELECT * FROM test WHERE num = ANY(SELECT array_agg(num) from test); 

Конечно ... ERROR: operator does not exist: integer = integer[]. Подзапрос возвращает одну строку, содержащую массив, и мне нужно просто получить массив.

пытался Кроме того, это без успеха:

SELECT * FROM test WHERE num = ANY((SELECT array_agg(num) from test)[0]); 

я ERROR: op ANY/ALL (array) requires array on right side. Это не имеет смысла для меня, но я не эксперт.

Что я должен здесь делать?

ответ

4

Первая ошибка связана с тем, что PostgreSQL видит

num = ANY(SELECT array_agg(num) from test) 

как форма

expression operator ANY (subquery) 

вместо того, вы хотите:

expression operator ANY (array expression) 

Как таковой, он пытается сравнить num с любым ROW возвращено по подзапросу (который в вашем примере возвращает одну строку ARRAY[1,2]), вместо каждого элемента массива (следовательно, вы получаете operator does not exist: integer = integer[]). См. the documentation для более подробной информации.


Вторые ошибки просто исходит из того, что [0] получает доступ к элементу integer[], и как таковой возвращает integer. Поскольку правый операнд должен быть массивом (или подзапросом, но PostgreSQL его не видит), PostgreSQL возвращает ERROR: op ANY/ALL (array) requires array on right side.
(В качестве альтернативы, массивы 1-based основаны на PostgreSQL, а не на основе 0).


Если вы уверены, что ваша функция всегда будет возвращать единственный массив, то вы можете просто заставить Postgre видеть SELECT array_agg(num) from test как integer[]:

SELECT * FROM test 
WHERE num = ANY((SELECT array_agg(num) from test)::integer[]); 
┌─────┐ 
│ num │ 
├─────┤ 
│ 1 │ 
│ 2 │ 
└─────┘ 
(2 rows) 

Заметим, однако, что если ваша функция возвращает несколько массивов, вы получите сообщение об ошибке (поскольку setof integer[] не может рассматриваться как integer[]):

SELECT * FROM test 
WHERE num = ANY((SELECT array_agg(num) from test GROUP BY num)::integer[]); 
ERROR: 21000: more than one row returned by a subquery used as an expression 
LOCATION: ExecSetParamPlan, nodeSubplan.c:970 

Другим решением является использование функции unnest, чтобы превратить ваш массив в набор целочисленных, используя expression operator ANY (subquery) форму.Это будет работать, даже если ваша функция возвращает несколько массивов, хотя она немного медленнее предыдущего запроса.

SELECT * 
FROM test 
WHERE num = ANY(
    SELECT unnest(sub.array_agg) 
    FROM (
    SELECT array_agg(num) FROM test GROUP BY num -- GROUP BY num to show off the multiple array scenario 
) AS sub 
); 
┌─────┐ 
│ num │ 
├─────┤ 
│ 1 │ 
│ 2 │ 
└─────┘ 
(2 rows) 
+1

Также будет работать простой 'where num = any (array (select num from test))'. –

+0

Спасибо, это отличный ответ! Я сделал это с типом, потому что я уверен, что он возвращает только один массив, и теперь он работает. – sudo

1

@Marth already explained детали и некоторые приложения для ANY конструкции. Тесно связанные с:

Существует более простой вариант: использовать подзапрос в качестве производной таблицы в предложении FROM и присоединиться к нему:

SELECT t.* 
FROM test t 
JOIN (SELECT array_agg(num) arr FROM test) a ON t.num = ANY(a.arr); 

Это даже работает если в подзапросе должно быть указано более одной строки (с каждым массивом). Вы получаете в каждой строке от test, которая соответствует любому элементу массива, но вы получаете только каждую строку один раз. Для массива '{1,1,2}' вы получите то же самое строк, что и для '{1,2}'.

Илиunnest() и присоединиться к безгнездным элементам без ANY. Удобно использовать одно и то же имя столбца и присоединиться к предложению USING, чтобы объединить объединенный столбец. Тогда вы можете просто SELECT *, чтобы получить строки просто test:

SELECT * 
FROM test t 
JOIN unnest((SELECT array_agg(num) arr FROM test)) num USING (num); 

Здесь вы получите один ряд для каждого элемента массива, так что вы получите три строки результата для «{1,1,2} ».

SQL Fiddle.

(SELECT array_agg(num) arr FROM test) просто манекен подзапрос для доказательства концепции. Но это нужно упомянуть хотя бы один раз: если это вообще возможно, не агрегируйте в первую очередь, тогда вам не придется больше беспокоиться. Например:

SELECT * 
FROM test t 
JOIN (SELECT num FROM test) t1 USING (num); 
Смежные вопросы