Получить несколько столбцов из выбранного подзапроса


24
SELECT 
   *, 
   p.name AS name, 
   p.image, 
   p.price,
   ( 
       SELECT ps.price 
       FROM product_special ps 
       WHERE p.id = ps.id
         AND ps.date < NOW() 
       ORDER BY ps.priority ASC, LIMIT 1
   ) AS special_price,
   ( 
       SELECT ps.date 
       FROM product_special ps 
       WHERE p.id = ps.id
         AND ps.date < NOW() 
       ORDER BY ps.priority ASC, LIMIT 1
   ) AS date
FROM product p LEFT JOIN product_special ps ON (p.id = ps.id)

Как вы можете видеть, я повторяю тот же подзапрос, чтобы вывести еще один столбец. Мне интересно, есть ли лучший способ сделать это?

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

Ответы:


11

Предполагая, что комбинация product_special.id, product_special.priorityуникальна

 SELECT p.*, special_price,special_date
 FROM product p
 LEFT JOIN 
 (
     SELECT ps.id, ps.price as special_price, ps.`date` as special_date
     FROM product_special ps
     INNER JOIN 
     (
       SELECT id, MIN(priority) as min_priority 
       FROM product_special
       GROUP BY id
     ) ps2 
     ON (ps2.id = ps.id)
 )a ON (a.id=p.id)

5

если вы не собираетесь возвращать поля как special_price.price и date.date, почему бы не использовать псевдонимы имен внутри подзапроса? например

SELECT p.*, p.name AS  name, p.image, p.price, ps.*
FROM product p
LEFT JOIN
   (SELECT
      psi.price as special_price, psi.date as my_date 
    FROM product_special psi
    WHERE 
      p.id = psi.id AND
      psi.date < NOW()
    ORDER BY psi.priority ASC, LIMIT 1
   ) AS ps ON
  p.id = ps.id

Есть ли в языке запросов агрегатная функция FIRST ()? Не уверен, что вы могли бы сделать PK product_special составным между id и приоритетом (оба типа ASC) и изменить предложение ORDER наGROUP BY id, psi.priority

вы МОЖЕТЕ иметь возможность полностью удалить предложение ORDER BY и использовать HAVING MIN(psi.priority)


2

Обратите внимание, что механизм «перекрестного применения» из SQL Server решит эту проблему, но он недоступен в PostgreSQL. По сути, это было их решение для передачи параметров (которые, как правило, являются ссылками на столбцы, внешние по отношению к текущему табличному выражению) в функции, называемые табличными выражениями в предложении FROM. Но это оказалось полезным для всех видов ситуаций, когда вы хотите избежать другого уровня вложенности подзапроса или перемещения элементов из предложения FROM в предложение SELECT. PostgreSQL позволил сделать это, сделав своего рода исключение - вы можете передавать такие параметры, если выражение является простым вызовом функции, но строго говоря не является встроенным SELECT. Так

left join highestPriorityProductSpecial(p.id) on true

в порядке, но не

left join (select * from product_special ps where ps.id = p.id order by priority desc limit 1) on true

хотя определение функции именно так.

Итак, на самом деле это удобное решение (по крайней мере, в 9.1): создайте функцию для извлечения строки с наивысшим приоритетом, выполнив ограничение внутри функции.

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


6
cross apply это доступно в Postgres , начиная с 9.3 (выпущена в 2013 году) , но они решили придерживаться стандарта SQL и использовать стандартный lateralоператор. Во втором запросе замените left joinнаleft join lateral
a_horse_with_no_name

2

Попробуйте следующую команду SQL:

SELECT p.name,p.image,p.price,pss.price,pss.date
FROM Product p OUTER APPLY(SELECT TOP(1)* 
FROM ProductSpecial ps
WHERE p.Id = ps.Id ORDER BY ps.priority )as pss

1
не могли бы вы добавить больше информации к своему ответу
Ахмад Абухасна

Рассматриваемый код использует LIMITи не помечен СУБД (так что это может быть MySQL или Postgres или SQLite или, возможно, некоторые другие базы данных). Код в ответе использует, OUTER APPLYи TOPпоэтому он будет работать только в SQL Server (и Sybase), которые не имеют LIMIT.
ypercubeᵀᴹ

Это применимо для сервера SQL только для других баз данных, мы можем использовать внутренний запрос в операторе выбора.
САНТОШ АППАНА

В Postgres нет OUTER APPLY, но есть LATERAL , который должен быть эквивалентным. Пример его использования: stackoverflow.com/a/47926042/4850646
Лукас Баскеротто

2

Вдохновленный ответом Дезсо /dba//a/222471/127433 Я решаю проблему в PostgreSQL с помощью массивов, например:

SELECT 
   *, 
   p.name AS name, 
   p.image, 
   p.price,
   ( 
       SELECT ARRAY[ps.price, ps.date]
       FROM product_special ps 
       WHERE p.id = ps.id
         AND ps.date < NOW() 
       ORDER BY ps.priority ASC, LIMIT 1
   ) AS special_price_and_date
FROM product p LEFT JOIN product_special ps ON (p.id = ps.id)

По общему признанию это - только один столбец, но в моем коде я могу легко получить доступ к двум значениям. Надеюсь, это работает и для вас.


1

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

Вы можете использовать что-то вроде:

SELECT (col1 || col2) as col3 

(С разделителем или форматированием col1 и col2 до определенной длины.) А потом нарисуйте ваши данные, используя подстроки.

Я надеюсь, что кто-то найдет это полезным.


0

В DB2 для z / OS используйте packи unpackфункции для возврата нескольких столбцов в подвыбор.

SELECT 
   *, 
   p.name AS name, 
   p.image, 
   p.price,
    unpack((select PACK (CCSID 1028,
               ps.price,
               ps.date)
         FROM product_special ps 
       WHERE p.id = ps.id
         AND ps.date < NOW() 
       ORDER BY ps.priority ASC, LIMIT 1)) .* AS (SPECIAL_PRICE double, DATE date)
FROM product p LEFT JOIN product_special ps ON (p.id = ps.id);
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.