La risposta tipica è aggiungere una clausola WHERE:
WHERE ISDATE(a.valor) = 1
Tuttavia questo è problematico nella tua situazione per un paio di motivi:
-
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
-
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.