Je vais utiliser un exemple concret mais hypothétique.
Chaque ordre a normalement un seul élément de campagne:
Commandes:
OrderGUID OrderNumber
========= ============
{FFB2...} STL-7442-1
{3EC6...} MPT-9931-8A
LineItems:
LineItemGUID Order ID Quantity Description
============ ======== ======== =================================
{098FBE3...} 1 7 prefabulated amulite
{1609B09...} 2 32 spurving bearing
Mais occasionnellement, il y aura un ordre avec deux éléments de ligne:
LineItemID Order ID Quantity Description
========== ======== ======== =================================
{A58A1...} 6,784,329 5 pentametric fan
{0E9BC...} 6,784,329 5 differential girdlespring
Normalement lors de l'affichage des commandes à l'utilisateur:
SELECT Orders.OrderNumber, LineItems.Quantity, LineItems.Description
FROM Orders
INNER JOIN LineItems
ON Orders.OrderID = LineItems.OrderID
Je veux montrer le seul article sur la commande. Mais avec cette commande occasionnelle contenant deux articles (ou plus), les commandes apparaissent soit dupliquées:
OrderNumber Quantity Description
=========== ======== ====================
STL-7442-1 7 prefabulated amulite
MPT-9931-8A 32 spurving bearing
KSG-0619-81 5 panametric fan
KSG-0619-81 5 differential girdlespring
Ce que je veux vraiment, c’est que SQL Server en choisisse un , car ce sera assez bon :
OrderNumber Quantity Description
=========== ======== ====================
STL-7442-1 7 prefabulated amulite
MPT-9931-8A 32 differential girdlespring
KSG-0619-81 5 panametric fan
Si je suis aventureux, je pourrais montrer à l'utilisateur un Ellipsis pour indiquer qu'il y en a plusieurs:
OrderNumber Quantity Description
=========== ======== ====================
STL-7442-1 7 prefabulated amulite
MPT-9931-8A 32 differential girdlespring
KSG-0619-81 5 panametric fan, ...
La question est donc de savoir comment
Ma première tentative naïve consistait uniquement à rejoindre les éléments de campagne "TOP 1":
SELECT Orders.OrderNumber, LineItems.Quantity, LineItems.Description
FROM Orders
INNER JOIN (
SELECT TOP 1 LineItems.Quantity, LineItems.Description
FROM LineItems
WHERE LineItems.OrderID = Orders.OrderID) LineItems2
ON 1=1
Mais cela donne l'erreur:
La colonne ou le préfixe 'Commandes' ne
correspond à un nom de table ou un nom d'alias
utilisé dans la requête.
Probablement parce que la sélection interne ne voit pas la table externe.
SELECT Orders.OrderNumber, LineItems.Quantity, LineItems.Description
FROM Orders
JOIN LineItems
ON LineItems.LineItemGUID =
(
SELECT TOP 1 LineItemGUID
FROM LineItems
WHERE OrderID = Orders.OrderID
)
Dans SQL Server 2005
et les versions ultérieures, vous pouvez simplement remplacer INNER JOIN
par CROSS APPLY
:
SELECT Orders.OrderNumber, LineItems2.Quantity, LineItems2.Description
FROM Orders
CROSS APPLY
(
SELECT TOP 1 LineItems.Quantity, LineItems.Description
FROM LineItems
WHERE LineItems.OrderID = Orders.OrderID
) LineItems2
Veuillez noter que TOP 1
sans ORDER BY
n'est pas déterministe: cette requête vous permettra d'obtenir un élément de campagne par commande, mais vous ne savez pas lequel.
Plusieurs invocations de la requête peuvent vous donner différents éléments de ligne pour le même ordre, même si le sous-jacent n'a pas changé.
Si vous souhaitez un ordre déterministe, vous devez ajouter une clause ORDER BY
à la requête la plus à l'intérieur.
Je sais que cette question a reçu une réponse il y a quelque temps, mais lorsqu'il s'agit de grands ensembles de données, les requêtes imbriquées peuvent être coûteuses. Voici une solution différente dans laquelle la requête imbriquée ne sera exécutée qu'une seule fois, et non pour chaque ligne renvoyée.
SELECT
Orders.OrderNumber,
LineItems.Quantity,
LineItems.Description
FROM
Orders
INNER JOIN (
SELECT
Orders.OrderNumber,
Max(LineItem.LineItemID) AS LineItemID
FROM
Orders INNER JOIN LineItems
ON Orders.OrderNumber = LineItems.OrderNumber
GROUP BY Orders.OrderNumber
) AS Items ON Orders.OrderNumber = Items.OrderNumber
INNER JOIN LineItems
ON Items.LineItemID = LineItems.LineItemID
Vous pourriez faire:
SELECT
Orders.OrderNumber,
LineItems.Quantity,
LineItems.Description
FROM
Orders INNER JOIN LineItems
ON Orders.OrderID = LineItems.OrderID
WHERE
LineItems.LineItemID = (
SELECT MIN(LineItemID)
FROM LineItems
WHERE OrderID = Orders.OrderID
)
Cela nécessite un index (ou clé primaire) sur LineItems.LineItemID
et un index sur LineItems.OrderID
ou il sera lent.
La réponse de @Quassnoi est bonne, dans certains cas (en particulier si la table externe est grande), une requête plus efficace pourrait consister à utiliser des fonctions fenêtrées, comme ceci:
SELECT Orders.OrderNumber, LineItems2.Quantity, LineItems2.Description
FROM Orders
LEFT JOIN
(
SELECT LineItems.Quantity, LineItems.Description, OrderId, ROW_NUMBER()
OVER (PARTITION BY OrderId ORDER BY (SELECT NULL)) AS RowNum
FROM LineItems
) LineItems2 ON LineItems2.OrderId = Orders.OrderID And RowNum = 1
Parfois, vous avez juste besoin de tester laquelle requête donne de meilleures performances.
, Une autre approche utilisant une expression de table commune:
with firstOnly as (
select Orders.OrderNumber, LineItems.Quantity, LineItems.Description, ROW_NUMBER() over (partiton by Orders.OrderID order by Orders.OrderID) lp
FROM Orders
join LineItems on Orders.OrderID = LineItems.OrderID
) select *
from firstOnly
where lp = 1
ou, à la fin, peut-être voudriez-vous afficher toutes les lignes jointes?
version séparée par des virgules ici:
select *
from Orders o
cross apply (
select CAST((select l.Description + ','
from LineItems l
where l.OrderID = s.OrderID
for xml path('')) as nvarchar(max)) l
) lines
Les sous-requêtes corrélées sont des sous-requêtes qui dépendent de la requête externe. C’est comme une boucle for en SQL. La sous-requête sera exécutée une fois pour chaque ligne de la requête externe:
select * from users join widgets on widgets.id = (
select id from widgets
where widgets.user_id = users.id
order by created_at desc
limit 1
)
À partir de SQL Server 2012 et après, je pense que cela fera l'affaire:
SELECT DISTINCT
o.OrderNumber ,
FIRST_VALUE(li.Quantity) OVER ( PARTITION BY o.OrderNumber ORDER BY li.Description ) AS Quantity ,
FIRST_VALUE(li.Description) OVER ( PARTITION BY o.OrderNumber ORDER BY li.Description ) AS Description
FROM Orders AS o
INNER JOIN LineItems AS li ON o.OrderID = li.OrderID
EDIT: Nevermind, Quassnoi a une meilleure réponse.
Pour SQL2K, quelque chose comme ceci:
SELECT
Orders.OrderNumber
, LineItems.Quantity
, LineItems.Description
FROM (
SELECT
Orders.OrderID
, Orders.OrderNumber
, FirstLineItemID = (
SELECT TOP 1 LineItemID
FROM LineItems
WHERE LineItems.OrderID = Orders.OrderID
ORDER BY LineItemID -- or whatever else
)
FROM Orders
) Orders
JOIN LineItems
ON LineItems.OrderID = Orders.OrderID
AND LineItems.LineItemID = Orders.FirstLineItemID
Ma méthode préférée pour exécuter cette requête consiste à utiliser une clause not existe. Je pense que c'est le moyen le plus efficace d'exécuter ce type de requête:
select o.OrderNumber,
li.Quantity,
li.Description
from Orders as o
inner join LineItems as li
on li.OrderID = o.OrderID
where not exists (
select 1
from LineItems as li_later
where li_later.OrderID = o.OrderID
and li_later.LineItemGUID > li.LineItemGUID
)
Mais je n'ai pas testé cette méthode par rapport aux autres méthodes suggérées ici.
Essayé la croix, fonctionne bien, mais prend un peu plus longtemps. Les colonnes de lignes ajustées ont un maximum et un groupe ajouté qui garde la vitesse et laisse tomber l’enregistrement supplémentaire.
Voici la requête ajustée:
SELECT Orders.OrderNumber, max(LineItems.Quantity), max(LineItems.Description)
FROM Orders
INNER JOIN LineItems
ON Orders.OrderID = LineItems.OrderID
Group by Orders.OrderNumber
Je résous un problème similaire en utilisant LEFT JOIN et GROUP BY Orders.OrderNumber. Y a-t-il une raison pour ne pas le faire de cette façon?
SELECT Orders.OrderNumber, LineItems.Quantity, LineItems.Description
FROM Orders
LEFT JOIN LineItems
ON Orders.OrderID = LineItems.OrderID
GROUP BY Orders.OrderNumber
Je vais répondre à votre question avec une réponse dans votre propre question:
Orders LineItems
+-------------+ +---------+----------+---------------+
| OrderNumber | | OrderID | Quantity | Description |
+-------------+ +---------+----------+---------------+
| 22586 | | 22586 | 17 | Trunion |
+-------------+ | 22586 | 3 | Girdle Spring |
+---------+----------+---------------+
Réunir les deux sur OrderNumber donne:
OrderNumber Quantity Description
----------- -------- -------------
22586 17 Trunion
22586 3 Girdle Spring
2 row(s) affected
Où nous voulions renvoyer une seule ligne:
OrderNumber Quantity Description
----------- -------- -------------
22586 17 Trunion
1 row(s) affected
C'est pourquoi j'utilise GROUP BY Orders.OrderNumber qui ne renvoie qu'une ligne par OrderNumber.