Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Wie konvertiert man varchar nur dann in ein Datum, wenn es ein gültiges Datum enthält?

Die typische Antwort besteht darin, eine WHERE-Klausel hinzuzufügen:

WHERE ISDATE(a.valor) = 1

Dies ist jedoch aus mehreren Gründen in Ihrer Situation problematisch:

  1. ISDATE() wird nicht unbedingt Ihren Wünschen entsprechen, abhängig von den regionalen Einstellungen des Servers, der Sprache des Benutzers oder den Datumsformatoptionen usw. Zum Beispiel:

    SET DATEFORMAT dmy;
    SELECT ISDATE('13/01/2012'); -- 1
    
    SET DATEFORMAT mdy;
    SELECT ISDATE('13/01/2012'); -- 0
    
  2. Sie können nicht wirklich steuern, dass SQL Server versucht, CONVERT auszuführen nach dem Filter.

Sie können nicht einmal Unterabfragen oder CTEs verwenden, um zu versuchen, den Filter von CONVERT zu trennen, weil SQL Server das kann Optimieren Sie die Operationen in der Abfrage in einer beliebigen Reihenfolge, die es für effizienter hält.

Mit einem begrenzten Beispiel werden Sie wahrscheinlich feststellen, dass dies gut funktioniert:

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';

Aber ich habe Fälle mit sogar diesem Konstrukt gesehen, wo SQL Server versucht hat, den Filter zuerst auszuwerten, was zu dem gleichen Fehler führte, den Sie gerade bekommen.

Ein paar sicherere Problemumgehungen:

Fügen Sie eine berechnete Spalte hinzu, z. B.

ALTER TABLE dbo.mytable ADD valor_date
  AS CONVERT(DATE, CASE WHEN ISDATE(valor) = 1 THEN valor 
    ELSE NULL END, 103);

Um sich vor möglichen Fehlinterpretationen zur Laufzeit zu schützen, sollten Sie dateformat angeben, bevor Sie eine Abfrage absetzen, die auf die berechnete Spalte verweist, z. B.

SET DATEFORMAT dmy;
SELECT valor, valor_date FROM dbo.mytable WHERE ...;

Erstellen Sie eine Ansicht:

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;

Auch hier sollten Sie ein SET DATEFORMAT ausgeben beim Abfragen der Ansicht.

Verwenden Sie eine temporäre Tabelle:

SELECT <cols>
INTO #foo
FROM dbo.mytable
WHERE ISDATE(valor) = 1;

SELECT <cols>, CONVERT(DATE, valor) FROM #foo WHERE ...;

Vielleicht möchten Sie trotzdem DATEFORMAT verwenden um sich vor Konflikten zwischen ISDATE zu schützen und Benutzereinstellungen.

Und nein, das sollten Sie nicht Versuchen Sie, Ihre Zeichenfolgen als Datumsangaben zu validieren, indem Sie den Zeichenfolgenmusterabgleich verwenden, wie in einer anderen (jetzt gelöschten) Antwort vorgeschlagen:

like '%__/%' or like '%/%'

Sie müssen dort eine ziemlich komplexe und schwerfällige Validierung durchführen, um alle gültigen Daten einschließlich Schaltjahre zu verarbeiten.