2014-06-26 4 views
1

Je travaille actuellement sur une table dans la base de données Oracle où des valeurs de temps sont stockées au format AM/PM. Par défaut, les heures sont un type de données VARCHAR2 donc je les ai convertis en utilisant la fonction to_date. Ce que je fais est de comparer deux moments différents dans la table et exécuter une requête qui devrait renvoyer les résultats pour tous les temps contenus dans cet intervalle. L'intervalle est acquis en utilisant la condition entre. Voici un exemple:Comparaison des temps d'horloge en SQL (Oracle)

SELECT TIME 
FROM TIME_TABLE 
WHERE to_date(TIME,'HH:MI:SS PM') between 
     to_date('04:00:00 PM', 'HH:MI:SS PM') and 
     to_date('08:00:00 PM', 'HH:MI:SS PM'); 

Le problème est que lorsque vous ne déclarez pas la date en utilisant la fonction to_date, il sera par défaut automatiquement au 1er du mois en cours. Ainsi, si je voulais obtenir tous les résultats de 23h59 à 00h00, j'obtiendrais des résultats incorrects. Fondamentalement, je veux calculer l'intervalle de temps d'une manière où il boucle au lieu de aller seulement dans une direction comme il le fait naturellement. Y a-t-il un moyen d'accomplir cela?

+2

Évidemment, vous ne devriez jamais stocker des dates comme varchar dans votre base de données. Y a-t-il une raison pour laquelle vous n'utilisez pas l'horodatage? – wvdz

+0

@popovitsj Malheureusement, ce n'est pas une base de données que j'ai créée. C'est celui sur lequel je travaille actuellement qui a déjà été établi. Est-ce que l'horodatage utiliserait ce problème? –

+0

pourquoi ne pas simplement le convertir avant de comparer ?? –

Répondre

4

Le plus gros problème est que lorsque vous ajoutez plus de données, les choses deviennent de plus en plus lentes. Il est difficile d'utiliser efficacement un index lorsque vous appelez une fonction sur le champ lui-même. Il devra scanner la table entière pour satisfaire la requête. Il existe trois façons d'éviter cela:

  • Ne stockez pas les dates ou les heures en tant que chaînes. Utilisez le bon type de champ. C'est la meilleure approche.

  • Comparez les chaînes en utilisant d'abord les chaînes. Cela ne fonctionnera que si vous avez une commande lexicographique garantie. Vous pouvez le faire avec des heures de 24 heures, mais pas avec des heures de 12 heures, parce que AM/PM entrave le processus. Comme Barett m'a éclairé dans les commentaires, Oracle a le concept Function-Based Indexes. Vous pouvez en créer un qui pré-calcule les résultats de to_date, de sorte que lorsque vous l'utilisez dans la requête sur laquelle il a un index à travailler. Cependant, il existe plusieurs disadvantages, donc si vous adoptez cette approche, je vous suggère de considérer attentivement les conséquences.

En ce qui concerne la question sur bouclez minuit une valeur de temps seule, l'algorithme général se présente comme suit:

  • Est-ce le début de gamme valeur < = le final valeur de gamme?

    • Si oui, le rendement vrai si la valeur de test est> = début de portée et < la fin de gamme. Sinon, retournez faux.

    • Si non, return true si la valeur de test est> = début de portée ou < la fin de gamme. Sinon, retournez faux.

Il n'y a pas besoin d'utiliser: 59, ou toute autre valeur de la sorte. Cela peut gêner d'autres choses, comme prendre la durée de la plage ou tester une valeur comme: 59.5.

+1

"Il est impossible qu'un index puisse être utilisé efficacement lorsque vous appelez une fonction sur le champ lui-même." Pas vrai; Oracle a des index basés sur des fonctions. Fola aurait juste besoin d'en ajouter un. Certes, cela a encore "odeur de code". – Barett

+0

