web-dev-qa-db-fra.com

Obtenez plusieurs colonnes d'une sous-requête sélectionnée

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)

Comme vous pouvez le voir, je répète la même sous-requête juste pour extraire une autre colonne. Je me demande s'il existe une meilleure façon de procéder?

id est la clé primaire dans les deux tables. Je n'ai aucun problème à rendre product_special.priority unique si cela peut aider.

26
Sparctus

En supposant une combinaison product_special.id, product_special.priority est unique

 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)
11
a1ex07

sauf si vous avez l'intention de renvoyer les champs en tant que special_price.price et date.date, pourquoi ne pas alias les noms à l'intérieur de la sous-requête? par exemple.

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

Votre langage de requête a-t-il une fonction d'agrégation FIRST ()? Je ne sais pas si vous pouvez faire du PK de product_special un composite entre id et priorité (les deux sont de type ASC) et changer la clause ORDER en GROUP BY id, psi.priority

vous POUVEZ être en mesure de supprimer la clause ORDER BY entièrement et d'utiliser HAVING MIN(psi.priority)

5
mpag

Notez que le mécanisme "cross-apply" de SQL Server résoudrait cela, mais il n'est pas disponible dans PostgreSQL. Fondamentalement, c'était leur solution pour savoir comment passer des paramètres (qui ont tendance à être des références à des colonnes externes à l'expression de table actuelle) aux fonctions appelées expressions de table dans la clause FROM. Mais il s'est avéré utile pour toutes sortes de situations où vous souhaitez éviter un autre niveau d'imbrication de sous-requête ou de déplacement de choses de la clause FROM vers la clause SELECT. PostgreSQL a rendu cela possible en faisant une sorte d'exception - vous pouvez passer des paramètres comme ça si l'expression est un simple appel de fonction mais pas à proprement parler un SELECT intégré. Donc

left join highestPriorityProductSpecial(p.id) on true

est ok, mais pas

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

même si la définition de la fonction est précisément cela.

Donc, c'est en fait une solution pratique (en 9.1 au moins): créez une fonction pour extraire votre ligne de priorité la plus élevée en faisant la limite à l'intérieur de la fonction.

Mais les fonctions ont l'inconvénient que le plan de requête ne montrera pas ce qui se passe à l'intérieur et je pense qu'il choisira toujours une jointure en boucle imbriquée, même lorsque ce n'est pas le meilleur.

2
Paul Vaughan

Inspiré par la réponse de dezso https://dba.stackexchange.com/a/222471/1274 Je résous le problème dans PostgreSQL en utilisant des tableaux, comme ceci:

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)

Certes, ce n'est encore qu'une colonne, mais dans mon code, je peux facilement accéder aux deux valeurs. J'espère que cela fonctionne aussi pour vous.

2
tobi42

Essayez la commande SQL suivante:

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
2
SANTOSH APPANA

Je veux juste mettre cela ici en dernier recours, pour tous ceux qui utilisent un moteur de base de données qui ne prend pas en charge une ou plusieurs des autres réponses ...

Vous pouvez utiliser quelque chose comme:

SELECT (col1 || col2) as col3 

(Avec séparateur ou formatage col1 et col2 à une longueur spécifique.) Et dessinez ensuite vos données à l'aide de sous-chaînes.

J'espère que quelqu'un le trouvera utile.

2
H.A.H.

Dans DB2 pour z/OS, utilisez les fonctions pack et unpack pour renvoyer plusieurs colonnes dans une sous-sélection.

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