La réponse typique est d'ajouter une clause WHERE :
WHERE ISDATE(a.valor) = 1
Cependant, cela pose problème dans votre situation pour plusieurs raisons :
-
ISDATE()
ne correspondra pas nécessairement à ce que vous souhaitez en fonction des paramètres régionaux du serveur, de la langue de l'utilisateur ou des options de format de date, etc. Par exemple :SET DATEFORMAT dmy; SELECT ISDATE('13/01/2012'); -- 1 SET DATEFORMAT mdy; SELECT ISDATE('13/01/2012'); -- 0
-
Vous ne pouvez pas vraiment contrôler que SQL Server essaiera d'effectuer le
CONVERT
après le filtre.
Vous ne pouvez même pas utiliser de sous-requêtes ou de CTE pour essayer de séparer le filtre du CONVERT car SQL Server peut optimiser les opérations de la requête dans l'ordre qu'il juge le plus efficace.
Par exemple, avec un échantillon limité, vous constaterez probablement que cela fonctionne correctement :
SET DATEFORMAT dmy;
SELECT valor, valor_date FROM (
SELECT valor, valor_date = CONVERT(DATE,
CASE WHEN ISDATE(valor) = 1 THEN valor ELSE NULL END, 103)
FROM dbo.mytable
WHERE ISDATE(valor) = 1
) AS sub WHERE valor_date BETWEEN '01/01/2012' AND '01/03/2012';
Mais j'ai vu des cas avec même cette construction où SQL Server a d'abord essayé d'évaluer le filtre, conduisant à la même erreur que vous obtenez actuellement.
Quelques solutions de contournement plus sûres :
Ajoutez une colonne calculée, par exemple
ALTER TABLE dbo.mytable ADD valor_date
AS CONVERT(DATE, CASE WHEN ISDATE(valor) = 1 THEN valor
ELSE NULL END, 103);
Pour vous protéger d'éventuelles interprétations erronées lors de l'exécution, vous devez spécifier dateformat avant d'émettre une requête faisant référence à la colonne calculée, par exemple
SET DATEFORMAT dmy;
SELECT valor, valor_date FROM dbo.mytable WHERE ...;
Créer une vue :
CREATE VIEW dbo.myview
AS
SELECT valor, valor_date = CONVERT(DATE,
CASE WHEN ISDATE(valor) = 1 THEN valor ELSE NULL END, 103)
FROM dbo.mytable
WHERE ISDATE(valor) = 1;
Encore une fois, vous voudrez émettre un SET DATEFORMAT
lors de l'interrogation de la vue.
Utiliser une table temporaire :
SELECT <cols>
INTO #foo
FROM dbo.mytable
WHERE ISDATE(valor) = 1;
SELECT <cols>, CONVERT(DATE, valor) FROM #foo WHERE ...;
Vous pouvez toujours utiliser DATEFORMAT
pour vous protéger des conflits entre ISDATE
et paramètres utilisateur.
Et non, vous ne devriez pas essayez de valider vos chaînes en tant que dates en utilisant la correspondance de modèle de chaîne comme cela a été suggéré dans une autre réponse (maintenant supprimée) :
like '%__/%' or like '%/%'
Vous devrez y avoir une validation assez complexe et lourde pour gérer toutes les dates valides, y compris les années bissextiles.