web-dev-qa-db-fra.com

Comment stockez-vous des "dates floues" dans une base de données?

C'est un problème que j'ai rencontré plusieurs fois. Imaginez que vous ayez un enregistrement que vous souhaitez stocker dans une table de base de données. Cette table a une colonne DateTime appelée "date_created". Cet enregistrement particulier a été créé il y a longtemps, et vous n'êtes pas vraiment sûr de la date exacte, mais vous connaissez l'année et le mois. D'autres disques que vous connaissez juste l'année. D'autres enregistrements que vous connaissez le jour, le mois et l'année.

Vous ne pouvez pas utiliser un champ DateTime, car "mai 1978" n'est pas une date valide. Si vous le divisez en plusieurs colonnes, vous perdez la possibilité d'interroger. Quelqu'un d'autre a-t-il rencontré cela, si oui, comment l'avez-vous géré?

Pour clarifier le système que je construis, c'est un système qui suit les archives. Certains contenus ont été produits il y a longtemps et tout ce que nous savons, c'est "mai 1978". Je pourrais le stocker comme 1er mai 1978, mais seulement avec une certaine manière de dénoter que cette date n'est précise qu'au mois. De cette façon, quelques années plus tard, lorsque je récupère cette archive, je ne suis pas confus lorsque les dates ne correspondent pas.

Pour mes besoins, il est important de différencier "jour inconnu en mai 1978" par "1er mai 1978". De plus, je ne voudrais pas stocker les inconnues à 0, comme "0 mai 1978" car la plupart des systèmes de base de données rejetteront cela comme une valeur de date non valide.

129
nbv4

Stockez toutes les dates dans le champ DATE normal de la base de données et ayez un champ de précision supplémentaire sur la précision du champ DATE.

date_created DATE,
date_created_accuracy INTEGER, 

date_created_accuracy: 1 = date exacte, 2 = mois, 3 = année.

Si votre date est floue (par exemple, mai 1980), enregistrez-la au début de la période (par exemple, le 1er mai 1980). Ou si votre date est exacte à l'année (par exemple 1980), enregistrez-la au 1er janvier. 1980 avec valeur de précision correspondante.

Cette façon peut facilement interroger d'une manière quelque peu naturelle et avoir encore une idée de la précision des dates. Par exemple, cela vous permet de rechercher des dates entre Jan 1st 1980 et Feb 28th 1981, et obtenez des dates floues 1980 et May 1980.

150
Juha Syrjälä

Si vous n'avez pas besoin d'utiliser ce type de données comme informations de date-heure normales, n'importe quel format de chaîne simple suffirait.

Mais si vous devez conserver toutes les fonctionnalités, je peux penser à deux solutions, nécessitant toutes deux des informations supplémentaires stockées dans la base de données:

  1. Créer min date et max date champs, qui ont des valeurs différentes pour les données "incomplètes", mais coïncideront pour des dates précises.
  2. Créez des types pour chaque type de date inexacte (aucun _ 0, date_missing _ 1, month_missing _ 2, year_missing_4, etc. _ afin de pouvoir les combiner). Ajoutez un champ type aux enregistrements et conservez les informations manquantes.
27
superM

Il s'agit vraiment plus d'une définition des exigences que d'un problème technique - ce sur quoi vous devez vous concentrer est "comment pouvons-nous définir les dates dans le passé" et la solution technique coulera.

Les fois où j'ai dû aborder quelque chose comme ça, nous avons généralement:

  • Définissez comment mapper les choses - comme MichaelT le ​​suggère , décidez que tout ce qui est défini comme Mois/Jour devient défini comme minuit le 1er dudit mois. C'est généralement assez bon pour la plupart des cas - si la date exacte était si importante que vous en auriez probablement un enregistrement 35 ans plus tard, non?
  • Déterminez si vous avez besoin de suivre cela - IE, les enregistrements avec des dates de création légèrement composées ont-ils besoin d'un indicateur le disant? Ou est-ce juste un problème de formation des utilisateurs afin que les gens sachent et puissent agir en conséquence.

