11
votes

Trouvez la ligne associée à un min / max, sans boucle intérieure

J'ai une question relative à T-SQL et SQL Server.

Disons que j'ai des commandes de table avec 2 colonnes:

  • Producteur Int
  • CustomerID Int
  • Date DateTime

    Je veux la date du premier ordre pour chaque produit, donc j'effectue ce type de requête: xxx

    J'ai un index sur ProductID , y compris les colonnes CustomerID et date pour accélérer la requête ( ix_orders ). Le plan de requête ressemble à une analyse d'index non clustere sur ix_orders , suivie d'un agrégat de flux (sans trier grâce à l'index).

    Maintenant, mon problème est que je veux aussi Pour récupérer le CustomerID associé à la première commande pour chaque produit (le produit 26 a été commandé pour la première fois le mardi 25, par le client 12). La partie délicate est que je ne veux pas de boucle intérieure dans le plan d'exécution, car cela signifierait une lecture supplémentaire par ProductID dans le tableau, qui est hautement inefficace.

    Cela devrait simplement être possible en utilisant le même balayage d'index non clustered, suivi des agrégats de flux, mais je ne peux pas sembler trouver une requête qui le ferait. Aucune idée?

    merci


0 commentaires

6 Réponses :


0
votes
SELECT
    o1.productid, 
    o1.date, 
    o1.customerid
FROM
    Orders o1
JOIN
    (select productid, min(date) as orderDate
     from Orders
     group by productid
    ) firstOrder
ON o1.productid = firstOrder.productid
This is the best I can come up with though to be honest, I don't know what the performance characteristics of this query are. If it's no good, I'd probably suggest running two queries to get the information you want.

3 commentaires

+1: Je pense que vous devez définir un alias pour min (date) à l'intérieur de votre jointure anonyme; Sinon, c'est exactement ce que j'ai eu. Serait bon de savoir s'il y a une meilleure approche à ce sujet.


Cette requête obtient la mauvaise réponse car elle n'inclut pas la date dans la jointure entre O1 et FirstOrder


Vous n'avez pas besoin d'inclure la date dans la jointure. Vous n'avez besoin que de l'ID de produit de la sous-requête, car cela a la date minimale associée à celle-ci. Le retour de la sélection des résultats réels renvoie la date.



2
votes
declare @Orders table (
    ProductId int,
    CustomerId int,
    Date datetime
)

insert into @Orders values (1,1,'20090701')
insert into @Orders values (2,1,'20090703')
insert into @Orders values (3,1,'20090702')
insert into @Orders values (1,2,'20090704')
insert into @Orders values (4,2,'20090701')
insert into @Orders values (1,3,'20090706')
insert into @Orders values (2,3,'20090704')
insert into @Orders values (4,3,'20090702')
insert into @Orders values (5,5,'20090703')

select O.* from @Orders O inner join 
(
    select ProductId,
    MIN(Date) MinDate 
    from @Orders 
    group by ProductId
) FO
on FO.ProductId = O.ProductId and FO.MinDate = O.Date
The estimated query plan for this is useless as I'm mocking it with table variables, but the anonymous inner join should be optimised over a subselect.

7 commentaires

Votre sélection doit inclure FO.Minder.


Je n'ai jamais entendu parler d'une jointure "anonyme", j'ai toujours utilisé la table dérivée du terme.


Cela ne fonctionnera pas s'il y a plusieurs rangées avec la même date minimale du même produit. Essayez-le, ajoutez ce code à l'exemple: Insérer dans les valeurs @orders (5,1, '20090703'); Insérer dans les valeurs @orders (5,5, '20090703') Vous obtiendrez un produit 5 fois plusieurs fois dans le jeu de résultats.


Mais n'est-ce pas valide? Vous souhaitez que l'ID client du premier produit pour tout produit, mais compte tenu des données, si plusieurs clients commandent le même produit le même jour, je dirais que vous devriez les récupérer. Si un client commande le même produit deux fois, cela ne devrait peut-être pas arriver - vous pourriez faire le O. * distinct dans ce cas.


OP dit "Je veux la date du premier ordre pour chaque produit", c'est-à-dire que signifie seulement énumérer le produit une fois. L'OP souhaite également le client qui l'a commandé. Si l'OP utilise des colonnes DateTime avec des temps réelles (pas seulement des jours tels que dans vos données de test), vous pouvez probablement obtenir le premier, car deux commandes simultanées en même temps sont une faible probabilité, mais peut encore être possible. Je penserais que s'il y a une cravate, le même produit commandé en même temps, OP ne veut toujours que une rangée de la série de résultats, mais c'est ma prise.


