Einzelner Datentyp für ungenaue Datumswerte gemäß ISO 8601

7

Wie kann ich Datums- und Zeitwerte mit reduzierter Genauigkeit in einem PostgreSQL-Typ speichern und sie als Datums- und / oder Zeitwerte verhalten lassen ?

ISO 8601 ermöglicht Datumswerte mit reduzierter Genauigkeit. '1964', '1964-05', '1964-05-02' sind alle gültigen Darstellungen eines Wertes mit zunehmender Genauigkeit. Die Python-Datetime-Typen ermöglichen auf diese Weise auch Werte mit reduzierter Genauigkeit.

Native PostgreSQL-Zeittypen erlauben keine reduzierte Genauigkeit

Im nativen Datumstyp muss jedes Element eines Datums vorhanden sein, sonst wird der Wert abgelehnt. Das Einstellen der Elemente unterhalb der gewünschten Genauigkeitsstufe auf '00' schlägt ebenfalls fehl.

=> SELECT CAST('1964-05-02' AS DATE);
    date    
------------
 1964-05-02
(1 row)

=> SELECT CAST('1964-05' AS DATE);
ERROR:  invalid input syntax for type date: "1964-05"
LINE 1: SELECT CAST('1964-05' AS DATE);
                    ^
=> SELECT CAST('1964' AS DATE);
ERROR:  invalid input syntax for type date: "1964"
LINE 1: SELECT CAST('1964' AS DATE);
                    ^
=> SELECT CAST('1964-00-00' AS DATE);
ERROR:  date/time field value out of range: "1964-00-00"
LINE 1: SELECT CAST('1964-00-00' AS DATE);
                    ^
HINT:  Perhaps you need a different "datestyle" setting.

Erwartetes Verhalten für einen Datums- und / oder Zeittyp mit reduzierter Genauigkeit

Gibt es eine einfache Standardmethode, um die Eingabe von ISO 8601-Datumswerten mit reduzierter Genauigkeit in einen PostgreSQL-Datums- und / oder Zeittyp zu unterstützen?

Es ist möglich, einen Typ dafür zu erstellen, aber ich weiß nicht wie. Natürlich müssen die Werte im Bereich überprüft werden, Zeitzonen behandelt und alle anderen nützlichen Funktionen der integrierten Typen sinnvoll mit anderen Zeitwerten verglichen werden.

Was ich erwarte ist, dass, genau wie der Wert '1964-05-02' sich auf das gesamte Intervall zwischen 00:00:00 an diesem Tag bis 00:00:00 am nächsten Tag bezieht, ein Wert mit reduzierter Genauigkeit einfach a darstellen würde größeres Intervall: "1962-05" bezieht sich auf das gesamte Intervall zwischen 00:00:00 Anfang Mai 1962 und 00:00:00 am ersten Juni 1962.

Ein Beispiel für das, was ich sehen möchte:

=> SELECT CAST('1964-05-02 00:00' AS TIMESTAMP) = CAST('1964-05-02 00:00:00' AS TIMESTAMP);
 ?column? 
----------
 t
(1 row)

=> SELECT CAST('1964-05' AS TIMESTAMP) = CAST('1964-05-02' AS TIMESTAMP);
 ?column? 
----------
 t
(1 row)

Derzeit verhält sich Ersteres wie oben; Letzterer beschwert sich über "ungültige Eingabesyntax für Typ Zeitstempel". Meines Erachtens handelt es sich bei beiden Fällen um Werte mit reduzierter Genauigkeit, die sich im Vergleich zu Werten mit feinerer Genauigkeit vernünftig verhalten.

Die Bedeutung von 1964-05in ISO 8601 umfasst die genaueren Werte 1964-05-02und 1964-05-02 18:27und 1964-05-23. Also sollten diese alle gleich vergleichen.

