Obtenga múltiples columnas de una subconsulta seleccionada

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 puede ver, estoy repitiendo la misma subconsulta solo para obtener otra columna. Me pregunto si hay una mejor manera de hacer esto.

id es la clave principal en ambas tablas. No tengo ningún problema para que product_special.priority sea único si eso puede ayudar.

Sparctus
fuente

Respuestas:

11

Asumiendo que la combinación product_special.id, product_special.priorityes ú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
fuente
5

a menos que tenga la intención de devolver los campos como special_price.price y date.date ¿por qué no alias los nombres dentro de la subconsulta? p.ej

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

¿Su lenguaje de consulta tiene una función agregada FIRST ()? No estoy seguro de si podría hacer que la PK de product_special sea un compuesto entre id y prioridad (ambos ASC) y cambiar la cláusula ORDER aGROUP BY id, psi.priority

PUEDE poder eliminar la cláusula ORDER BY por completo y usar HAVING MIN(psi.priority)

mpag
fuente
2

Tenga en cuenta que el mecanismo de "aplicación cruzada" de SQL Server resolvería esto, pero no está disponible en PostgreSQL. Básicamente, fue su solución sobre cómo pasar parámetros (que tienden a ser referencias a columnas externas a la expresión de tabla actual) a funciones llamadas como expresiones de tabla en la cláusula FROM. Pero resultó ser útil para todo tipo de situaciones en las que desea evitar otro nivel de anidamiento de subconsultas o mover cosas de la cláusula FROM a la cláusula SELECT. PostgreSQL hizo posible hacer esto haciendo una especie de excepción: puede pasar parámetros como ese si la expresión es una simple llamada de función pero no hablando estrictamente un SELECT incrustado. Asi que

left join highestPriorityProductSpecial(p.id) on true

está bien, pero no

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

a pesar de que la definición de la función es precisamente eso.

Entonces, esa es de hecho una solución práctica (al menos en 9.1): haga una función para extraer su fila de mayor prioridad haciendo el límite dentro de la función.

Pero las funciones tienen el inconveniente de que el plan de consulta no mostrará lo que está sucediendo dentro de ellas y creo que siempre elegirá una unión de bucle anidado, incluso cuando eso no sea lo mejor.

Paul Vaughan
fuente
66
cross apply está disponible en Postgres a partir de 9.3 (lanzado en 2013) pero eligieron adherirse al estándar SQL y usar el lateraloperador estándar . En su segunda consulta, reemplace left joinconleft join lateral
a_horse_with_no_name el
2

Pruebe el siguiente 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
APPANA SANTOSH
fuente
1
¿podría agregar más información a su respuesta?
Ahmad Abuhasna
El código en cuestión usa LIMITy no está etiquetado con un DBMS (por lo que podría ser MySQL o Postgres o SQLite o posiblemente algún otro dbms). El código en la respuesta usa OUTER APPLYy, TOPpor lo tanto, funcionará solo en SQL Server (y Sybase) que no tiene LIMIT.
ypercubeᵀᴹ
Este es aplicable para el servidor sql solo para otras bases de datos que podemos usar en la consulta interna dentro de la instrucción select.
SANTOSH APPANA
En Postgres no existe OUTER APPLY, pero existe LATERAL , que debería ser equivalente. Un ejemplo de uso: stackoverflow.com/a/47926042/4850646
Lucas Basquerotto
2

Inspirado por la respuesta de dezso /dba//a/222471/127433 Estoy resolviendo el problema en PostgreSQL usando matrices, así:

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)

Es cierto que todavía es solo una columna, pero en mi código, puedo acceder fácilmente a los dos valores. Espero que funcione para ti también.

tobi42
fuente
1

Solo quiero poner esto aquí como último recurso, para todos los que usan un motor de base de datos que no admite una o más de las otras respuestas ...

Puedes usar algo como:

SELECT (col1 || col2) as col3 

(Con separador, o formateando col1 y col2 a una longitud específica). Y luego dibuje sus datos usando subcadenas.

Espero que alguien lo encuentre útil.

HAH
fuente
0

En DB2 para z / OS, use packy unpackfunciones para devolver múltiples columnas en una subselección.

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
fuente