2016-02-01 12 views
5

Ecco un problema che ho più volte incontrato mentre gioca con la Stack Exchange Data Explorer, che si basa su T-SQL:modello T-SQL corrispondenza con le eccezioni

Come cercare una stringa tranne quando si verifica come sottostringa di qualche altra stringa?

Per esempio, come posso selezionare tutti i record di una tabella MyTable dove la colonna MyCol contiene la stringa foo, ma ignorando qualsiasi foo s che fanno parte della stringa foobar?

Un tentativo di rapido e sporco sarebbe qualcosa di simile:

SELECT * 
FROM MyTable 
WHERE MyCol LIKE '%foo%' 
    AND MyCol NOT LIKE '%foobar%' 

ma ovviamente questo non riuscirà a corrispondere per esempio MyCol = 'not all foos are foobars', che voglio abbinare.

Una soluzione che è venuta in mente è quello di sostituire tutte le occorrenze di foobar con alcuni marcatore fittizio (che non è una stringa di foo) e poi la verifica di eventuali rimanenti foo s, come in:

SELECT * 
FROM MyTable 
WHERE REPLACE(MyCol, 'foobar', 'X') LIKE '%foo%' 

Funziona, ma sospetto che non sia molto efficiente, dal momento che deve eseguire il REPLACE() su ogni record nella tabella. (Per SEDE, si tratta in genere della tabella Posts, che attualmente ha circa 30 milioni di righe.) I modi migliori per farlo?

(FWIW, the real use case che ha spinto a questa domanda era alla ricerca di SO messaggi con URL di immagine che utilizzano il prefisso http:// schema, ma non puntano all'host i.stack.imgur.com.)

+0

Stai giocando con la versione hosted, o stai scaricando i dati con cui giocare sul sistema locale? Se si sta eseguendo il download localmente o si è in grado di farlo se non si è a conoscenza di un'opzione, è possibile aggiungere la funzionalità RegEx tramite SQLCLR. Ad esempio, è possibile scaricare la libreria [SQL #] (http://SQLsharp.com/) (che ho scritto, ma la roba RegEx è nella versione gratuita), installarla in un DB 'Utility' e quindi utilizzare in query per questa o altre cose :-). –

+0

@srutzky: sto utilizzando il DB ospitato. Suppongo che potrei esaminare il download dei dati, ma sarebbe preferibile una soluzione che funziona online. –

risposta

5

Nessuno dei due le modalità fornite finora sono garantite per funzionare come pubblicizzato e eseguono solo lo REPLACE su un sottoinsieme di righe.

SQL Server does not guarantee short circuiting of predicates e can move compute scalars up into the underlying query for derived tables and CTEs.

L'unica cosa che è (mostly) garantita per funzionare è la dichiarazione CASE. Qui di seguito ho utilizzare lo zucchero varietà sintattico di IIF che si espande verso CASE

SELECT * 
FROM MyTable 
WHERE 1 = IIF(MyCol LIKE '%foo%', 
       IIF(REPLACE(MyCol, 'foobar', 'X') LIKE '%foo%', 1, 0), 
       0); 
1

Un filtro a tre stadi dovrebbe funzionare:

  1. raccogliere tutte le righe corrispondenti a '% pippo%';

  2. sostituire tutte le occorrenze di 'foobar' con una stringa non ricorrente (come '' forse ');

  3. Controllare ancora per corrispondenza '% pippo%'

Qui si esegue solo il Sostituisci file potenzialmente corrispondenti, non tutte le righe. Se ti aspetti solo una piccola percentuale di partite, questo dovrebbe essere molto più efficiente.

SQL sarebbe simile a questa:

;with data as (
    select * 
    from MyTable 
    where MyCol like '%foo%'  
) 
select * 
from data 
where replace(MyCol, 'foobar', 'X') like '%foo%' 

Si noti che è necessario un sub-query, come non ci sono espressione scorciatoie in SQL; il motore è libero di riordinare i termini booleani come desiderato per un'elaborazione efficiente all'interno di un singolo livello di query.

+0

fare tutto questo nello stesso SELECT sarà altrettanto veloce –

+0

@ t-clausen.dk: SQL non ha cortocircuito delle espressioni booleane. L'unico modo per *** *** di garantire che Replace e test vengano eseguiti solo sulle righe che stanno già superando il primo test è quello di nidificare la query. Non si può giudicare da un particolare piano di esecuzione. –

+0

@PieterGeerkens - Anche questo non garantisce nulla. –

0

Supponendo che siete interessati solo a trovare le istanze di foo con spazi che li circonda

SELECT * 
FROM MyTable 
WHERE MyCol LIKE 'foo %' OR MyCol LIKE '% foo %' OR MyCol LIKE '% foo' 
+0

Purtroppo, questo non corrisponderà, ad es. ''un fooing fooer foos the foos'', che voglio abbinare. Consentitemi di aggiornare il mio esempio nella domanda. –

+0

Quindi vuoi abbinare il fooing, il fooer e il foos ma non il foobar? –

+0

Sì. Il vero caso d'uso che ha spinto questa domanda è stato in realtà trovare post con URL di immagine che usano il prefisso dello schema 'http: //' ma non * puntano * sull'host 'i.stack.imgur.com'. –

1

Questo sarà il più veloce di query corrente:

SELECT * 
FROM MyTable 
WHERE 
    MyCol like '%foo%' AND 
    REPLACE(MyCol, 'foobar', 'X') LIKE '%foo%' 

Sostituisci viene calcolata dopo MyCol è stata applicato, quindi è più veloce di:

REPLACE(MyCol, 'foobar', 'X') LIKE '%foo%' 
Problemi correlati