große Nase
quelle
Mit anderen Worten, Sie möchten, dass der Gleichheitsoperator angibt, truewann die Zeitstempel "möglicherweise" gleich sind (unter Berücksichtigung der Genauigkeit)?
Jack sagt, versuchen Sie es mit topanswers.xyz
Nicht "könnte sein"; Die Werte sind bewusst präziser reduziert. Die Bedeutung von 1964-05in ISO 8601 umfasst die genaueren Werte 1964-05-02und 1964-05-02 18:27und 1964-05-23. Also sollten diese alle gleich vergleichen.
Bignose
Mein Punkt ist, dass Sie die Definition der Gleichheit so strecken, dass A = C und B = C A = B nicht mehr implizieren. Solange du das willst ... siehe meine aktualisierte Antwort
Jack sagt, versuche topanswers.xyz
Möchten Sie auch, dass A = B B = A impliziert, wie ich es in meiner Bearbeitung angenommen habe?
Jack sagt, versuchen Sie topanswers.xyz
Gute Fragen, danke, dass ich über meine Anforderungen nachgedacht habe. Ich erkunde
bignose

Antworten:

6

Ich habe in der Vergangenheit CHAR und VARCHAR verwendet und die fehlenden Teile durch Fragezeichen oder Bindestriche ersetzt. Fragezeichen bedeuten "nicht bekannt" und Bindestriche bedeuten "nicht zutreffend". Dies erwies sich für die Benutzer (Sekretäre und Rechtsanwaltsanwärter in komplexen Rechtsstreitigkeiten) als intuitiv genug, für die Anwälte als flexibel genug und sinnvoll sortiert.

"1964------"
"1964-??-??"
"1964-05---"
"1964-05-??"
"1964-05-02"
"1964-06---"
"1964-06-??"

Schließen Sie Ihre Deklaration und CHECK-Einschränkungen in CREATE DOMAIN oder CREATE TYPE ein, um die Wartung zu vereinfachen. CREATE DOMAIN erfordert keine zusätzliche Codierung. CREATE TYPE erfordert Support-Funktionen, die in einer einfachen Sprache geschrieben sind.

Mike Sherrill 'Cat Recall'
quelle
3
+1, create typeerfordert jedoch keine Unterstützungsfunktionen in einer einfachen Sprache, es sei denn, Sie erstellen einen neuen Basistyp. Ein zusammengesetzter Typ von a datetimeund eine Präzision enumzum Beispiel. create domainkann dann um den Typ gewickelt werden, um checkEinschränkungen hinzuzufügen . Ich persönlich möchte, dass die DB die Komplexität von Zeitzonen, Schaltjahren usw. bewältigt, wenn sie erforderlich sind. Daher würde ich es vorziehen, die mitgelieferten Typen zu verpacken.
Jack sagt, versuchen Sie topanswers.xyz
@ Jack Douglas: Guter Punkt über Support-Funktionen. Wir mussten uns nicht mit der Tageszeit befassen. Wir mussten uns mit Arten von Ungenauigkeiten auseinandersetzen, die ISO 8601 nicht erwartet hatte, wie "196? - ?? - 01". (Die Ungenauigkeit ging nicht immer einfach von rechts nach links. Fast jede Ziffer könnte beispielsweise durch einen Kaffeefleck verdeckt werden.) Ich denke also nicht, dass es funktionieren würde, den zusammengesetzten Typ bei der Ausgabe darzustellen, aber es würde nicht funktionieren Es ist nicht zu schwierig, den zusammengesetzten Typ als Eingabe zu verwenden und eine leicht verständliche Zeichenfolge mit Fragezeichen auszugeben.
Mike Sherrill 'Cat Recall'
Antwort an sich selbst: Ich create domain kann keinen zusammengesetzten Typ als Basis verwenden .
Jack sagt, versuchen Sie es mit topanswers.xyz
1
@bignose: Werte mit ähnlicher Genauigkeit werden korrekt auf Ungleichung verglichen. Werte mit unterschiedlichen Genauigkeiten lassen sich möglicherweise nicht gut vergleichen, aber es ist nicht klar, was ('1964 - ?? - ??' <'1964-05- ??') zu bewerten ist. Sie unterstützen auch keine Intervallarithmetik, aber das ist bei reduzierter Genauigkeit sowieso nicht gut definiert.
Mike Sherrill 'Cat Recall'
2
@bignose: Nein, ich habe dich verstanden und ich habe verstanden, dass sie nicht als Datums- / Uhrzeitwerte fungieren. Ich habe darauf hingewiesen, was sie gut gemacht haben, was sie nicht gut gemacht haben und was keine Implementierung unbestreitbar gut kann. Wenn Sie die von benötigten Funktionen schreiben create type, können Sie Ausdrücke des Formulars (DATE '1964-??-??' < 'DATE 1964-05-??')immer als WAHR auswerten lassen. Was Sie nicht tun können, ist alle davon zu überzeugen, dass es sinnvoll ist, dies zu tun. Und das ist wahrscheinlich der Grund, warum PostgreSQL, das native Unterstützung für etwa 3 Dutzend Datentypen bietet, diese Art von Datentyp nicht nativ unterstützt.
Mike Sherrill 'Cat Recall'
5

