Ich bin im RoR-Stack und ich musste etwas SQL schreiben, um diese Abfrage für alle Datensätze abzuschließen, die "offen" sind, was bedeutet, dass die aktuelle Zeit innerhalb der angegebenen Betriebsstunden liegt. In der Tabelle hours_of_operations
speichern zwei integer
Spalten opens_on
und closes_on
einen Wochentag, und zwei time
Felder opens_at
und closes_at
speichern die jeweilige Tageszeit.
Ich habe eine Abfrage erstellt, die das aktuelle Datum und die aktuelle Uhrzeit mit den gespeicherten Werten vergleicht, aber ich frage mich, ob es eine Möglichkeit gibt, zu einem bestimmten Datumstyp zu wechseln und PostgreSQL den Rest erledigt?
Das Fleisch der Abfrage ist:
%Vor%Der Grund für solch eine dichte Komplexität ist, dass eine Betriebsstunde um das Ende der Woche herumgehen kann, zum Beispiel beginnend am Mittag am Sonntag und bis Montag um 6 Uhr morgens. Da ich Werte in UTC speichere, gibt es viele Fälle, in denen die lokale Zeit des Benutzers auf sehr seltsame Weise umbrechen könnte. Die obige Abfrage stellt sicher, dass Sie ANY zwei mal der Woche eingeben können und wir den Umbruch kompensieren.
Gestalten Sie die Tabelle neu und speichern Sie die Öffnungszeiten (Betriebsstunden) als eine Gruppe von tsrange
(Bereich der Zeitmarke ohne Zeitzone) Werte. Benötigt Postgres 9.2 oder höher .
Wählen Sie eine zufällige Woche, um Ihre Öffnungszeiten zu planen. Ich mag die Woche:
1996-01-01 (Montag) bis 1996-01-07 (Sonntag)
Das ist das letzte Schaltjahr, in dem der 1. Januar zufällig ein Montag ist. Aber es kann jede zufällige Woche für diesen Fall sein. Sei nur konsequent.
Installieren Sie zuerst das zusätzliche Modul btree_gist
. Warum?
Erstellen Sie die Tabelle wie folgt:
%Vor% Die one Spalte hours
ersetzt alle Ihre Spalten:
Beispielsweise werden die Betriebsstunden von Mittwoch, 18:30 bis Donnerstag, 05:00 UTC wie folgt eingegeben:
%Vor% Die Ausschlussbedingung hoo_no_overlap
verhindert überlappende Einträge pro Shop. Es wird mit einem GiST-Index implementiert, der auch Ihre Abfrage unterstützt. Betrachten Sie das Kapitel "Index und Leistung" , in dem die Indexierungsstrategie erläutert wird.
Die Check-Einschränkung hoo_bounds_inclusive
erzwingt inklusive Grenzen für Ihre Bereiche, mit zwei bemerkenswerten Konsequenzen:
Die Check-Einschränkung hoo_standard_week
erzwingt die äußeren Grenzen der Staging-Woche mit dem " Bereich ist enthalten durch "operator <@
.
Bei einschließlich -Begrenzungen müssen Sie einen Sonderfall beachten, bei dem die Zeit um Sonntag Mitternacht herumgeht:
%Vor%Sie müssen beide Zeitstempel gleichzeitig suchen. Hier ist ein ähnlicher Fall mit exklusiver Obergrenze, der diesen Mangel nicht aufweist:
f_hoo_time(timestamptz)
Um eine gegebene timestamp with time zone
zu normalisieren:
Die Funktion akzeptiert timestamptz
und gibt timestamp
zurück. Es fügt das verstrichene Intervall der jeweiligen Woche ( - date_trunc('week', )
in UTC-Zeit (!) Zum Startpunkt unserer Staging-Woche hinzu. ( date
+ interval
erzeugt timestamp
.)
f_hoo_hours(timestamptz, timestamptz)
Um Bereiche zu normalisieren und diese zu teilen, die Mon 00:00 kreuzen. Diese Funktion benötigt ein beliebiges Intervall (als zwei timestamptz
) und erzeugt ein oder zwei normalisierte tsrange
-Werte. Es umfasst beliebige legale Eingabe und verbietet den Rest:
To INSERT
a einzelne Eingabezeile:
Dies führt zu zwei Zeilen, wenn der Bereich um Mo 00:00 geteilt werden muss.
To INSERT
mehrere Eingabezeilen:
Über den impliziten LATERAL
Join:
Mit dem angepassten Design kann Ihre gesamte große, komplexe, teure Abfrage durch ... ersetzt werden:
SELECT *
FROM hoo
WHERE hours @> f_hoo_time(now());
Für ein wenig Spannung habe ich eine Spoilerplatte über die Lösung gelegt. Bewegen Sie die Maus über .
Die Abfrage wird durch den GiST-Index und schnell unterstützt, sogar für große Tabellen.
SQL Fiddle (mit weiteren Beispielen).
Wenn Sie die Gesamtöffnungszeiten (pro Geschäft) berechnen möchten, hier ist ein Rezept:
Der Begrenzungsoperator für Bereichstypen kann mit GiST oder SP-GiST index. Beide können verwendet werden, um eine Ausschlussbeschränkung zu implementieren, aber nur GiST unterstützt mehrspaltige Indizes :
Derzeit unterstützen nur die B-Tree-, GiST-, GIN- und BRIN-Indextypen mehrspaltige Indizes.
Und die Reihenfolge der Indexspalten ist wichtig :
Ein mehrspaltiger GiST-Index kann mit diesen Abfragebedingungen verwendet werden Beteiligen Sie jede Teilmenge der Spalten des Index. Bedingungen für zusätzliche Spalten beschränken die vom Index zurückgegebenen Einträge, aber die Bedingung auf der ersten Spalte ist der wichtigste für die Bestimmung, wie viel des Index muss gescannt werden. Ein GiST-Index wird relativ sein unwirksam, wenn seine erste Spalte nur einige eindeutige Werte aufweist wenn es viele verschiedene Werte in zusätzlichen Spalten gibt.
Wir haben also widerstreitende Interessen hier. Bei großen Tabellen gibt es viel mehr unterschiedliche Werte für shop_id
als für hours
.
shop_id
ist schneller zum Schreiben und zum Erzwingen der Ausschlussbeschränkung. hours
in unserer Abfrage. Die erste Spalte wäre besser. shop_id
nachschlagen müssen, ist ein einfacher btree-Index viel schneller dafür. hours
gefunden, der für die Abfrage am schnellsten sein soll. Mein Skript zum Erzeugen von Dummy-Daten:
%Vor% Ergebnisse in 141k zufällig generierten Zeilen, 30k distinct shop_id
, 12k distinct hours
. (In der Regel ist der Unterschied größer.) Tabellengröße 8 MB.
Ich habe die Ausschlussbeschränkung gelöscht und neu erstellt:
%Vor% shop_id
ist zuerst ~ 4x schneller.
Zusätzlich habe ich zwei weitere für die Leseleistung getestet:
%Vor% Nach VACUUM FULL ANALYZE hoo;
habe ich zwei Abfragen ausgeführt:
Ich habe einen index-only-Scan für jeden (außer natürlich für "no index"):
%Vor%Wenn Sie viel mehr lesen als Sie schreiben (typischer Anwendungsfall), behalten Sie die Ausschlussbeschränkung, wie zu Beginn vorgeschlagen, und erstellen Sie einen zusätzlichen SP-GiST-Index, um die Leseleistung zu optimieren.
Tags und Links sql ruby-on-rails postgresql constraints range-types