Je pense que le point est que l'OP veut plus que la date du premier ordre pour chaque produit: "Je souhaite également récupérer le CustomerID associé à la première commande pour chaque produit". Dans ce cas, l'OP doit déterminer comment ils choisiront une seule réponse lorsque deux sont également valables; Cela pourrait être aussi simple que celui avec la clientèle la plus basse.


Seul l'OP peut répondre à ce qu'ils sont après. Cependant, ils n'ont pas dit qu'ils voulaient la première date pour chaque client. Dans ma solution, je ne renvoie qu'une ligne par produit et je parsemé des cravates en utilisant la clientèle la plus basse.



0
votes

est ix_orders triés par le producteur, puis cutomerid, puis date ou est-ce le produit, puis la date, puis la clientèle? Si c'est le premier changement à ce dernier.

En d'autres termes, n'utilisez pas ceci: p> xxx pré>

Utilisez cela à la place: p>

WITH cte AS
(
    SELECT ProductID, CustomerID, Date, 
        ROW_NUMBER() OVER(PARTITION BY ProductID ORDER BY Date ASC) AS row
    FROM [Order]
)
SELECT ProductID, CustomerId, Date
FROM cte
WHERE row = 1
ORDER BY ProductID


0 commentaires

3
votes

Ceci gérera des produits qui ont des dates en double:

;WITH MinOrders AS
(SELECT
     o.ProductId, o.CustomerId, o.Date
         ,row_number() over(partition by o.ProductId order by o.ProductId,o.CustomerId) AS RankValue
     FROM @Orders o
 )
SELECT
    m.ProductId, m.CustomerId, m.Date
    FROM MinOrders  m
    WHERE m.RankValue=1
    ORDER BY m.ProductId, m.CustomerId


1 commentaires

Bien, il n'y a qu'un seul scan d'index, mais cela donne une sorte dans le plan d'exécution de la requête cependant.



0
votes

Je ne vois pas une façon de le faire bien sans faire une sous-requête ou une fonction de fenêtres (telle que Row_Number, grade), car le maximum ne regarde que dans une colonne.

Cependant, vous pouvez le faire non bien. P>

SELECT
    productid, 
    min(date), 
cast(
    substring( 
        min(convert(varchar(23),date,21) + cast(customerid as varchar(20)))
              , 24, 44)
    as int) customerid
from 
    orders
group by
    productid 


2 commentaires

MSG 8120, niveau 16, état 1, ligne 51 colonne '@ ordres.productid' n'est pas valide dans la liste Select, car il n'est pas contenu dans une fonction globale ou le groupe par clause.


Oups, j'ai oublié d'ajouter le groupe par clause



1
votes

in SQL Server 2005 + code>: xxx pré>

nominalement, le plan de la requête contient boucles imbriquées code>. P> Cependant, la boucle externe utilisera une analyse index code> avec agrégate de flux code> et la boucle interne contiendra un Index recherche code> pour le ProductID code> avec un haut code>. P>

En fait, la deuxième opération est presque gratuite, car la page d'index utilisée dans la boucle interne résidera probablement dans le cache Parce qu'il venait d'être utilisé pour la boucle extérieure. p>

Voici le résultat du test sur 1 000 000 code> lignes (avec 100 code> distinct code> ProductID CODE> 'S): P>

SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 1 ms.

(строк обработано: 100)
Table 'Orders'. Scan count 1, logical reads 5123, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:
   CPU time = 406 ms,  elapsed time = 415 ms.


5 commentaires

Besoin de changer d'où: où Oi.productid = OD.Productid. Mon plan de requête a montré une boucle de tri et imbriquée.


Je reçois msg 4104, niveau 16, état 1, ligne 2 L'identifiant multi-pièces "oo.productid" n'a pas pu être lié. Pour résoudre le changement lorsque le titre de @shannon suggère.


Droite, j'ai oublié de le réparer. @shannon : Avez-vous créé l'index en tant que @op dit? Créer index ix_orders_pdc sur les commandes (productide, date, clientId)


Mon indice était (Producteur, Date, CustomerID) Je vérifierai avec (productid, date, CustomerID)


@Quassnoi: déplacer les colonnes de l'index, j'ai perdu le tri. Toujours la boucle avec une recherche, comme vous soulignez votre réponse.