Méthode préférée pour stocker DateTime

18

Nous pouvons stocker les informations de date et d'heure de deux manières. Quelle est la meilleure approche pour stocker des informations DateTime?

Stocker la date et l'heure dans 2 colonnes séparées ou une colonne en utilisant DateTime ?

Pouvez-vous expliquer pourquoi cette approche est meilleure?

(Lien vers les documents MySQL pour référence, la question est générale, non spécifique à MySQL)
Types de date et d'heure : Date et heure

julien
la source
3
Cela dépend en grande partie du système de base de données que vous utilisez. Pour ce que ça vaut: Oracle a choisi de le faire en une seule colonne (en tant que type de données DATETIME), auquel cas, l'utilisation de leur support intégré va certainement être supérieure au stockage de ces informations dans 2 colonnes en tant que types de données NUMBER (même si vous besoin de 1 pièce pour une requête donnée ... la date ou l'heure).
Kris Johnston
5
Pour SQL Server, un cas où la répartition peut être préférée est le regroupement par date. Un agrégat de flux pourra être utilisé sans tri pour l'index composite date,time avec group by datemais pas pour un index datetime avec group by cast(datetime as date)même s'il fournira l'ordre souhaité.
Martin Smith
1
Notez que tout calcul sur les valeurs d'heure nécessite de connaître la date et le fuseau horaire - par exemple, la distance entre deux heures dépend du jour ou il contient un événement DST, certains jours ont 23 ou 25 heures et des secondes intercalaires existent également.
Peteris

Réponses:

23

Le stockage des données dans une seule colonne est la méthode préférée, car elles sont inextricablement liées. Un point dans le temps est une seule information, pas deux.

Une façon courante de stocker des données de date / heure, employées "en arrière-plan" par de nombreux produits, est de les convertir en une valeur décimale où la "date" est la partie entière de la valeur décimale et le "temps" est la fraction valeur. Ainsi, 1900-01-01 00:00:00 est stocké en tant que 0.0 et le 20 septembre 2016 9:34:00 est stocké en tant que 42631.39861. 42631 est le nombre de jours depuis le 1900-01-01. .39861 est la partie du temps écoulé depuis minuit. N'utilisez pas de type décimal directement pour ce faire, utilisez un type de date / heure explicite; mon point ici est juste une illustration.

Le stockage des données dans deux colonnes distinctes signifie que vous devrez combiner les deux valeurs de colonne chaque fois que vous voulez voir si un moment donné est antérieur ou postérieur à la valeur stockée.

Si vous stockez les valeurs séparément, vous rencontrerez invariablement des "bogues" difficiles à détecter. Prenez par exemple ce qui suit:

IF OBJECT_ID('tempdb..#DT') IS NOT NULL
DROP TABLE #DT;
CREATE TABLE #DT
(
    dt_value DATETIME NOT NULL
    , d_value DATE NOT NULL
    , t_value TIME(0) NOT NULL
);


DECLARE @d DATETIME = '2016-09-20 09:34:00';

INSERT INTO #DT (dt_value, d_value, t_value)
SELECT @d, CONVERT(DATE, @d), CONVERT(TIME(0), @d);

SET @d = '2016-09-20 11:34:00';

INSERT INTO #DT (dt_value, d_value, t_value)
SELECT @d, CONVERT(DATE, @d), CONVERT(TIME(0), @d);

/* show all rows with a date after 2016-07-01 11:00 am */
SELECT *
FROM #DT dt
WHERE dt.dt_value >= '2016-07-01 11:00:00';

/* show all rows with a date after 2016-07-01 11:00 am */
SELECT *
FROM #DT dt
WHERE dt.d_value >= CONVERT(DATE, '2016-07-01')
    AND dt.t_value >= CONVERT(TIME(0), '11:00:00');

Dans le code ci-dessus, nous créons une table de test, la remplissons avec deux valeurs, puis effectuons une requête simple sur ces données. La première SELECTrenvoie les deux lignes, mais la seconde SELECTne renvoie qu'une seule ligne, ce qui peut ne pas être le résultat souhaité:

