web-dev-qa-db-fra.com

Obtenez des lignes avec la date la plus récente pour chaque élément différent

Disons que c'est la date d'échantillon provenant d'une jointure de 2 tables. La base de données est Postgres 9.6

id  product_id  invoice_id  amount       date
1    PROD1       INV01       2          01-01-2018
2    PROD2       INV02       3          01-01-2018
3    PROD1       INV01       2          05-01-2018
4    PROD1       INV03       1          05-01-2018
5    PROD2       INV02       3          08-01-2018
6    PROD2       INV04       4          08-01-2018

Je veux savoir s'il est possible de manière optimisée de:

  1. Obtenez tous les PRODx avec leurs INVx respectifs qui ont la dernière date, mais par product_id. Veuillez noter que les enregistrements inutilisés d'un jour peuvent être signalés à un nouveau. Ça signifie:
id  product_id  invoice_id  amount       date
3    PROD1       INV01       2          05-01-2018
4    PROD1       INV03       1          05-01-2018
5    PROD2       INV02       3          08-01-2018
6    PROD2       INV04       4          08-01-2018
  1. Obtenez des montants totaux quotidiens pour chaque PRODx mais comblez les lacunes avec les précédentes si le jour n'existe pas.

Ça signifie:

 product_id    amount       date
   PROD1         2          01-01-2018
   PROD2         3          01-01-2018
   PROD1         2          02-01-2018
   PROD2         3          02-01-2018
   PROD1         2          03-01-2018
   PROD2         3          03-01-2018
   PROD1         2          04-01-2018
   PROD2         3          04-01-2018
   PROD1         3          05-01-2018
   PROD2         3          05-01-2018
   PROD1         3          06-01-2018
   PROD2         3          06-01-2018
   PROD1         3          07-01-2018
   PROD2         3          07-01-2018
   PROD1         3          08-01-2018
   PROD2         7          08-01-2018

Quelques réflexions:

  1. Pour la première question, je pourrais obtenir la max(date) pour chaque PRODx et le choix pour chaque PRODx les lignes qui ont la date=with max(date) mais je me demandais s'il y avait un moyen plus rapide d'obtenir cela étant donné un grand nombre des recors dans la base de données

  2. Pour la deuxième question, je pourrais générer une série de dates pour l'intervalle nécessaire, puis utiliser WITH rows As Et faire le regroupement des requêtes par product_id Et sum par montant, puis sélectionner pour chaque date les valeurs précédentes de rows avec un limit 1 mais cela ne semble pas optimisé non plus.

Dans l'attente de toute entrée. Je vous remercie.

Modification ultérieure: essayer de tester DISTINCT ON ().

  • Si j'ai distinct on(product_id, invoice_id) alors je n'ai pas seulement les plus récents pour la date la plus récente. S'il y avait des factures_id dans le passé, à côté de la dernière date, elles seront retournées
  • Si j'ai distinct on (product_id) alors il revient de la date la plus récente, mais comme d'habitude, seulement les dernières lignes même si au dernier jour j'ai deux positions pour PROD1.

Fondamentalement, j'ai besoin de quelque chose comme "J'ai besoin de la date la plus récente, de tous les product_ids et de leurs facture_ids tout en gardant à l'esprit qu'un product_id peut avoir plusieurs facture_ids"

Édition ultérieure 2:

L'exécution d'une requête comme pour la première question semble être assez rapide:

select product_id, invoice_id, amount
from mytable inner join myOtherTable on...
             inner join (select max(date) as last_date, product_id 
                         from mytable 
                         group by product_id) sub on mytable.date = 
                         sub.last_date 
7
Alin

Skinning Q # 1 indépendamment et légèrement différent de @ypercube

with cte as (select row_number() over (partition by product_id,
                                       invoice_id 
                                 order by dt desc) as rn,
                    product_id,
                    invoice_id,
                    amount,dt
               from product ) 
select product_id, invoice_id,amount,dt
  from cte
 where rn=1
 order by product_id,invoice_id;

 product_id | invoice_id | amount |     dt     
------------+------------+--------+------------
 PROD1      | INV01      |      2 | 2018-01-05
 PROD1      | INV03      |      1 | 2018-01-05
 PROD2      | INV02      |      3 | 2018-01-08
 PROD2      | INV04      |      4 | 2018-01-08
(4 rows)

Pour Q # 2, vous êtes sur la bonne voie, mais le SQL aura une jointure croisée (halètement!)

Je pense qu'une fonction avec une boucle/curseur serait plus optimisée (je vais essayer ça dans mon prochain bloc de temps libre)

--the cte will give us the real values
with cte as (select product_id, 
                    sum(amount) as amount, 
                    dt
               from product
              group by product_id,dt)
select p.product_id,  
       (select cte.amount --choose the amount
          from cte
         where cte.product_id = p.product_id
           and cte.dt <= d.gdt -- for same day or earlier
         order by cte.dt desc
         limit 1) as finamt,
       d.gdt
from (select generate_series( (select min(dt)
                                 from product), --where clause if some products 
                                                --don't have an amount
                              (select max(dt)
                                 from product),
                              '1 day' 
                            )::date as gdt)  d
cross join --assuming each listed product has an amount on the min date
     (select distinct product_id
        from product) p
left join --since we need to fill the gaps
     cte on ( d.gdt = cte.dt 
             and p.product_id = cte.product_id)
order by d.gdt, p.product_id
;
5
amacvar

Je comprends que vous souhaitez que toutes les lignes avec la dernière date pour chaque produit (liens inclus, c'est-à-dire toutes les lignes avec la dernière date). Cela peut être fait avec la fonction rank():

select id, product_id, invoice_id, amount, date
from
  ( select id, product_id, invoice_id, amount, date,
           rank() over (partition by product_id
                        order by date desc) as rnk
    from 
        -- your joins
  ) as t 
where rnk = 1 ;
3
ypercubeᵀᴹ

J'accepte votre méthode d'édition ultérieure, elle devrait être:

select product_id, invoice_id, amount 
    from mytable inner join 
    (select max(date) as last_date, product_id, invoice_id 
        from mytable 
        group by product_id) sub 
    on mytable.date = sub.last_date 
    and mytable.product_id = sub.product_id 
    and mytable.invoice_id = sub.invoice_id;

La "clé" doit être le date, product_id et invoice_id.

0
user166779