Parfois, il faut faire quelque chose comme rendre les dates floues - par exemple, une date peut avoir besoin de répondre à une requête pour quoi que ce soit en mai 1978. C'est faisable - il suffit de rendre vos champs create_date 2, les anciens enregistrements obtiennent un 30 jours répartis comme il convient, les nouveaux obtiennent 2 valeurs identiques.

20
Wyatt Barnett

La façon la plus simple d'indiquer si la date est exacte est de créer un champ de précision INT (1) avec NULL par défaut

Si la date est exacte, enregistrez la date et l'heure dans "date_created" et laissez la précision NULL

Si la date n'est précise qu'au mois, enregistrez la date et l'heure au 1er du mois avec la valeur de précision 1

Si la date n'est exacte que pour l'année, date-heure du 1er janvier avec valeur d'exactitude 2

Vous pouvez utiliser différents nombres pour contenir différentes valeurs telles que le premier trimestre, etc.

18
david strachan

Dans le passé, j'ai enregistré des dates avec précision comme date de début et date de fin. Le jour 21 mai 2012 serait représenté comme début = 12 h 00, 21 mai 2012 et fin = 12 h 00, 22 mai 2012. L'année 2012 serait représentée comme début = 12 h, 1er janvier 2012 fin = 12 h, 1er janvier 2013.

Je ne sais pas si je recommanderais cette approche. Lorsque vous affichez les informations à l'utilisateur, vous devez détecter correctement qu'une plage de dates couvre exactement une journée afin d'afficher "le 25 mai" au lieu de deux points de terminaison trop spécifiques (ce qui signifie traiter de l'heure d'été, etc.).

Cependant, lorsque vous n'essayez pas de traduire en humain, la programmation avec les points d'extrémité est beaucoup plus facile qu'avec la précision centre +. Vous ne vous retrouvez pas avec beaucoup de cas. C'est plutôt sympa.

17
Craig Gidney

Pourquoi ne pas stocker deux dates.

Created_After et Created_Before. La sémantique réelle étant "créée le ou après" et "créée le ou avant"

Donc, si vous connaissez la date exacte, Created_After et Created_Before seront la même date.

Si vous savez que c'était la première semaine de mai 2000, alors Created_After = '2000-05-01' et Created_Before = '2000-05-07'.

Si vous ne connaissez que mai 1999, les valeurs seront "1999-05-01" et "1999-05-30".

S'il s'agit de "Summer of '42", les valeurs seraient "1942-06-01" et "1942-08-31".

Ce schéma est simple à interroger avec du SQL normal et assez facile à suivre pour un utilisateur non technique.

Par exemple, pour trouver tous les documents qui pourraient ont été créés en mai 2001:

SELECT * FROM DOCTAB WHERE Created_After < '2001-05-31' And Created_Before > 2001-05-01;

Inversement pour retrouver tous les documents qui ont été définitivement créés en mai 2001:

SELECT * FROM DOCTAB WHERE Created_After > '2001-05-01' And Created_Before < 2001-05-31;
14
James Anderson

ISO 8601 le format date-heure est fourni avec la définition de la durée, par ex.

2012-01-01P1M (lire: 1er janvier 2012, période: 1 mois) est ce qui devrait être "en janvier 2012".

Je l'utiliserais pour stocker les données. Vous aurez peut-être besoin d'un champ de base de données de type String pour ce faire. C'est un sujet différent sur la façon d'effectuer une recherche judicieuse à ce sujet.

10
Matthias Ronge

Une autre option serait de stocker les dates sous forme d'entiers de la forme YYYYMMDD.

  • Vous savez seulement que l'année est 1951: Enregistrez sous 19510000
  • Vous savez que le mois et l'année sont mars 1951: stocker sous 19510300
  • Vous savez que la date complète est le 14 mars 1951: stocker sous 19510314
  • Une date complètement inconnue: stocker sous 0

Avantages

Vous pouvez stocker votre date floue dans un champ au lieu de deux champs de date ou d'une date et d'une précision comme le suggèrent la plupart des autres réponses.

