2016-04-18 10 views
2

Als vereinfachtes Beispiel muss ich jede Instanz auswählen, bei der ein Kunde eine Lieferadresse hatte, die sich von der vorherigen Lieferadresse unterscheidet. So habe ich eine große Tabelle mit Spalten wie:SQL: Sortieren nach, dann erste Zeile mit unterschiedlichem Wert für mehrere Spalten auswählen

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 

Beachten Sie, dass Kunden „zurückgehen“, um eine vorherige Adresse als Kunde 5 in Reihe tat 7.

Was ich wählen möchten (und wie so effizient wie möglich, da dies eine ziemlich große Tabelle ist) ist die erste Zeile von jedem "Block", in dem ein Kunde nachfolgende Bestellungen an die gleiche Adresse versandt hat. In diesem Beispiel wären dies die Zeilen 1,2,5,7 und 8. Bei allen anderen hat der Kunde die gleiche Adresse wie bei seiner vorherigen Bestellung.

So effektiv möchte ich zuerst ORDER BY (cust_id, date), dann SELECT purchase_id, cust_id, min(date), address, description.

Allerdings habe ich Probleme, weil SQL GROUP BY normalerweise vor ORDER BY getan werden muss. Ich kann daher nicht herausfinden, wie man z.B. eine der Top-Antworten auf this question (die ich sonst ziemlich gerne.) Es ist notwendig (zumindest konzeptionell), nach Datum zu sortieren, bevor gruppieren oder Aggregatfunktionen wie min() verwenden, sonst würde ich Instanzen wie Zeile 7 in meiner Beispieltabelle vermissen, wo ein Kunde zu einer vorherigen Adresse "zurückbewegte".

Beachten Sie auch, dass zwei Kunden eine Adresse teilen können, also muss ich effektiv nach cust_id und address nach der Bestellung nach Datum gruppieren.

Ich bin mit Schneeflocke, die ich glaube, die meisten die gleichen Befehle wie neuere Versionen von PostgreSQL und SQL Server hat (obwohl ich ziemlich neu bin so auf Schneeflocke nicht ganz sicher.)

+0

Haben Sie nur für Kunden mit mehr als einer Adresse zurück Einkäufe wollen? –

+0

Kann 1,2,5,8 statt 1,2,7,8 sein? –

+0

Anthony E: Nein, ich möchte (mindestens) eine Zeile für alle Kunden zurückgeben, die jemals eine Adresse hatten, und mehr Zeilen für Kunden, die die Adressen einmal oder mehrmals geändert haben. Giorgi Nakeuri: Danke, sollte 1,2,5,7 und 8 sein. (Zeilen 5 und 7 haben beide eine andere Adresse als die letzte, die der Kunde benutzt hat.) Bearbeitet. – DNB

Antwort

0

Dies ist wahrscheinlich wäre am besten durch eine Unterabfrage gelöst, um den ersten Kauf für jeden Benutzer zu erhalten, und dann IN verwenden, um Zeilen basierend auf diesem Ergebnis zu filtern.

Um zu klären, purchase_id ist eine Autoinkrement-Spalte, richtig? Wenn ja, hat ein Kauf mit einem höheren purchase_id muss zu einem späteren Zeitpunkt erstellt wurde, und sollen Folgendes genügen:

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

Wenn Sie nur den ersten Kauf für Kunden mit mehr als einer Adresse wünschen, eine HAVING Klausel hinzufügen , http://sqlfiddle.com/#!9/12d75/6

Allerdings, wenn purchase_id ist nicht eine Autoinkrement-Spalte, dann SELECT auf beide cust_id: in Ihre Unterabfrage:

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 und min(date) auf Ihre Unterabfrage und verwenden Sie ein INNER JOIN auf cust_id und 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 

Die erste Abfrage Beispiel wird wahrscheinlich schneller sein, aber so verwenden, dass, wenn Sie purchase_id eine Autoinkrement-Spalte.

+0

Dank Anthony jedoch nicht jede nachfolgende "neue" Adresse für jeden Kunden zurückgegeben. Ich möchte nicht nur den ersten Kauf eines jeden Nutzers; Ich möchte * jeden * ersten Kauf, der eine Lieferadresse hat, die sich von der vorherigen Lieferadresse unterscheidet. – DNB

2

können Sie row_number Fensterfunktion verwenden den Trick zu tun:

;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

Danke Giorgi Nakeuri, das funktioniert. Ich wusste von row_number() wusste aber nicht, dass ich über mehrere Felder partitionieren und das gewünschte Ergebnis erhalten konnte. – DNB

+0

Ich bin mir nicht sicher, ob das eine richtige Antwort ist. Zeile 7 wird nicht gefunden, da sie dieselbe cust_id und Adresse hat. –

0

Sorry für eine späte Antwort.Ich wollte vor ein paar Tagen auf diesen Beitrag reagieren.

Die "richtigste" Möglichkeit, die ich mir vorstellen kann, ist die Verwendung der LAG-Funktion.

Nehmen:

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  | 
-------------+---------+----------+--------------+ 

Und dann kann man leicht erkennen Reihen mit den Ereignissen, wie Sie

beschrieben
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  | 
-------------+---------+----------+--------------+ 

Bitte beachte, dass ich EQUAL_NULL Funktion bin mit NULL = NULL Semantik hat.

Beachten Sie, dass die LAG-Funktion allerdings sehr rechenintensiv sein kann (aber vergleichbar mit der Verwendung von ROW_NUMBER vorgeschlagen früher)