2009-08-20 13 views
7

Mi chiedo se esiste una query di buona prestazione per selezionare date distinte (tempi di ignorazione) da una tabella con un campo data/ora in SQL Server.Come selezionare rapidamente le date DISTINCT da un campo Data/Ora, SQL Server

Il mio problema non sta facendo in modo che il server lo faccia (ho già visto this question e avevamo già qualcosa di simile sul posto con DISTINCT). Il problema è se c'è qualche trucco per farlo fare più velocemente. Con i dati che stiamo usando, la nostra query attuale restituisce ~ 80 giorni distinti per i quali ci sono ~ 40.000 righe di dati (dopo il filtraggio su un'altra colonna indicizzata), c'è un indice sulla colonna della data e la query riesce sempre a prendere 5+ secondi. Che è troppo lento

La modifica della struttura del database potrebbe essere un'opzione, ma meno desiderabile.

risposta

6

Ogni opzione che coinvolge la manipolazione CAST o TRUNCATE o DATEPART nel campo datetime ha lo stesso problema: la query deve eseguire la scansione dell'intero resultset (40k) per trovare le date distinte. Le prestazioni possono variare leggermente tra vari implementaitons.

Quello di cui hai veramente bisogno è avere un indice che possa produrre la risposta in un batter d'occhio. È possibile avere una colonna calcolata persistente con e indicizzare che (richiede modifiche alla struttura della tabella) o una vista indicizzata (requires Enterprise Edition for QO to consider the index pronta all'uso).

persistenti In colonna calcolata:

alter table foo add date_only as convert(char(8), [datetimecolumn], 112) persisted; 
create index idx_foo_date_only on foo(date_only); 

indicizzato vista:

create view v_foo_with_date_only 
with schemabinding as 
select id 
    , convert(char(8), [datetimecolumn], 112) as date_only 
from dbo.foo; 
create unique clustered index idx_v_foo on v_foo_with_date_only(date_only, id); 

Aggiorna

Per eliminare completamente i una scansione potrebbe utilizzare un GROUP BY ingannato vista indicizzata, in questo modo:

create view v_foo_with_date_only 
with schemabinding as 
select 
    convert(char(8), [d], 112) as date_only 
    , count_big(*) as [dummy] 
from dbo.foo 
group by convert(char(8), [d], 112) 

create unique clustered index idx_v_foo on v_foo_with_date_only(date_only) 

La query select distinct date_only from foo utilizzerà invece questa vista indicizzata. È ancora una scansione tecnicamente, ma su un indice già "distinto", quindi vengono scansionati solo i record necessari. È un trucco, immagino, non lo consiglierei per il codice di produzione dal vivo.

AFAIK SQL Server non ha la capacità di scansionare un indice vero con ripetizioni saltate, es. cerca in alto, poi cerca più in alto, poi cerca con successo più dell'ultimo trovato.

+0

C'è un modo per usare 'SKIP SCAN' in' SQL Server'? Ho appena provato la tua soluzione su una tabella '2M' e la situazione è peggiorata (' DISTINCT CAST (...) 'in un campo' DATETIME' ha richiesto '850 ms' con un' Hash Match Aggregate', 'DISTINCT date' ha preso '1800 ms' con un' Stream Aggregate'). 'Oracle' e' MySQL' semplicemente saltano sopra i campi distinti nell'indice, 'SQL Server' non lo fa. – Quassnoi

+0

È necessario selezionare date_due distinte dopo la creazione di un indice. –

+0

'@ Remus': ho creato un indice e l'ottimizzatore l'ha usato. – Quassnoi

9

Ho usato il seguente:

CAST(FLOOR(CAST(@date as FLOAT)) as DateTime); 

questo elimina il tempo a partire dalla data convertendolo in un float e troncare via la parte "tempo", che è il decimale del float.

Sembra un po 'goffo ma funziona bene su un set di dati di grandi dimensioni (~ 100.000 righe) che uso ripetutamente per tutto il giorno.

3

Il modo più semplice è quello di aggiungere una colonna calcolata solo per la parte della data e selezionarla. Puoi farlo in una vista se non vuoi cambiare tabella.

2

Aggiornamento:

soluzione qui di seguito testato per l'efficienza su un tavolo e prende 2M ma 40 ms.

Plain DISTINCT su una colonna calcolata indicizzata ha preso 9 seconds.

