Obter várias colunas de uma subconsulta selecionada

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)

Como você pode ver, estou repetindo a mesma subconsulta apenas para obter outra coluna. Gostaria de saber se existe uma maneira melhor de fazer isso?

id é a chave primária nas duas tabelas. Não tenho problema em tornar o product_special.priority exclusivo, se isso puder ajudar.

Sparctus
fonte

Respostas:

11

Assumindo que a combinação product_special.id, product_special.priorityé única

 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)
a1ex07
fonte
5

a menos que você pretenda retornar os campos como special_price.price e date.date, por que não alias os nomes dentro da subconsulta? por exemplo

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

Sua linguagem de consulta possui uma função agregada FIRST ()? Não tenho certeza se você poderia fazer da PK de product_special uma composição entre id e prioridade (ambas da classificação ASC) e alterar a cláusula ORDER paraGROUP BY id, psi.priority

você PODE remover a cláusula ORDER BY completamente e usar HAVING MIN(psi.priority)

mpag
fonte
2

Observe que o mecanismo "aplicação cruzada" do SQL Server resolveria isso, mas não está disponível no PostgreSQL. Basicamente, foi a solução deles para como passar parâmetros (que tendem a ser referências a colunas externas à expressão atual da tabela) para funções chamadas como expressões de tabela na cláusula FROM. Mas acabou sendo útil para todos os tipos de situações em que você deseja evitar outro nível de aninhamento de subconsultas ou mover coisas da cláusula FROM para a cláusula SELECT. O PostgreSQL tornou possível fazer isso abrindo um tipo de exceção - você pode passar parâmetros assim se a expressão for uma simples chamada de função, mas sem falar estritamente de um SELECT incorporado. tão

left join highestPriorityProductSpecial(p.id) on true

está ok, mas não

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

mesmo que a definição da função seja exatamente isso.

Portanto, essa é de fato uma solução útil (pelo menos na versão 9.1): crie uma função para extrair sua linha de maior prioridade, fazendo o limite dentro da função.

Mas as funções têm a desvantagem de que o plano de consulta não mostrará o que está acontecendo dentro delas e acredito que sempre escolherá uma junção de loop aninhada, mesmo quando isso pode não ser o melhor.

Paul Vaughan
fonte
6
cross apply está disponível no Postgres começando com 9.3 (lançado em 2013), mas eles optaram por aderir ao padrão SQL e usar o lateraloperador padrão . Em sua segunda consulta substituir left joincomleft join lateral
a_horse_with_no_name
2

Tente o seguinte comando 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
SANTOSH APPANA
fonte
11
pode fazer o favor de adicionar mais informações para a sua resposta
Ahmad Abuhasna
O código em questão usa LIMITe não está marcado com um DBMS (portanto, pode ser MySQL ou Postgres ou SQLite ou possivelmente outros dbms). O código na resposta usa OUTER APPLYe, TOPportanto, ele funciona apenas no SQL Server (e Sybase) que não possui LIMIT.
usar o seguinte comando
Este é aplicável ao servidor sql apenas para outros bancos de dados que podemos usar consulta interna na instrução select.
SANTOSH APPANA
No Postgres não existe OUTER APPLY, mas existe o LATERAL , que deve ser equivalente. Um exemplo de usá-lo: stackoverflow.com/a/47926042/4850646
Lucas Basquerotto
2

Inspirado pela resposta da dezso /dba//a/222471/127433 Estou resolvendo o problema no PostgreSQL usando matrizes, assim:

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)

É certo que ainda é apenas uma coluna, mas no meu código, eu posso acessar facilmente os dois valores. Espero que funcione para você também.

tobi42
fonte
1

Eu só quero colocar isso aqui como último recurso, para todos que usam o mecanismo de banco de dados que não suporta uma ou mais das outras respostas ...

Você pode usar algo como:

SELECT (col1 || col2) as col3 

(Com separador ou formatando col1 e col2 para um comprimento específico.) E depois desenhe seus dados usando sub-strings.

Espero que alguém ache útil.

HAH
fonte
0

No DB2 para z / OS, use packe unpackfunções para retornar várias colunas em uma subseleção.

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);
Keith C
fonte