Je passe beaucoup de temps à répondre aux questions SQL sur SO. Je rencontre fréquemment des requêtes de cet acabit:
SELECT * FROM person WHERE birthdate BETWEEN '01/01/2017' AND '01/03/2017'
SELECT * FROM person WHERE birthdate BETWEEN '2017-01-01' AND '2017-03-01'
SELECT * FROM person WHERE birthdate BETWEEN 'some string' AND 'other string'
c'est-à-dire soit en s'appuyant sur une conversion implicite de chaîne en date (mauvaise), des paramètres donnés, soit en se basant sur la base de données convertissant x millions de valeurs de ligne de base de données en chaîne et effectuant une comparaison de chaîne (pire)
Je fais parfois un commentaire, en particulier si c'est un utilisateur de haut niveau qui écrit une réponse intelligente, mais que je pense vraiment devrait être moins bâclé / tapé avec leurs types de données
Le commentaire prend généralement la forme qu'il serait probablement préférable de convertir explicitement leurs chaînes en dates, en utilisant to_date (Oracle), str_to_date (MySQL), convert (SQLSERVER) ou un mécanisme similaire:
--oracle
SELECT * FROM person WHERE birthdate BETWEEN TO_DATE('20170101', 'YYYYMMDD') AND TO_DATE('20170301', 'YYYYMMDD')
--mysql
SELECT * FROM person WHERE birthdate BETWEEN STR_TO_DATE('20170101', '%Y%m%d') AND STR_TO_DATE('20170301', '%Y%m%d')
--SQLS, ugh; magic numbers
SELECT * FROM person WHERE birthdate BETWEEN CONVERT(datetime, '20170101', 112) AND CONVERT(datetime, '20170301', 112)
Ma justification technique pour ce faire est qu'elle est explicite quant au format de la date et garantit que les quelques paramètres source deviennent définitivement le type de données de la colonne cible. Cela empêche toute possibilité que la base de données obtienne une conversion implicite incorrecte (l'argument du 3 janvier / 1er mars du tout premier exemple) et empêche la base de données de décider de convertir un million de valeurs de date dans la table en chaînes (en utilisant une date spécifique au serveur formatage qui pourrait même ne pas correspondre au format de la date dans les paramètres de chaîne dans le sql) afin de faire la comparaison - les horreurs abondent
Ma justification sociale / académique pour le faire est que SO est un site d'apprentissage; les gens y acquièrent des connaissances implicitement ou explicitement. Pour frapper un débutant avec cette requête comme réponse:
SELECT * FROM person WHERE birthdate BETWEEN '2017-01-01' AND '2017-03-01'
Cela pourrait les amener à penser que cela est raisonnable, en ajustant la date pour un format qu'ils préfèrent:
SELECT * FROM person WHERE birthdate BETWEEN '01/01/2017' AND '01/03/2017'
S'ils ont au moins vu une tentative explicite de convertir la date, ils pourraient commencer à le faire pour leur format de date étrange, et tuer des bugs pour toujours avant qu'ils ne surviennent. Après tout, nous (j') essayons de dissuader les gens de prendre l'habitude d'injection SQL (et est-ce que quelqu'un préconiserait de paramétrer une requête puis de déclarer au pilote qu'il @pBirthdate
s'agit d'une chaîne, lorsque le frontend a un type datetime?)
Revenons à ce qui se passe après avoir fait ma recommandation: j'obtiens généralement une réponse à la recommandation "soyez explicite, utilisez x", comme "tout le monde le fait", "ça marche toujours pour moi", "montrez-moi un manuel ou un document de référence qui dit que je devrais être explicite "ou même" quoi ?? "
J'ai demandé, en réponse à certains d'entre eux, s'ils rechercheraient une colonne int en faisant WHERE age = '99'
passer l'âge comme une chaîne. "Ne sois pas stupide, nous n'avons pas besoin de mettre" lors de la recherche int "vient la réponse, donc il y a une certaine appréciation pour différents types de données dans leur esprit quelque part, mais peut-être juste aucun lien avec le saut logique que la recherche d'un int colonne en passant une chaîne (apparemment idiot) et la recherche d'une colonne de date en passant une chaîne (apparemment sensible) est de l'hypocrisie
Donc, dans nos SQL, nous avons un moyen d'écrire des choses sous forme de nombres (utilisez des chiffres, sans délimiteurs), des choses sous forme de chaînes de caractères (utilisez n'importe quoi entre les délimiteurs d'apostrophe). Pourquoi pas de délimiteurs pour les dates? C'est un type de données fondamental dans la plupart des bases de données? Est-ce que tout cela pourrait être résolu simplement en ayant un moyen d'écrire une date de la même manière que javascript nous permet de spécifier une expression régulière en mettant de /
chaque côté de certains caractères. /Hello\s+world/
. Pourquoi ne pas avoir quelque chose pour les dates?
En fait, à ma connaissance, (uniquement) Microsoft Access a en fait des symboles qui indiquent "une date a été écrite entre ces délimiteurs" afin que nous puissions obtenir un bon raccourci comme WHERE datecolumn = #somedate#
mais la présentation de la date est toujours susceptible de poser des problèmes, par exemple mm / di vs dd / mm, parce que les MS ont toujours joué vite et avec les trucs que la foule VB pensait être une bonne idée
Revenons au point principal: je soutiens qu'il est sage d'être explicite avec ce médium qui nous oblige à passer une multitude de types de données différents sous forme de chaînes.
Est-ce une affirmation valable?
Dois-je continuer cette croisade? Est-ce un point valable que la saisie en chaîne est un non-non moderne? Ou est-ce que tous les SGBDR (y compris les versions anciennes) là-bas, lorsque poussé une requête, WHERE datecolumn = 'string value'
convertiront certainement correctement la chaîne en une date et effectueront la recherche sans convertir les données de table / perdre l'utilisation des index? Je soupçonne que non, du moins par expérience personnelle d'Oracle 9. Je soupçonne également qu'il peut y avoir des scénarios d'évasion si les chaînes sont toujours écrites dans un certain format standard ISO, et la colonne est une saveur de date, puis le Le paramètre de chaîne sera toujours correctement implicitement converti. Est-ce que cela le rend juste?
Est-ce une tâche valable?
Beaucoup de gens ne semblent pas comprendre, ou s'en moquent, ou présentent une certaine hypocrisie dans la mesure où leurs ints sont des ints mais leurs dates sont des chaînes. quoi, je suis d'accord avec votre point. Je serai explicite sur mes dates à partir de maintenant ".
la source
WHERE datecolumn =
01/02/12 '' où il est possible qu'ils demandent l'année 1912, 2012, 2001, 1901, 12 ou 1. C'est aussi un problème en dehors du monde de la base de données, le nombre des programmeurs qui ne comprennent pas pourquoi la conversion"09"
en int entraîne un crash sont légion, 9 n'est pas un chiffre octal valide et un 0 de tête rend la chaîne octale dans de nombreux systèmesWHERE age = '0x0F'
c'est un moyen valable d'espérer qu'une base de données recherchera des jeunes de quinze ans.Réponses:
Tu as écrit:
C'est en effet une source potentielle d'erreurs. Le signaler à un demandeur peut être utile à d'autres lecteurs, alors oui, c'est une préoccupation valable. Cependant, pour être constructif, je voudrais
se référer à ANSI SQL et utiliser les littéraux DATE ou DATETIME de cette norme
utiliser le format datetime habituel et sans ambiguïté d'un SGBD spécifique (et mentionner le dialecte SQL utilisé)
Malheureusement, tous les SGBD ne prennent pas en charge les littéraux de date ANSI SQL exactement de la même manière (s'ils le prennent en charge), donc cela conduira généralement à une variante de la deuxième approche. Le fait que "la norme" ne soit pas implémentée de manière rigide par différents fournisseurs de bases de données fait probablement partie du problème ici.
Notez en outre que, pour de nombreux systèmes du monde réel, les gens peuvent réellement compter sur un environnement local fixe spécifique sur le serveur de base de données, même si les applications clientes sont localisées, car il n'y a qu'un seul type de serveur, toujours configuré de la même manière. Ainsi, '01 / 03/2017 'peut souvent être supposé avoir le format fixe' jj / mm / aaaa 'ou' mm / jj / aaaa 'pour tout SQL utilisé sur le système spécifique avec lequel ils travaillent. Donc, si quelqu'un vous dit "ça marche toujours pour moi", c'est peut-être une réponse sensée pour son environnement . Si tel est le cas, il est moins utile de discuter de ce sujet.
Parler de "raisons de performances": tant qu'il n'y a pas de problèmes de performances mesurables, il est assez superstitieux de discuter des "problèmes de performances potentiels". Si une base de données effectue un million de conversions de chaînes à jour ou non, peu importe quand la différence de temps n'est que de 1/1000 seconde, et le véritable goulot d'étranglement est le réseau qui fait que la requête dure 10 secondes. Il vaut donc mieux mettre ces préoccupations de côté tant que quelqu'un demande explicitement des considérations de performance.
Je vous dis un secret: je déteste les guerres de religion. Ils ne mènent à rien d'utile. Donc, si des spécifications de date / heure ambigües dans SQL peuvent entraîner des problèmes, mentionnez-les, mais n'essayez pas de forcer les gens à être plus rigides si cela ne leur apporte pas vraiment d'avantages dans leur contexte actuel.
la source
Votre croisade ne résout pas le problème.
Il y a deux problèmes distincts:
conversion de type implicite en SQL
formats de date ambigus comme 05/06/07
Je vois d'où vous venez avec votre croisade, mais je ne pense pas que la conversion explicite résout réellement le problème actuel:
La conversion implicite se produit toujours en cas de non-correspondance entre les types dans une comparaison. Si une chaîne est comparée à une date, SQL tentera de convertir d'abord la chaîne en date. Ainsi, la comparaison d'une colonne de type date à une valeur de date convertie explicitement est exactement la même chose que la comparaison à une date au format chaîne. La seule différence que je vois est que si vous comparez une valeur de date à une colonne qui ne contient pas réellement de dates mais des chaînes - mais ce serait une erreur dans tous les cas.
L'utilisation d'une conversion explicite ne résout pas l'ambiguïté dans les formats de date non ISO.
La seule solution que je vois:
Et bien sûr, ne stockez jamais les dates dans une colonne de type chaîne. Mais encore une fois, la conversion explicite des littéraux de date n'empêchera pas cela.
On peut dire que les conversions implicites étaient une erreur dans SQL, mais étant donné la conception du langage, je ne vois pas l'avantage de la conversion explicite. De toute façon, il n'évitera pas la conversion implicite et ne fera que rendre le code plus difficile à lire et à écrire.
la source
D'abord et avant tout, vous avez raison. Les dates ne doivent pas être mises en chaînes. Les moteurs de base de données sont des bêtes complexes où vous n'êtes jamais sûr à 100% de ce qui se passera exactement sous le capot, étant donné une requête arbitraire. La conversion en dates rend les choses sans ambiguïté et peut augmenter les performances.
MAIS
Ce n'est pas un problème qui mérite un effort de réflexion supplémentaire à résoudre pour la plupart des gens. S'il était facile d'utiliser des littéraux de date dans une requête, il serait facile de défendre votre position. Mais ce n'est pas le cas. J'utilise principalement SQL Server, donc essayer de me souvenir de ce gâchis pour convertir une date ne se produit tout simplement pas.
Pour la plupart des gens, le gain de performances est négligeable. "Pourquoi oui M. Boss-man, j'ai passé 10 minutes supplémentaires à corriger ce simple bug (j'ai dû chercher comment convertir les dates sur google parce que cette syntaxe est ... spéciale ...). Mais j'ai économisé 0,00001 seconde de plus sur une requête rarement exécutée. " Cela ne va pas voler dans la plupart des endroits où j'ai travaillé.
Mais cela supprime l'ambiguïté dans les formats de date que vous dites. Encore une fois, pour de nombreuses applications (applications internes à l'entreprise, administration locale, etc., etc.), ce n'est pas vraiment un problème. Et pour les applications où il s'agit d'un problème (applications de grande taille, internationales ou d'entreprise), cela devient un problème d'interface utilisateur / de couche métier ou ces entreprises ont déjà une équipe de DBA bien informés qui le savent déjà. TL / DR: si l'internationalisation est une préoccupation, quelqu'un y pense déjà et a déjà fait ce que vous suggérez (ou a autrement atténué le problème).
Et maintenant?
Si vous vous sentez si enclin, continuez à combattre le bon combat. Mais ne soyez pas surpris si la plupart des gens ne pensent pas que c'est suffisamment important pour s'inquiéter. Tout simplement parce qu'il y a des situations où cela compte, cela ne signifie pas que c'est la situation de tout le monde (et ce n'est probablement pas le cas). Ne soyez donc pas surpris lorsque vous obtenez un coup de pouce pour quelque chose qui est techniquement correct et meilleur, mais pas vraiment pertinent.
la source
En supposant que des "dates" sont transmises "dans" les chaînes, alors oui; Je suis absolument d'accord que vous avez raison de le faire.
Quand est le "01/04/07"?
* 4 janvier?
* 1er avril?
* 7 avril [2001]?
Tout ou partie de ces informations peuvent être correctes, selon la façon dont "l'ordinateur" choisit de les interpréter.
Si vous devez créer du SQL dynamique avec des littéraux, votre formatage de date doit être bien défini et, de préférence, indépendant de la machine (j'en avais un étrange sur un serveur Windows où le traitement basé sur la date dans un service Windows a mal tourné car un opérateur s'est connecté à la console avec des préférences de format de date différentes!). Personnellement, j'utilise exclusivement [d] le format "aaaa-mm-jj".
Pourtant ...
La meilleure solution consiste à utiliser des requêtes paramétrées qui forcent le type de données à être converti avant que SQL ne s'implique - obtenir une valeur de "date" dans un paramètre de date force la conversion de type dès le début (ce qui en fait purement un problème de codage, pas SQL) .
la source
WHERE datecolumn = @dateParameter
et ensuite dans le code frontal, en indiquant au pilote de base de données qui@dateParameter
est de type varchar, et en y collant"01/04/07"
. L'inspiration originale de ma question est que je soupçonne que quiconque me dirait que je suis fou de le faire pour une requête paramétrée donnerait alors, dans le même souffle, une réponse SO d'une ligne qui ressemble àWHERE datecol = 'some string that looks like a date'
(et s'attend à ce qu'un débutant sache c'est juste un indice / paramétrez-le pour éviter les problèmes)