Vedere questa voce nel mio blog per i dettagli delle prestazioni:


Purtroppo, SQL Server 'ottimizzatore s può fare né di SKIP SCAN Oracle né MySQL' s INDEX FOR GROUP-BY.

È sempre Stream Aggregate che richiede molto tempo.

Puoi costruito una lista di possibili date utilizzando un ricorsivo CTE e unirlo con il tuo tavolo:

WITH rows AS (
     SELECT CAST(CAST(CAST(MIN(date) AS FLOAT) AS INTEGER) AS DATETIME) AS mindate, MAX(date) AS maxdate 
     FROM mytable 
     UNION ALL 
     SELECT mindate + 1, maxdate 
     FROM rows 
     WHERE mindate < maxdate 
     ) 
SELECT mindate 
FROM rows 
WHERE EXISTS 
     (
     SELECT NULL 
     FROM mytable 
     WHERE date >= mindate 
       AND date < mindate + 1 
     ) 
OPTION (MAXRECURSION 0) 

Questo sarà più efficiente rispetto Stream Aggregate

+0

Costruire una tabella di data e quindi semi-unisce a quello originale è un ottima soluzione. IMHO il sovraccarico aggiuntivo di una colonna persistente con un indice o una vista indicizzata ha senso solo se si dovesse eseguire questa operazione molto frequentemente (ipotesi arbitraria: come un paio di centinaia di volte al giorno). Preferirei sempre provare prima a trovare una query migliore piuttosto che aggiungere più complessità/overhead alla struttura del DB. –

0

Se si vuole evitare l'estrazione passo o riformattando la data - che è presumibilmente la causa principale del ritardo (forzando una scansione completa della tabella) - non si ha altra scelta che memorizzare la data solo una parte del datetime, che purtroppo richiederà una modifica alla struttura del database.

Se la vostra utilizzando SQL Server 2005 o versioni successive, allora un campo calcolata persistente è la strada da percorrere

 
Unless otherwise specified, computed columns are virtual columns that are 
not physically stored in the table. Their values are recalculated every 
time they are referenced in a query. The Database Engine uses the PERSISTED 
keyword in the CREATE TABLE and ALTER TABLE statements to physically store 
computed columns in the table. Their values are updated when any columns 
that are part of their calculation change. By marking a computed column as 
PERSISTED, you can create an index on a computed column that is deterministic 
but not precise. 
+1

La causa principale del ritardo è la scansione e l'ordinamento per produrre il distinto. A meno che qualcosa di * extreamly * complesso si verifichi in un'operazione scalare, i ritardi in un database sono sempre correlati all'accesso ai dati, non alle operazioni scalari. –

+0

È la causa principale del ritardo perché impone una scansione completa della tabella: mi spiace, dovrei averlo chiarito. – Cruachan

0

Qual è il suo predicato che altra colonna filtrata? Hai provato se ottieni miglioramenti da un indice su quell'altra colonna filtrata, seguita dal campo datetime?

Sto indovinando molto qui, ma 5 secondi per filtrare un set di forse 100000 righe fino a 40000 e quindi fare un ordinamento (che presumibilmente è quello che succede) non mi sembra un momento irragionevole per me. Perché dici che è troppo lento? Perché non corrisponde alle aspettative?

3

Non sono sicuro del motivo per cui la query esistente richiederebbe oltre 5 secondi per 40.000 righe.

Ho appena provato la seguente query su una tabella con 100.000 righe e ha restituito in meno di 0.1 s.

SELECT DISTINCT DATEADD(day, 0, DATEDIFF(day, 0, your_date_column)) 
FROM your_table 

(Si noti che questa query probabilmente non sarà in grado di approfittare di eventuali indici della colonna della data, ma dovrebbe essere ragionevolmente veloce, supponendo che non si sta eseguendo decine di volte al secondo.)

+0

facile e pulito, questa dovrebbe essere la risposta corretta –

0

solo convertire la data: dateadd(dd,0, datediff(dd,0,[Some_Column]))

1

Ho usato questo

SELECT 
DISTINCT DATE_FORMAT(your_date_column,'%Y-%m-%d') AS date 
FROM ... 
+0

Non sono sicuro efficienza, ma questo è sicuramente il modo più bello per farlo. – ylnor

5

Questo funziona per me:

SELECT distinct(CONVERT(varchar(10), {your date column}, 111)) 
FROM {your table name} 
Problemi correlati