entrez la description de l'image ici

La bonne façon de filtrer une plage de date / heure où les valeurs sont dans des colonnes discrètes, comme indiqué par @ypercube dans les commentaires, est:

WHERE dt.d_value > CONVERT(DATE, '2016-07-01') /* note there is no time component here */
    OR (
        dt.d_value = CONVERT(DATE, '2016-07-01') 
        AND dt.t_value >= CONVERT(TIME(0), '11:00:00')
    )

Si vous avez besoin de séparer la composante temporelle à des fins d' analyse , vous pouvez envisager d'ajouter une colonne calculée et persistante pour la partie temporelle de la valeur:

ALTER TABLE #DT
ADD dt_value_time AS CONVERT(TIME(0), dt_value) PERSISTED;

SELECT *
FROM #dt;

entrez la description de l'image ici

La colonne persistante pourrait alors être indexée, ce qui permettrait des tri rapides, etc., par heure de la journée.

Si vous envisagez de diviser la date et l'heure en deux champs à des fins d'affichage, vous devez vous rendre compte que le formatage doit être effectué sur le client, pas sur le serveur.

Max Vernon
la source
11

Je vais donner une opinion dissidente aux autres réponses.

Si les composants de date et d'heure sont requis ensemble, c'est-à-dire qu'une entrée n'est pas valide si elle contient l'un mais pas l'autre (ou est NULL dans l'un mais pas l'autre), alors le stockage dans une seule colonne est logique pour les raisons données dans d'autres réponses.

Cependant, il peut arriver qu'un ou les deux composants soient individuellement optionnels. Dans ce cas, il serait incorrect de le stocker dans une seule colonne. Cela vous obligerait à représenter les valeurs NULL de manière arbitraire, par exemple en stockant l'heure au format 00:00:00.

Voici quelques exemples:

  • Vous enregistrez les trajets en véhicule pour les déductions fiscales de kilométrage. Il serait utile de connaître l'heure exacte du voyage, mais si un employé ne l'a pas noté et l'a oublié, la date doit toujours être enregistrée par elle-même (date requise, heure facultative).

  • Vous menez une enquête pour savoir à quelle heure les gens mangent leur déjeuner et vous demandez aux participants de remplir un formulaire avec un échantillon de leurs heures de déjeuner, y compris les dates. Certains ne prennent pas la peine de remplir la date, et vous ne voulez pas supprimer les données car ce sont les heures qui vous intéressent vraiment (date facultative, heure requise).

Voir cette question connexe pour des approches alternatives.

JBentley
la source
Dans la RFC 3339, il existe une convention pour l'enregistrement d'un "décalage local inconnu". Je ne pense pas que cela couvre tout à fait le cas d'utilisation du "temps inconnu", mais c'est proche. La section suivante "heure locale non qualifiée" est encore plus proche, mais encore une fois ce n'est pas tout à fait suffisant.
geneorama
Oui, je regarde le baril de refactorisation de mon schéma à cause de cela en ce moment. Prenez une situation de location de voiture. Pour récupérer une voiture dans une entreprise de location - l'entreprise doit être ouverte; vous spécifiez donc une date et une heure pour le ramassage. Cependant, beaucoup ont des boîtes de clés; donc vous déposez après les heures. Donc, si l'emplacement est fermé le dimanche; il y a une date de restitution; mais pas un temps. Le stockage d'une valeur 0 (par exemple 12h) ne fonctionnera pas car certains emplacements sont ouverts jusqu'à minuit, ce qui est une valeur valide dans d'autres situations.
Reece
5

Je préfère toujours le stocker dans une seule colonne, sauf s'il existe une demande spécifique pour l'entreprise / l'application. Voici mes points -

  • Extraire l'heure de l'horodatage n'est pas un problème
  • Pourquoi ajouter une colonne supplémentaire juste pour le temps si nous pouvons stocker les deux ensemble
  • Pour éviter d'ajouter la date et l'heure à chaque fois que vous interrogez.