Les requêtes sont toujours faciles:

  • tous les records de l'année 1951 - SELECT * FROM table WHERE thedate>=19510000 and thedate<19520000
  • tous les enregistrements pour mars 1951 - SELECT * FROM table where thedate>=19510300 and thedate<19510400
  • tous les enregistrements pour le 14 mars 1951 - SELECT * FROM table where thedate=19510314

REMARQUES

  • Votre interface graphique aurait besoin d'une GetDateString(int fuzzyDate) qui est assez facile à implémenter.
  • Le tri est facile avec le format int. Vous devez savoir que les dates inconnues viendront en premier. Vous pouvez inverser cela en utilisant 99 Pour le 'padding' au lieu de 00 Pour le mois ou le jour.
3
Rick

Si vous le divisez en plusieurs colonnes, vous perdez la possibilité d'interroger.

Dit qui? Voici ce que vous faites:

  1. Avoir 3 colonnes, Jour, Mois, Année, chacune de type int, et une quatrième colonne de type DateDate DateTime.
  2. Avoir un déclencheur qui utilise les 3 colonnes Jour, Mois, Année pour créer TheDate si TheDate est laissé nul mais qu'un ou plusieurs des champs Jour, Mois, Année ont une valeur.
  3. Avoir un déclencheur qui remplit les champs Jour, Mois et Année lorsque TheDate est fourni, mais ces champs ne le sont pas.

Donc, si je fais un insert comme: insert into thistable (Day, Month, Year) values (-1, 2, 2012); alors TheDate deviendra le 2/1/2013 mais je saurai que c'est vraiment une date indéterminée en 2/2012 en raison du -1 dans le champ Jour.

Si je insert into thistable (TheDate) values ('2/5/2012'); alors le jour sera 5, le mois sera 2 et l'année sera 2012 et comme aucun d'entre eux n'est -1, je saurai que c'est la date exacte.

Je ne perds pas la possibilité d'interroger car le déclencheur d'insertion/mise à jour s'assure que mes 3 champs (jour, mois, année) produisent toujours une valeur DateTime dans TheDate qui peut être interrogée.

3
junk

En règle générale, je les stocke toujours sous forme de dates pour que les requêtes générales soient toujours possibles même si elles sont légèrement moins précises.

S'il est important de connaître la précision que j'ai dans le passé, soit stocké une "fenêtre" de précision soit sous forme de +/- décimale, soit sous forme de recherche (jour, mois, année, etc.). Dans d'autres cas, au lieu de la fenêtre, je stocke simplement la valeur de date d'origine sous forme de chaîne et convertis ce que je peux en une date/heure, éventuellement 1978-05-01 00:00:00 et "mai 1978" pour votre exemple donné.

3
Bill

ISO 8601 spécifie également une syntaxe pour les "dates floues". Le 12 février 2012 à 15h serait "2012-02-12T15" et février 2012 pourrait être simplement "2012-02". Cela s'étend bien en utilisant le tri lexicographique standard:

$ (echo "2013-03"; echo "2013-03"; echo "2012-02-12T15"; echo "2012-02"; echo "2011") | sort
2011
2012
2012-02
2012-02-12T15
2013-03
1
AnAnswer

Voici mon point de vue à ce sujet:

Passer de la date floue à l'objet datetime (qui s'intégrera dans une base de données)

import datetime
import iso8601

def fuzzy_to_datetime(fuzzy):
    flen = len(fuzzy)
    if flen == 4 and fuzzy.isdigit():
        dt = datetime.datetime(year=int(fuzzy), month=1, day=1, microsecond=111111)

    Elif flen == 7:
        y, m = fuzzy.split('-')
        dt = datetime.datetime(year=int(y), month=int(m), day=1, microsecond=222222)

    Elif flen == 10:
        y, m, d = fuzzy.split('-')
        dt = datetime.datetime(year=int(y), month=int(m), day=int(d), microsecond=333333)

    Elif flen >= 19:
        dt = iso8601.parse_date(fuzzy)

    else:
        raise ValueError("Unable to parse fuzzy date: %s" % fuzzy)

    return dt

Et puis une fonction qui prend l'objet datetime et le ramène dans une date floue.

