2016-04-18 1 views
2

À titre d'exemple simplifié, je dois sélectionner chaque instance dans laquelle un client avait une adresse de livraison différente de son adresse de livraison précédente. J'ai donc une grande table avec des colonnes telles que:SQL: classer par, puis sélectionner la première ligne avec une valeur distincte pour plusieurs colonnes

purchase_id | cust_id | date | address | description 
----------------------------------------------------------- 
1   | 5  | jan | address1 | desc1 
2   | 6  | jan | address2 | desc2 
3   | 5  | feb | address1 | desc3 
4   | 6  | feb | address2 | desc4 
5   | 5  | mar | address3 | desc5 
6   | 5  | mar | address3 | desc6 
7   | 5  | apr | address1 | desc7 
8   | 6  | may | address4 | desc8 

Notez que les clients peuvent « retourner » vers une adresse précédente en tant que client 5 fait dans la ligne 7.

Ce que je veux choisir (et le plus efficacement possible car il s'agit d'une table assez grande) est la première rangée de chaque «bloc» dans lequel un client avait des commandes ultérieures expédiées à la même adresse. Dans cet exemple, ce sont les lignes 1,2,5,7 et 8. Dans tous les autres, le client a la même adresse que sa précédente commande. Donc, je veux d'abord ORDER BY (cust_id, date), puis SELECT purchase_id, cust_id, min(date), address, description.

Cependant, je rencontre des problèmes parce que SQL nécessite habituellement GROUP BY avant le ORDER BY. Je ne peux donc pas comprendre comment adapter, par ex. l'une des meilleures réponses à this question (ce que j'apprécie). Il est nécessaire (conceptuellement, au moins) de classer par date avant de regrouper ou d'utiliser des fonctions agrégées comme min(), sinon des instances comme la ligne 7 dans ma table d'exemple me manqueraient, où un client a «reculé» à une adresse antérieure.

Notez également que deux clients peuvent partager une adresse, donc je dois regrouper efficacement à la fois par cust_id et address après la commande par date.

J'utilise flocon de neige que je crois a la plupart des mêmes commandes disponibles que les versions de PostgreSQL et SQL Server (bien que je suis assez nouveau pour flocon de neige donc pas complètement sûr.)

+0

Voulez-vous pour revenir achats pour les clients avec plus d'une adresse? –

+0

Peut être 1,2,5,8 au lieu de 1,2,7,8? –

+0

Anthony E: Non, je veux retourner (au moins) 1 ligne pour tous les clients qui ont déjà eu une adresse, et plus de lignes pour les clients qui ont changé d'adresse une ou plusieurs fois. Giorgi Nakeuri: Merci, devrait être 1,2,5,7, et 8. (Les lignes 5 et 7 ont toutes les deux une adresse différente de celle utilisée par le client). – DNB

Répondre

0

Ce serait probablement mieux résolu par une sous-requête pour obtenir le premier achat pour chaque utilisateur, puis en utilisant IN pour filtrer les lignes en fonction de ce résultat.

Pour clarifier, purchase_id est une colonne auto-incrémentée, correct? Dans ce cas, un achat avec un plus purchase_id doit avoir été créé à une date ultérieure, et ce qui suit devrait suffire:

SELECT * 
FROM purchases 
WHERE purchase_id IN (
    SELECT MIN(purchase_id) AS first_purchase_id 
    FROM purchases 
    GROUP BY cust_id 
) 

Si vous souhaitez que le premier achat pour les clients avec plus d'une adresse, ajouter une clause HAVING à votre sous-requête:

SELECT * 
FROM purchases 
WHERE purchase_id IN (
    SELECT MIN(purchase_id) AS first_purchase_id 
    FROM purchases 
    GROUP BY cust_id 
    HAVING COUNT(DISTINCT address) > 1 
) 

Fiddle: http://sqlfiddle.com/#!9/12d75/6

Cependant, si purchase_id est pas une colonne autoincrement, puis sur les deux SELECTcust_id et min(date) sur votre sous-requête et utiliser un INNER JOIN sur cust_id et min(date):

SELECT * 
FROM purchases 
INNER JOIN (
    SELECT cust_id, MIN(date) AS min_date 
    FROM purchases 
    GROUP BY cust_id 
    HAVING COUNT(DISTINCT address) > 1 
) cust_purchase_date 
ON purchases.cust_id = cust_purchase_date.cust_id AND purchases.date = cust_purchase_date.min_date 

Cependant, le premier exemple de requête sera probablement plus rapide, donc utiliser que si vous purchase_id est une colonne autoincrement.

+0

Merci Anthony mais cela ne retourne pas chaque nouvelle adresse pour chaque client. Je ne veux pas seulement le premier achat de chaque utilisateur; Je veux * chaque * premier achat qui a une adresse de livraison différente de l'adresse de livraison précédente. – DNB

2

Vous pouvez utiliser la fonction de fenêtre row_number faire l'affaire:

;with cte as(select *, row_number() over(partition by cust_id, address 
             order by purchase_id) as rn from table) 
select * from cte 
where rn = 1 
+0

Merci Giorgi Nakeuri, ça marche. Je connaissais row_number() mais je n'avais pas réalisé que je pouvais partitionner plusieurs champs et obtenir le résultat désiré. – DNB

+0

Je ne suis pas sûr que ce soit une bonne réponse. Il ne trouve pas la ligne 7, car il a le même cust_id et l'adresse. –

0

Désolé pour une réponse tardive.Je voulais réagir à ce post il y a quelques jours.

La façon la plus appropriée que je puisse penser est d'utiliser la fonction LAG.

Prenez ceci:

select purchase_id, cust_id, address, 
lag(address, 1) over (partition by cust_id order by purchase_id) prev_address 
from x order by cust_id, purchase_id; 
-------------+---------+----------+--------------+ 
PURCHASE_ID | CUST_ID | ADDRESS | PREV_ADDRESS | 
-------------+---------+----------+--------------+ 
1   | 5  | address1 | [NULL]  | 
3   | 5  | address1 | address1  | 
5   | 5  | address3 | address1  | 
6   | 5  | address3 | address3  | 
7   | 5  | address1 | address3  | 
2   | 6  | address2 | [NULL]  | 
4   | 6  | address2 | address2  | 
8   | 6  | address4 | address2  | 
-------------+---------+----------+--------------+ 

Et vous pouvez facilement détecter les lignes avec les événements comme vous avez décrit

select purchase_id, cust_id, address, prev_address from (
    select purchase_id, cust_id, address, 
    lag(address, 1) over (partition by cust_id order by purchase_id) prev_address 
    from x 
) sub 
where not equal_null(address, prev_address) 
order by cust_id, purchase_id; 
-------------+---------+----------+--------------+ 
PURCHASE_ID | CUST_ID | ADDRESS | PREV_ADDRESS | 
-------------+---------+----------+--------------+ 
1   | 5  | address1 | [NULL]  | 
5   | 5  | address3 | address1  | 
7   | 5  | address1 | address3  | 
2   | 6  | address2 | [NULL]  | 
8   | 6  | address4 | address2  | 
-------------+---------+----------+--------------+ 

Notez que j'utilise la fonction EQUAL_NULL avoir NULL = NULL sémantique.

Notez que la fonction LAG peut être intense que informatiquement (mais comparable à l'utilisation ROW_NUMBER proposé précédemment)