Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Come convertire varchar in data solo quando contiene una data valida?

La risposta tipica è aggiungere una clausola WHERE:

WHERE ISDATE(a.valor) = 1

Tuttavia questo è problematico nella tua situazione per un paio di motivi:

  1. ISDATE() non corrisponderà necessariamente al modo desiderato a seconda delle impostazioni regionali del server, della lingua dell'utente o delle opzioni di formato della data, ecc. Ad esempio:

    SET DATEFORMAT dmy;
    SELECT ISDATE('13/01/2012'); -- 1
    
    SET DATEFORMAT mdy;
    SELECT ISDATE('13/01/2012'); -- 0
    
  2. Non puoi davvero controllare che SQL Server proverà ad eseguire CONVERT dopo il filtro.

Non puoi nemmeno usare sottoquery o CTE per provare a separare il filtro da CONVERT perché SQL Server può ottimizzare le operazioni nella query nell'ordine che ritiene più efficiente.

Ad esempio, con un campione limitato, probabilmente scoprirai che funziona bene:

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

Ma ho visto casi anche con questo costrutto in cui SQL Server ha provato a valutare prima il filtro, portando allo stesso errore che stai ricevendo attualmente.

Un paio di soluzioni alternative più sicure:

Aggiungi una colonna calcolata, ad es.

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

Per proteggerti da possibili interpretazioni errate in fase di esecuzione, dovresti specificare dateformat prima di eseguire una query che faccia riferimento alla colonna calcolata, ad es.

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

Crea una vista:

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;

Ancora una volta, ti consigliamo di emettere un SET DATEFORMAT quando si interroga la vista.

Usa una tabella temporanea:

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

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

Potresti comunque voler usare DATEFORMAT per proteggerti dai conflitti tra ISDATE e impostazioni utente.

E no, dovresti non prova a convalidare le tue stringhe come date usando la corrispondenza del modello di stringa come suggerito in un'altra risposta (ora eliminata):

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

Dovrai avere una convalida piuttosto complessa e pesante per gestire tutte le date valide, inclusi gli anni bisestili.