Nein, die Intervall - Typ unterstützt reduzierter Genauigkeit , aber keiner der anderen Datum / Zeit - Typen tun.

Mit Postgres können Sie Ihre eigenen rollen, create typeaber leider können dem Typ keine Einschränkungen hinzugefügt werden, was die Nützlichkeit in diesem Szenario einschränkt. Das Beste, was ich mir einfallen lassen kann, ist, dass Sie die Überprüfungsbeschränkungen für jedes Feld wiederholen, in dem der fuzzyTyp verwendet wird:

create type preciseness as enum('day', 'month', 'year');
create type fuzzytimestamptz as (ts timestamptz, p preciseness);
create table t( id serial primary key,
                fuzzy fuzzytimestamptz
                    check( (fuzzy).ts is not null 
                           or ((fuzzy).ts is null and (fuzzy).p is not null) ),
                    check((fuzzy).ts=date_trunc('year', (fuzzy).ts) or (fuzzy).p<'year'),
                    check((fuzzy).ts=date_trunc('month', (fuzzy).ts) or (fuzzy).p<'month'),
                    check((fuzzy).ts=date_trunc('day', (fuzzy).ts) or (fuzzy).p<'day') );

insert into t(fuzzy) values (row(date_trunc('year', current_timestamp), 'year'));
insert into t(fuzzy) values (row(date_trunc('month', current_timestamp), 'month'));
insert into t(fuzzy) values (row(date_trunc('day', current_timestamp), 'day'));

select * from t;

 id |              fuzzy
----+----------------------------------
  1 | ("2011-01-01 00:00:00+00",year)
  2 | ("2011-09-01 00:00:00+01",month)
  3 | ("2011-09-23 00:00:00+01",day)

--edit - ein Beispiel für einen Gleichheitsoperator:

create function fuzzytimestamptz_equality(fuzzytimestamptz, fuzzytimestamptz)
                returns boolean language plpgsql immutable as $$
begin
  return ($1.ts, $1.ts+coalesce('1 '||$1.p, '0')::interval)
         overlaps ($2.ts, $2.ts+coalesce('1 '||$2.p, '0')::interval);
end;$$;
--
create operator = ( procedure=fuzzytimestamptz_equality, 
                    leftarg=fuzzytimestamptz, 
                    rightarg=fuzzytimestamptz );

Beispielabfrage:

select *, fuzzy=row(statement_timestamp(), null)::fuzzytimestamptz as equals_now,
          fuzzy=row(statement_timestamp()+'1 day'::interval, null)::fuzzytimestamptz as equals_tomorrow,
          fuzzy=row(date_trunc('month', statement_timestamp()), 'month')::fuzzytimestamptz as equals_fuzzymonth,
          fuzzy=row(date_trunc('month', statement_timestamp()+'1 month'::interval), 'month')::fuzzytimestamptz as equals_fuzzynextmonth
from t;
 id |               fuzzy                | equals_now | equals_tomorrow | equals_fuzzymonth | equals_fuzzynextmonth
----+------------------------------------+------------+-----------------+-------------------+-----------------------
  1 | ("2011-01-01 00:00:00+00",year)    | t          | t               | t                 | t
  2 | ("2011-09-01 00:00:00+01",month)   | t          | t               | t                 | f
  3 | ("2011-09-24 00:00:00+01",day)     | t          | f               | t                 | f
  4 | ("2011-09-24 11:45:23.810589+01",) | f          | f               | t                 | f
Jack sagt, versuchen Sie es mit topanswers.xyz
quelle
Könnten diese Einschränkungen durch Erstellen einer DOMAIN angewendet werden, um zu vermeiden, dass sie in jedem Feld wiederholt werden?
Bignose
Nein, leider nicht, siehe meinen zweiten Kommentar zu Catcalls Antwort. Ich glaube, Sie können einen benutzerdefinierten Konstruktor erstellen, aber das würde Änderungen später nicht verhindern
Jack sagt, versuchen Sie es mit topanswers.xyz