@Barett - Très intéressant. Je ne savais pas à propos de ceux-là! Lecture [ces docs] (http://docs.oracle.com/cd/E11882_01/appdev.112/e10471/adfns_indexes.htm#ADFNS00505), il semble toujours y avoir beaucoup d'inconvénients cependant. Mais merci pour l'info! –

+1

Mis à jour ma réponse en conséquence. –

0

Vous ne pouvez pas convertir d'un varchar am/pm en 24h varchar et de comparer celui-là avec BETWEEN?

SELECT TIME 
    FROM TIME_TABLE 
WHERE to_char(to_date(TIME,'HH:MI:SS PM'),'HH24:MI:SS') 
     BETWEEN '04:00:00' AND '05:00:00'; 
+0

Malheureusement cela ne change rien. Si vous comparez 23:59 (23:59 PM) à 00:30:00 (12:30) par exemple, vous ne pourrez pas réellement les temps entre ceux puisque le type de caractère peut seulement vérifier entre deux valeurs si la première valeur est plus élevée que l'autre. Puisque 23 est inférieur à 00, vous n'obtiendrez jamais rien pour un résultat. –

+0

Maintenant, j'ai compris ce que vous vouliez dire par "boucles". ENTRE '23: 10 'ET '00: 30' devrait être une clause valide et revenir tout le temps entre 23h10 et 23h59, et entre 00h00 et 00h30, non? –

+0

Exactement. Désolé que ma formulation n'était pas assez claire. –

1

Pas vraiment. Vous devrez ajouter une logique pour détecter une "boucle" dans vos critères intermédiaires. S'il y a une boucle (< startRange fin d'une fourchette), les critères doivent être:

to_char(to_date(TIME,'HH:MI:SS PM'),'HH24:MI:SS') >= '08:00:00' // endRange 
OR to_char(to_date(TIME,'HH:MI:SS PM'),'HH24:MI:SS') <= '04:00:00' // startRange 

Sur une note côté, je réitère la suggestion de wolφi d'utiliser le temps de 24 heures. Sinon, vous aurez aussi des problèmes AM/PM.

0

Vous pouvez tester si la deuxième fois est 'plus tôt' que la première, et l'ajuster au jour suivant si c'est le cas. vous devez ajuster le temps de la table de la même manière. En utilisant des CTE, vous n'avez pas besoin de répéter les valeurs de chaîne et les conversions:

WITH R AS (
    SELECT to_date('11:59:00 PM', 'HH:MI:SS PM') start_t, 
     to_date('12:00:00 AM', 'HH:MI:SS PM') end_t 
    FROM DUAL 
), 
T AS (
    SELECT to_date(TIME,'HH:MI:SS PM') AS TIME 
    FROM TIME_TABLE 
) 
SELECT to_char(TIME, 'HH:MI:SS PM') 
FROM R 
JOIN T 
ON  T.time + case when T.time < R.start_t then 1 else 0 end 
     between R.start_t and 
     R.end_t + case when R.end_t < R.start_t then 1 else 0 end 
ORDER BY T.time + case when T.time < R.start_t then 1 else 0 end; 

Ce qui n'est pas très agréable.

SQL Fiddle demo.

Espérons que c'est une petite table et vous ne verrez pas les problèmes de performance Matt Johnson visé à ...

Vous pouvez éviter cela en générant tous les temps possibles dans votre gamme et en comparant ceux qui ont la table réel chaînes:

WITH R AS (
    SELECT to_date('11:10:00 PM', 'HH:MI:SS PM') start_t, 
     to_date('12:30:00 AM', 'HH:MI:SS PM') end_t 
    FROM DUAL 
), 
T AS (
    SELECT level AS rn, 
     to_char(start_t 
      + numtodsinterval(level - 1, 'SECOND'), 
      'HH:MI:SS PM') AS time 
    FROM R 
    CONNECT BY level <= 86400 * 
    (end_t + case when end_t < start_t then 1 else 0 end - start_t) 
) 
SELECT TT.time 
FROM T 
JOIN TIME_TABLE TT 
ON  TT.time = T.time 
ORDER BY T.rn; 

SQL Fiddle.

Le premier CTE s'arrête juste de répéter la chaîne. La seconde utilise la première comme base et utilise un CONNECT BY pour trouver tous les temps intermédiaires, en ajoutant le nombre de secondes entre le début et les secondes chaînes (ajustées). Cela revient au lendemain, mais cela n'a pas d'importance car il est reconverti en une chaîne et perd la partie date; et cette chaîne est ensuite utilisée comme filtre pour votre table réelle.

Vous auriez les mêmes problèmes avec l'heure de 24 heures, car il n'y a toujours pas de partie date, mais cela pourrait rendre la logique un peu plus facile à suivre. Il y a si longtemps que j'ai fait n'importe quoi avec des heures de 12 heures qu'il est très difficile de taper '12' pour minuit ...