3

Dans PostgreSQL: J'ai une table qui a 3 colonnes:PostgreSQL SELECT la dernière commande par client et par plage de dates

CustomerNum, OrderNum, OrderDate.

Il peut y avoir (ou non) beaucoup de commandes pour chaque client par plage de dates. Ce dont j'ai besoin, c'est le dernier numéro de commande pour chaque client qui se trouve dans la plage de dates fournie. Ce que j'ai fait est obtenir un ResultSet des clients et interroger chacun séparément, mais cela prend trop de temps.

Existe-t-il un moyen d'utiliser une sous-sélection pour sélectionner les clients, puis obtenir le dernier numéro de commande pour chaque client?

Répondre

7
select customernum, max(ordernum) 
from table 
where orderdate between '...' and '...' 
group by customernum 

qui est tout.

+0

C'est ce que j'allais dire. Tout ce qu'il faut c'est un groupe par. –

+0

L'hypothèse selon laquelle 'OrderNum' est une valeur séquentielle peut ne pas tenir ou simplement l'ordre de création peut ne pas correspondre à l'ordre des dates (par exemple,' OrderDate' peut marquer quand la commande a été finalisée, plutôt que quand elle a été créée, et vous vous souciez du temps de finalisation). –

4
SELECT t1.CustomerNum, t1.OrderNum As LastOrderNum, t1.LastOrderDate 
    FROM table1 As t1 
WHERE t1.OrderDate = (SELECT MAX(t2.OrderDate) 
         FROM table1 t2 
         WHERE t1.CustomerNum = t2.CustomerNum 
          AND t2.OrderDate BETWEEN date1 AND date2) 
    AND t1.OrderDate BETWEEN date1 AND date2 
+0

Cela entraînera une Une boucle imbriquée avec une analyse de table complète externe et une sous-requête interne car Postgresql n'est pas capable de lever la contrainte de plage OrderDate via l'agrégation dans la sous-requête. Ajouter AND t1.OrderDate ENTRE date1 ET date2 lui permettra d'utiliser un index sur OrderDate pour limiter le jeu de résultats. –

+0

@Ants: Postgre n'est-il pas assez intelligent pour utiliser la sous-requête lorsque la table externe de la boucle imbriquée se joint? – erikkallen

0

ne suis pas sûr de votre structure de table Client ou relations, mais cela devrait fonctionner:

SELECT Customer.Num, (
    SELECT OrderNum FROM Orders WHERE CustomerNum = Customer.Num AND OrderDate BETWEEN :start AND :end ORDER BY OrderNum DESC LIMIT 1 
) AS LastOrderNum 
FROM Customer 
0

Si par le dernier numéro de commande vous dire le plus grand nombre de commandes, vous pouvez aussi utiliser votre sélection comme le prédicat pour le client num , groupe les résultats et sélectionnez le maximum:

SELECT CustomerNum, MAX(OrderNum) AS LastOrderNum 
    FROM Orders 
    WHERE 
     CustomerNum IN (SELECT CustomerNum FROM ...) 
      AND 
     OrderDate BETWEEN :first_date AND :last_date 
    GROUP BY CustomerNum 

Si le dernier numéro de commande est pas nécessairement le plus grand nombre de commandes, vous devrez soit trouver la plus grande date de commande pour chaque client et rejoindre ainsi la reste des commandes pour trouver t il nombre correspondant (s):

SELECT O.CustomerNum, O.OrderNum AS LastOrderNum 
    FROM 
     (SELECT CustomerNum, MAX(OrderDate) AS OrderDate 
      FROM Orders 
      WHERE 
       OrderDate BETWEEN :first_date AND :last_date 
        AND 
       CustomerNum IN (SELECT CustomerNum FROM ...) 
      GROUP BY CustomerNum 
     ) AS CustLatest 
      INNER JOIN 
     Orders AS O USING (CustomerNum, OrderDate); 
10

Sur postgres vous pouvez également utiliser la clause DISTINCT ON non standard:

SELECT DISTINCT ON (CustomerNum) CustomerNum, OrderNum, OrderDate 
    FROM Orders 
    WHERE OrderDate BETWEEN 'yesterday' AND 'today' 
    ORDER BY CustomerNum, OrderDate DESC; 

Voir http://www.postgresql.org/docs/current/static/sql-select.html#SQL-DISTINCT

+0

On dirait que la meilleure réponse à cette question, même si ce n'est pas standard SQL. Merci – jlandercy

+0

Plus rapide que les autres solutions, il m'a donné un résultat en 35 secondes sur une table de 26 millions de lignes. Cela peut aussi fonctionner avec des champs qui ne sont pas dans le groupe, comme: 'SELECT DISTINCT ON (champ (s)) * FROM ...' –

0
-- generate some data 
DROP TABLE tmp.orders; 
CREATE TABLE tmp.orders 
    (id INTEGER NOT NULL 
    , odate DATE NOT NULL 
    , payload VARCHAR 
    ) 
    ; 
ALTER TABLE tmp.orders ADD PRIMARY KEY (id,odate); 

INSERT INTO tmp.orders(id,odate,payload) VALUES 
    (1, '2011-10-04' , 'one') 
, (1, '2011-10-24' , 'two') 
, (1, '2011-10-25' , 'three') 
, (1, '2011-10-26' , 'four') 
, (2, '2011-10-23' , 'five') 
, (2, '2011-10-24' , 'six') 
    ; 

-- CTE to the rescue ... 
WITH sel AS (
    SELECT * FROM tmp.orders 
    WHERE odate BETWEEN '2011-10-23' AND '2011-10-24' 
    ) 
SELECT * FROM sel s0 
WHERE NOT EXISTS (
    SELECT * FROM sel sx 
    WHERE sx.id = s0.id 
    AND sx.odate > s0.odate 
    ) 
    ; 

Résultat:

DROP TABLE 
CREATE TABLE 
NOTICE: ALTER TABLE/ADD PRIMARY KEY will create implicit index "orders_pkey" for table "orders" 
ALTER TABLE 
INSERT 0 6 
id | odate | payload 
----+------------+--------- 
    1 | 2011-10-24 | two 
    2 | 2011-10-24 | six 
(2 rows)