Exécution d'une requête complexe pour chaque date d'une plage

9

J'ai un tableau des commandes

   Column   |            Type             |                      Modifiers                      
------------+-----------------------------+-----------------------------------------------------
 id         | integer                     | not null default nextval('orders_id_seq'::regclass)
 client_id  | integer                     | not null
 start_date | date                        | not null
 end_date   | date                        | 
 order_type | character varying           | not null

Les données ont des ordres permanents qui ne se chevauchent pas pour un client_id et parfois une commande temporaire qui remplace l'ordre permanent à sa date_début lorsqu'ils ont un client_id correspondant. Des contraintes au niveau de l'application empêchent le chevauchement des commandes du même type.

 id | client_id | start_date |  end_date  | order_type 
----+-----------+------------+------------+------------
 17 |        11 | 2014-02-05 |            | standing
 18 |        15 | 2014-07-16 | 2015-07-19 | standing
 19 |        16 | 2015-04-01 |            | standing
 20 |        16 | 2015-07-18 | 2015-07-18 | temporary

Par exemple, sur le 2015-07-18client 16, l'ordre n ° 20 est actif car il remplace l'ordre permanent n ° 19. Avec un peu d'histoires, j'ai trouvé un moyen efficace de rechercher des identifiants de commande actifs à une date.

    SELECT id from (
      SELECT
        id,
        first_value(id) OVER (PARTITION BY client_id ORDER BY order_type DESC) active_order_id
      FROM orders
      WHERE start_date <= ? and (end_date is null OR end_date >= ?)
    ) active_orders
    WHERE id = active_order_id

Si vous interrogez ceci avec 2015-07-18comme espaces réservés, vous obtiendrez

 id 
----
 17
 18
 20

Le plan de requête sur cette requête par rapport à certaines de mes autres idées (comme les sous-requêtes comptant le nombre de commandes temporaires pour un client à une date) est assez petit et j'en suis assez satisfait. (la conception de la table, je ne suis pas ravi)

Maintenant, j'ai besoin d'un pour trouver toutes les commandes actives pour une plage de dates jointes aux dates auxquelles elles sont actives. Par exemple, avec la plage de dates de 2015-07-18à 2015-07-19j'aimerais le résultat suivant.

active_date | id 
------------+----
 2015-07-18 | 17
 2015-07-18 | 18
 2015-07-18 | 20
 2015-07-19 | 17
 2015-07-19 | 18
 2015-07-19 | 19

L'ordre 20 remplace l'ordre 19 activé 2015-07-18mais pas activé 2015-07-19.

J'ai trouvé que generate_series()je peux générer une plage de dates, mais je n'ai pas la moindre idée de la façon de joindre cela à ceci pour obtenir un tableau des dates et des identifiants de commande. Mon intuition est une jointure croisée, mais je ne peux pas comprendre comment faire fonctionner cela dans cette circonstance.

Merci

MISE À JOUR Ajout d'un violon sql .

reconbot
la source
2
Pourriez-vous montrer quelques exemples de données? Ces choses actives / non actives et temporaires ne sont pas très claires après la première lecture.
dezso
Oui, ce n'est pas clair. Votre requête trouvera une commande par client et elle ne semble pas déterministe. S'il y a 2 commandes ou plus pour un client, de même type, lequel des deux sera retourné sera arbitraire et variera selon l'exécution. Donc, soit vous avez des contraintes sur la table que vous ne nous avez pas dites, soit votre requête n'est pas correcte.
ypercubeᵀᴹ
J'ai mis à jour ma question avec beaucoup plus de détails, et oui il y a des contraintes sur les données.
reconbot

Réponses:

5

J'utiliserais select distinct onau lieu de la fonction de fenêtre, puis joignais simplement les jours.

select 
    distinct on (date, client_id) date, 
    id 
from orders
inner join generate_series('2015-07-18'::date, '2015-07-19'::date, '1 day') date
  on start_date <= date and (end_date is null or date <= end_date)
order by date, client_id, order_type desc

http://sqlfiddle.com/#!15/5a420/16/0

Je peux en dire plus si quelque chose n'est pas clair.

Simon Perepelitsa
la source
Cela ne couvre pas l'ordre temporaire / l'ordre permanent, mais cela pourrait être fait après la jointure =)
reconbot
Cela spécifie le même ordre que dans votre requête de fenêtre. Ainsi, pour tout (date, client_id), il sélectionnerait le premier type de commande dans l'ordre alphabétique inversé.
Simon Perepelitsa
La jointure interne est parfaite et la sélection distincte est beaucoup plus facile à comprendre (et fonctionne tout aussi bien) que la fenêtre. Pour une autre raison, je ne devrais pas utiliser les fonctions de fenêtrage?
reconbot
1
C'est à peu près ça. Je pense que distinct onc'est encore plus optimisé que la requête de fenêtre. Soit dit en passant, je dois mentionner qu'il s'agit d'un problème courant de "top-in-group" dans SQL: stackoverflow.com/questions/3800551/…
Simon Perepelitsa
C'est une excellente lecture, j'ai quelques études à faire. Si vous avez un peu de temps, j'ai une version développée de cette question qui utilise ce que j'ai appris ici. dba.stackexchange.com/questions/108767/… Je suis sûr que je serai de retour pour le mettre à jour avec ce que j'apprends de ce lien. Et merci
reconbot
0

Écrivez une fonction qui prend une seule date comme paramètre et renvoie une liste de date + id qui ont une commande.

Ensuite, utilisez le generate_series comme vous l'avez suggéré et appelez la fonction sur la plage de dates.

Il s'agit d'une stratégie courante pour traiter des conditions complexes en SQL.

J'ai inclus du code ci-dessous, mais la réponse SQL ci-dessus est beaucoup plus simple.

Voici la fonction:

create or replace function o( date) returns setof INT AS '
SELECT id from (
 SELECT
  id,
  first_value(id) OVER (PARTITION BY client_id ORDER BY order_type DESC) active_order_id
 FROM orders
 WHERE start_date <= $1 and (end_date is null OR end_date >= $1)
) active_orders
WHERE id = active_order_id;
' LANGUAGE sql ;

Et comment l'appeler:

select distinct d, o(d::date) 
from generate_series('2015-07-18'::date, '2015-07-19'::date, '1 day') as d;

SQLFiddle

Don Drake
la source
2
Vous voudrez peut-être vider cette réponse avec quelques détails, un exemple de code, etc. En l'état, cette réponse peut être supprimée car elle est assez vague.
Max Vernon
Pourriez-vous mettre à jour mon violon avec un exemple? sqlfiddle.com/#!15/5a420/3/0
reconbot
J'ai mis à jour ma réponse pour inclure du code mais la réponse ci-dessus est plus simple.
Don Drake