Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Comment convertir varchar en date uniquement lorsqu'il contient une date valide?

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 :

  1. 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
    
  2. 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.