def datetime_to_fuzzy(dt):
    ms = str(dt.microsecond)
    flag1 = ms == '111111'
    flag2 = ms == '222222'
    flag3 = ms == '333333'

    is_first = dt.day == 1
    is_jan1 = dt.month == 1 and is_first

    if flag1 and is_jan1:
        return str(dt.year)

    if flag2 and is_first:
        return dt.strftime("%Y-%m")

    if flag3:
        return dt.strftime("%Y-%m-%d")

    return dt.isoformat()

Et puis un test unitaire. Ai-je raté des cas?

if __name__ == '__main__':
    assert fuzzy_to_datetime('2001').isoformat() == '2001-01-01T00:00:00.111111'
    assert fuzzy_to_datetime('1981-05').isoformat() == '1981-05-01T00:00:00.222222'
    assert fuzzy_to_datetime('2012-02-04').isoformat() == '2012-02-04T00:00:00.333333'
    assert fuzzy_to_datetime('2010-11-11T03:12:03Z').isoformat() == '2010-11-11T03:12:03+00:00'

    exact = datetime.datetime(year=2001, month=1, day=1, microsecond=231)
    assert datetime_to_fuzzy(exact) == exact.isoformat()

    assert datetime_to_fuzzy(datetime.datetime(year=2001, month=1, day=1, microsecond=111111)) == '2001'
    assert datetime_to_fuzzy(datetime.datetime(year=2001, month=3, day=1, microsecond=222222)) == '2001-03'
    assert datetime_to_fuzzy(datetime.datetime(year=2001, month=6, day=6, microsecond=333333)) == '2001-06-06'

    assert datetime_to_fuzzy(fuzzy_to_datetime('2002')) == '2002'
    assert datetime_to_fuzzy(fuzzy_to_datetime('2002-05')) == '2002-05'
    assert datetime_to_fuzzy(fuzzy_to_datetime('2002-02-13')) == '2002-02-13'
    assert datetime_to_fuzzy(fuzzy_to_datetime('2010-11-11T03:12:03.293856+00:00')) == '2010-11-11T03:12:03.293856+00:00'

Il y a un cas d'angle où un événement qui s'est produit précisément à 2001-01-01T00:00:00.333333 mais le système ne sera interprété que comme "2001", mais cela semble très improbable.

0
nbv4

Je travaille pour une maison d'édition qui traite de nombreux livres anciens où nous ne pouvons souvent pas obtenir les dates exactes des choses. Nous avons généralement deux champs pour une entrée de date donnée, la date et un circa booléen:

date date
dateCirca enum('Y', 'N')

Nous utilisons le champ de date pour indiquer la date d'un événement, ou une date qui est "suffisamment proche" dans le cas où nous ne connaissons pas la vraie date. Dans le cas où nous ne connaissons pas la vraie date, nous marquons le champ dateCirca comme Y et donnons une date suffisamment proche, qui est marquée comme "1er", comme

1st March, 2013  // We don't know the day of the month
1st January, 2013  // We don't know the month/day of the year
1st January, 2000  // We don't know the month/day/year, we only know the century
0
user7007

Aperçu

Il existe de nombreuses représentations possibles, et donc des schémas de base de données, pour stocker des dates-heures floues (ou même simplement des dates floues):

  1. Date-heure et code indiquant sa précision ou son exactitude
  2. Date-heure et intervalle où il existe plusieurs possibilités pour représenter un intervalle:
    1. Représenter tous les intervalles comme une quantité entière (ou autre numérique) d'une unité fixe, par ex. jours, minutes, nanosecondes.
    2. Représente un intervalle à la fois comme une quantité entière (ou autre numérique) et un code indiquant ses unités.
  3. Date et heure de début et de fin
  4. Chaîne
  5. Distribution de probabilité:
    1. Quantités décimales ou à virgule flottante pour les paramètres qui spécifient une distribution spécifique dans une famille particulière, par ex. moyenne et écart type d'une distribution normale.
    2. Fonction de distribution de probabilité, par ex. comme un code (de recherche) (potentiellement avec des paramètres de valeurs spécifiques), ou comme une expression dans un langage, un format ou une représentation suffisamment expressifs.

