Ausführen einer komplexen Abfrage für jedes Datum in einem Bereich

9

Ich habe eine Tabelle mit Bestellungen

   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

Die Daten haben nicht überlappende Daueraufträge für eine client_id und gelegentlich einen temporären Auftrag, der den Dauerauftrag am Startdatum überschreibt, wenn sie eine übereinstimmende client_id haben. Es gibt Einschränkungen auf Anwendungsebene, die verhindern, dass sich Aufträge desselben Typs überschneiden.

 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

Zum Beispiel hat auf 2015-07-18Client 16 die Bestellung Nr. 20 als aktive Bestellung, da sie den Dauerauftrag Nr. 19 überschreibt. Mit einigem Aufwand fand ich eine effiziente Möglichkeit, aktive Bestell-IDs an einem Datum abzufragen.

    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

Wenn Sie dies 2015-07-18als Platzhalter abfragen , erhalten Sie

 id 
----
 17
 18
 20

Der Abfrageplan für diese Abfrage ist im Vergleich zu einigen meiner anderen Ideen (z. B. Unterabfragen, bei denen die Anzahl der temporären Bestellungen für einen Kunden an einem Datum gezählt wird) recht klein und ich bin ziemlich zufrieden damit. (Das Design des Tisches, ich bin nicht begeistert)

Jetzt muss ich alle aktiven Bestellungen für einen Datumsbereich finden, die mit den Daten verknüpft sind, an denen sie aktiv sind. Zum Beispiel möchte ich mit dem Datumsbereich von 2015-07-18bis 2015-07-19das folgende Ergebnis.

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

Bestellung 20 überschreibt Bestellung 19 ein, 2015-07-18aber nicht ein 2015-07-19.

Ich habe festgestellt, dass generate_series()ich eine Reihe von Daten generieren kann, aber ich habe keine Ahnung, wie ich das damit verbinden soll, um eine Tabelle mit Daten und Bestell-IDs zu erhalten. Meine Vermutung ist ein Cross Join, aber ich kann nicht herausfinden, wie das unter diesen Umständen funktioniert.

Vielen Dank

UPDATE Es wurde eine SQL-Geige hinzugefügt .

Reconbot
quelle
2
Könnten Sie einige Beispieldaten zeigen? Diese aktiven / nicht aktiven und temporären Dinge sind nach dem ersten Lesen nicht sehr klar.
Dekso
Ja, das ist nicht klar. Ihre Anfrage findet eine Bestellung pro Kunde und scheint nicht deterministisch zu sein. Wenn es zwei oder mehr Aufträge für einen Kunden mit demselben Typ gibt, ist der zurückgegebene Auftrag beliebig und variiert je nach Ausführung. Sie haben entweder einige Einschränkungen in der Tabelle, die Sie uns nicht mitgeteilt haben, oder Ihre Anfrage ist nicht korrekt.
Ypercubeᵀᴹ
Ich habe meine Frage mit viel mehr Details aktualisiert, und ja, es gibt Einschränkungen für die Daten.
Reconbot

Antworten:

5

Ich würde select distinct onanstelle der Fensterfunktion verwenden, dann einfach die Tage verbinden.

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

Ich kann mehr ausarbeiten, wenn etwas nicht klar ist.

Simon Perepelitsa
quelle
Dies gilt nicht für die vorübergehende Bestellung / Dauerauftrag, aber dies könnte nach dem Beitritt erfolgen =)
Reconbot
Dies gibt die gleiche Reihenfolge wie in Ihrer Fensterabfrage an. Für jedes (Datum, client_id) würde es also den ersten order_type in umgekehrter alphabetischer Reihenfolge auswählen.
Simon Perepelitsa
Die innere Verknüpfung ist perfekt, und die Auswahlunterscheidung ist viel einfacher zu verstehen (und funktioniert ungefähr genauso gut) als das Fenster. Gibt es einen anderen Grund, warum ich die Fensterfunktionen nicht verwenden sollte?
Reconbot
1
Das ist alles. Ich denke distinct onist noch optimierter als die Fensterabfrage. Übrigens sollte ich erwähnen, dass dies ein häufiges "Top-in-Group" -Problem in SQL ist: stackoverflow.com/questions/3800551/…
Simon Perepelitsa
Das ist eine großartige Lektüre, ich muss noch etwas lernen. Wenn Sie etwas Zeit haben, habe ich eine erweiterte Version dieser Frage, die das verwendet, was ich hier gelernt habe. dba.stackexchange.com/questions/108767/… Ich bin sicher, ich werde zurück sein, um es mit dem zu aktualisieren, was ich aus diesem Link lerne. Und danke
Reconbot
0

Schreiben Sie eine Funktion, die ein einzelnes Datum als Parameter verwendet und eine Liste von Datums- und IDs mit einer Reihenfolge zurückgibt.

Verwenden Sie dann die von Ihnen vorgeschlagene generate_series und rufen Sie die Funktion über den Datumsbereich auf.

Dies ist eine gängige Strategie beim Umgang mit komplexen Bedingungen in SQL.

Ich habe unten Code eingefügt, aber die obige SQL-Antwort ist viel einfacher.

Hier ist die Funktion:

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 ;

Und wie man es nennt:

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

SQLFiddle

Don Drake
quelle
2
Möglicherweise möchten Sie diese Antwort mit einigen Details, Beispielcode usw. löschen. Diese Antwort wird möglicherweise gelöscht, da sie ziemlich vage ist.
Max Vernon
Könntest du meine Geige mit einem Beispiel aktualisieren? sqlfiddle.com/#!15/5a420/3/0
Reconbot
Ich habe meine Antwort so aktualisiert, dass sie Code enthält, aber die obige Antwort ist einfacher.
Don Drake