Ashwini Mohan
la source
1
@a_horse_with_no_name marque un point ici. Je pense que "Extraire l'horodatage de l'horodatage n'est pas un problème" devrait être reformulé comme "Extraire l' heure de l'horodatage n'est pas un problème" . "Horodatage" signifie généralement à la fois la date et l'heure (et généralement le fuseau horaire).
ypercubeᵀᴹ
Oui, d'accord @ ypercubeᵀᴹ. L'horodatage signifie généralement la date et l'heure. J'ai explicitement mentionné le mot DateTimeStamp, donc tout le monde peut comprendre que nous parlons de la date et de l'heure à la fois. Mais vous avez également raison. Modifié la réponse.
Ashwini Mohan
3

Dans SQL Server, il est préférable de stocker DataTime dans un seul champ. Si vous créez un index sur la colonne DataTime, il peut être utilisé comme recherche de date et comme recherche de date. Par conséquent, si vous devez limiter tous les enregistrements qui existent pour la date spécifique, vous pouvez toujours utiliser l'index sans rien faire de spécial. Si vous devez interroger la partie de l'heure, vous ne pourrez pas utiliser le même index et, par conséquent, si vous avez une analyse de rentabilisation où vous vous souciez plus de l'heure de la journée que de DateTime, vous devez la stocker séparément car vous devrez créer un index dessus et améliorer les performances.

Vladimir Oselsky
la source
1

En effet, c'est dommage qu'il n'y ait pas de type de SGBD standard pour cela (comme INT et VARCHAR sont pour les entiers et les valeurs de chaîne). Les 2 approches de bases de données croisées que j'ai rencontrées jusqu'à présent utilisent des colonnes VARCHAR / CHAR pour stocker les valeurs DataTime sous forme de chaînes formatées selon la norme ISO 8601 (plus pratique, lisible par l'homme) et utilisent BIGINT pour les stocker en tant qu'horodatages POSIX (stockés plus efficace, plus rapide, plus facile à manipuler mathématiquement).

Ivan
la source
2
Oui, c'est timestampce que définit le standard SQL. Stocker des horodatages sous forme de chaînes est un très mauvais conseil
a_horse_with_no_name
0

Après avoir lu un tas de trucs, l'heure UTC Unix dans BIGINT semble être la solution optimale. TZDB timesone ID dans VARCHAR pour le stockage de fuseau horaire en cas de besoin. Quelques arguments:

  1. TIMESTAMP et DATETIME font un tas de conversions gimmicky en arrière-plan qui semblent être complexes et pas claires. Le serveur passe de l'heure locale à l'heure UTC ou à l'heure du serveur et vice-versa, parfois ou non. Un tas de frais généraux cachés pour chaque fonction.

  2. BIGINT (8 Ko) est au moins aussi léger ou plus léger que DECIMAL requis pour le stockage au format xxxxxx.xxxxxx, qui est pratiquement stocké sous la forme de deux INT + quelque chose par MySQL . Et c'est suffisant pour stocker les siècles à venir.

  3. Presque tous les principaux langages de programmation ont des bibliothèques de fonctions standard pour fonctionner avec le temps Unix.

  4. Les opérations mathématiques avec BIGINT devraient être aussi rapides ou plus rapides que toute autre chose sur n'importe quel matériel.

Bien sûr, tout ce qui précède est pertinent pour les grands projets internationaux. Pour quelque chose de petit, aller avec le format par défaut du cadre choisi semble être assez bon.

Arthur Tarasov
la source
2
" faire un tas de conversions gimmicky en arrière-plan qui semblent être ... pas claires " - de quel SGBD parlez-vous? Pour une timestampcolonne, aucune "conversion gimmicky" ne se produit (au niveau de la couche de base de données) et pour timestamp with time zonecela, elle est bien documentée et expliquée dans les manuels (au moins pour Oracle et Postgres)
a_horse_with_no_name
1
"Presque tous les principaux langages de programmation ont des bibliothèques de fonctions standard pour fonctionner avec le temps Unix." Et pourtant, vous jetez toutes les bibliothèques et fonctions sur les dates, les heures et les horodatages que SQL / SGBD ont, avec votre choix d'utiliser bigint ...
ypercubeᵀᴹ