[1], [2] et [3] sont tous (implicitement) des intervalles uniformes, c'est-à-dire un ensemble de points (également) possibles dans le temps.

[4] est la plus expressive, c'est-à-dire lorsqu'elle autorise des phrases ou des phrases écrites possibles (ou au moins arbitrairement longues). Mais c'est aussi le plus difficile à travailler. À la limite, l'IA au niveau humain serait nécessaire pour gérer des valeurs arbitraires. En pratique, la plage de valeurs possibles devrait être sévèrement restreinte, et des valeurs `` structurées '' alternatives seraient probablement préférées pour de nombreuses opérations, par ex. tri, recherche.

[5] est probablement la représentation compacte la plus générale qui soit (quelque peu) pratique.

Intervalles uniformes

Les intervalles uniformes sont le moyen compact le plus simple de représenter un ensemble de valeurs date-heure (possibles).

Pour [1], les parties de la valeur date-heure sont ignorées, c'est-à-dire les parties correspondant à des unités plus fines que la précision ou l'exactitude indiquée; sinon, cela équivaut à [2] et le code de précision/exactitude est équivalent à un intervalle avec les mêmes unités (et une quantité implicite de 1).

[2] et [3] sont expressivement équivalents. [1] est strictement moins expressif que l'un ou l'autre car il existe des intervalles efficaces qui ne peuvent pas être représentés par [1], ex. une date-heure floue équivalente à un intervalle de 12 heures qui s'étend sur une limite de date.

[1] est plus facile à saisir pour les utilisateurs que toute autre représentation et devrait généralement nécessiter (au moins légèrement) moins de saisie. Si des dates-heures peuvent être saisies dans diverses représentations textuelles, par ex. "2013", "2014-3", "2015-5-2", "7/30/2016 11p", "2016-07-31 18:15", la précision ou l'exactitude pourrait également être déduite automatiquement de l'entrée .

L'exactitude ou la précision de [1] est également plus facile à convertir en un formulaire à transmettre aux utilisateurs, par ex. "2015-5 avec précision du mois" à "mai 2015", contre "13 mai 2015 2p, plus ou moins 13,5 jours" (notez que ce dernier ne peut de toute façon pas être représenté par [1]).

Cordes

Pratiquement, les valeurs de chaîne devront être converties en d'autres représentations pour interroger, trier ou comparer autrement plusieurs valeurs. Ainsi, alors que tout langage naturel (humain) écrit est strictement plus expressif que [1], [2], [3] ou [5], nous n'avons pas encore les moyens de gérer bien au-delà des représentations ou formats de texte standard. Étant donné que, c'est probablement la représentation la moins utile en soi.

Un avantage de cette représentation est que les valeurs devraient, dans la pratique, être présentées aux utilisateurs telles quelles et ne nécessitent pas de transformation pour être facilement compréhensibles.

Distributions de probabilité

Les distributions de probabilité généralisent les représentations d'intervalle uniforme [1], [2], [3] et (sans doute) sont équivalentes à la représentation (générale) des chaînes [4].

Un avantage des distributions de probabilité sur les chaînes est que la première est sans ambiguïté.

[5-1] serait approprié pour les valeurs qui sont (principalement) conformes à une distribution existante, par ex. une valeur date-heure produite par un appareil dont les mesures sont connues (ou supposées) conformes à une distribution spécifique.

[5-2] est probablement le meilleur moyen (quelque peu) pratique de de manière compacte représenter des valeurs arbitraires de 'datetime floue'. Bien sûr, la calculabilité des distributions de probabilités spécifiques utilisées est importante et il y a certainement des problèmes intéressants (et peut-être impossibles) à résoudre lors de l'interrogation, du tri ou de la comparaison de différentes valeurs, mais une grande partie de cela est probablement déjà connue ou résolue quelque part dans l'existant la littérature mathématique et statistique est donc définitivement une représentation extrêmement générale et sans ambiguïté.

0
Kenny Evitt