sql >> Databasteknik >  >> RDS >> Sqlserver

Hur konverterar man varchar till datum endast när den innehåller ett giltigt datum?

Det typiska svaret är att lägga till en WHERE-sats:

WHERE ISDATE(a.valor) = 1

Detta är dock problematiskt i din situation av ett par anledningar:

  1. ISDATE() kommer inte nödvändigtvis att matcha som du vill beroende på regionala inställningar på servern, användarens språk- eller datumformatalternativ, etc. Till exempel:

    SET DATEFORMAT dmy;
    SELECT ISDATE('13/01/2012'); -- 1
    
    SET DATEFORMAT mdy;
    SELECT ISDATE('13/01/2012'); -- 0
    
  2. Du kan inte riktigt kontrollera att SQL Server kommer att försöka utföra CONVERT efter filtret.

Du kan inte ens använda underfrågor eller CTE för att försöka separera filtret från CONVERT eftersom SQL Server kan optimera operationerna i frågan i vilken ordning den än anser vara effektivare.

Till exempel, med ett begränsat urval, kommer du förmodligen att upptäcka att detta fungerar okej:

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

Men jag har sett fall med även den här konstruktionen där SQL Server har försökt utvärdera filtret först, vilket leder till samma fel som du får för närvarande.

Ett par säkrare lösningar:

Lägg till en beräknad kolumn, t.ex.

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

För att skydda dig mot möjliga feltolkningar vid körning bör du ange datumformat innan du skickar en fråga som refererar till den beräknade kolumnen, t.ex.

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

Skapa en vy:

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;

Återigen, du vill utfärda en SET DATEFORMAT när du frågar vyn.

Använd en temptabell:

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

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

Du kanske fortfarande vill använda DATEFORMAT för att skydda dig från konflikter mellan ISDATE och användarinställningar.

Och nej, du borde inte försök att validera dina strängar som datum med strängmönstermatchning som föreslogs i ett annat (nu borttaget) svar:

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

Du måste ha en ganska komplicerad och hårdhänt validering där för att hantera alla giltiga datum inklusive skottår.



  1. Perl DBI-felmeddelande:Kan inte anropa metoden selectcol_arrayref på ett odefinierat värde

  2. Lös matematiska funktioner PL/SQL

  3. Kan en INNER JOIN erbjuda bättre prestanda än FINNS

  4. Hur man hittar långsammaste frågor