Supera la LUNGA limitazione della lunghezza dei caratteri


13

Leggendo questa limitazione della lunghezza dei caratteri LIKE qui, sembra che non riesca a inviare un testo più lungo di ~ 4000 caratteri in una clausola LIKE.

Sto cercando di recuperare il piano di query dalla cache del piano di query per una query specifica.

SELECT *
FROM sys.dm_exec_cached_plans AS cp 
CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) AS qp 
CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) AS st
where st.text like '%MY_QUERY_LONGER_THAN_4000_CHARS%' ESCAPE '?'

se la query all'interno di the LIKEè più lunga di 4000 caratteri, ottengo 0 risultati anche se la mia query è nel piano della cache. (Mi aspettavo almeno un errore).

C'è un modo per aggirare il problema o farlo diversamente? Ho delle domande che possono essere 10000lunghe > caratteri e sembra che non riesca a trovarle con il LIKE.


2
Rompi il testo forse ... dato che non dovresti avere molte domande esattamente l'una dell'altra:where st.text like '%MY_QUERY%CHARS%' ESCAPE '?'
scsimon

4
In realtà hai testi di query identici per 4.000 caratteri e quindi diversi?
Martin Smith,

@MartinSmith sì, ho domande del genere.
Dan Dinu,

Risposte:


9

Non sembra che ciò possa essere risolto in T-SQL puro poiché CHARINDEXPATINDEXconsentire né utilizzare più di 8000 byte nella stringa "cercare" (ovvero un massimo di 8000 VARCHARo 4000 NVARCHARcaratteri). Questo può essere visto nei seguenti test:

SELECT 1 WHERE CHARINDEX(N'Z' + REPLICATE(CONVERT(NVARCHAR(MAX), N'a'), 7000),
                         N'Z' + REPLICATE(CONVERT(NVARCHAR(MAX), N'a'), 6000)) > 0

SELECT 1 WHERE PATINDEX(N'Z' + REPLICATE(CONVERT(NVARCHAR(MAX), N'a'), 7000),
                        N'Z' + REPLICATE(CONVERT(NVARCHAR(MAX), N'a'), 6000)) > 0

Entrambe queste query restituiscono il seguente errore:

Messaggio 8152, livello 16, stato 10, riga xxxxx
Dati troncati o binari verrebbero troncati.

E, riducendo l'una 7000o l' altra di quelle query fino a 3999eliminare l'errore. In 4000entrambi i casi verrà inoltre visualizzato un errore (a causa del N'Z'carattere aggiuntivo all'inizio).

TUTTAVIA, ciò può essere realizzato usando SQLCLR. È abbastanza semplice creare una funzione scalare che accetta due parametri di input di tipoNVARCHAR(MAX) .

L'esempio seguente illustra questa capacità usando la versione gratuita della libreria SQL # SQLCLR (che ho creato, ma String_Contains è di nuovo disponibile nella versione gratuita :-).

L' UDF scalare String_Contains attualmente ha il @SearchValueparametro di input come NVARCHAR(4000)invece di NVARCHAR(MAX)(non devo aver pensato che le persone sarebbero alla ricerca di stringhe di oltre 4000 caratteri ;-) ma è molto facile da cambiare apportando la seguente modifica una tantum (dopo SQL # è stato installato, ovviamente):

GO
ALTER FUNCTION [SQL#].[String_Contains](@StringValue [NVARCHAR](MAX),
                                        @SearchValue [NVARCHAR](MAX))
RETURNS [BIT]
WITH EXECUTE AS CALLER
AS EXTERNAL NAME [SQL#].[STRING].[Contains];
GO

IMPOSTARE

-- DROP TABLE #ContainsData;
CREATE TABLE #ContainsData
(
  ContainsDataID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
  Col1 NVARCHAR(MAX) NOT NULL
);

INSERT INTO #ContainsData ([Col1])
VALUES (N'Q' + REPLICATE(CONVERT(NVARCHAR(MAX), N'a'), 15000)),
       (N'W' + REPLICATE(CONVERT(NVARCHAR(MAX), N'a'), 20000)),
       (N'Z' + REPLICATE(CONVERT(NVARCHAR(MAX), N'a'), 70000));

-- verify the lengths being over 8000
SELECT tmp.[ContainsDataID], tmp.[Col1], DATALENGTH(tmp.[Col1])
FROM   #ContainsData tmp;

PROVE

SELECT tmp.[ContainsDataID], tmp.[Col1], DATALENGTH(tmp.[Col1])
FROM   #ContainsData tmp
WHERE  SQL#.String_Contains(tmp.[Col1], REPLICATE(CONVERT(NVARCHAR(MAX), N'a'), 15100)) = 1;
-- IDs returned: 2 and 3

SELECT tmp.[ContainsDataID], tmp.[Col1], DATALENGTH(tmp.[Col1])
FROM   #ContainsData tmp
WHERE  SQL#.String_Contains(tmp.[Col1], REPLICATE(CONVERT(NVARCHAR(MAX), N'a'), 26100)) = 1;
-- IDs returned: 3

Tieni presente che String_Contains sta utilizzando un confronto tutto sensibile (maiuscolo / minuscolo, accento, Kana e larghezza).


2

Poiché hai anche chiesto approcci alternativi, un altro modo per trovare un piano specifico è cercarlo plan_hash, modificando la query come segue:

SELECT *
FROM sys.dm_exec_cached_plans AS cp 
INNER JOIN sys.dm_exec_query_stats qs
    ON cp.plan_handle = qs.plan_handle
CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) AS qp 
CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) AS st
WHERE qs.query_hash = 0xE4026347B5F49802

Il modo più rapido che ho trovato per ottenere il QueryHashvalore da cercare è incollare la query in questione in una finestra di query e quindi visualizzare il piano di esecuzione stimato. Leggi l'output XML e cerca l' QueryHashattributo StmtSimplenell'elemento e questo dovrebbe darti ciò di cui hai bisogno. Inserisci il valore QueryHash nella query sopra e spero che tu abbia quello che stai cercando.

Ecco alcuni screenshot che mostrano come ottenere rapidamente il QueryHashvalore nel caso in cui lo spieghi male.

Visualizza piano di esecuzione stimato

inserisci qui la descrizione dell'immagine

Mostra piano di esecuzione XM ...

inserisci qui la descrizione dell'immagine

Cerca valore QueryHash

inserisci qui la descrizione dell'immagine

Ovviamente il trucco non funzionerà se la query che stai cercando differisce dalla query per la quale stai visualizzando il Piano di esecuzione stimato, ma questo potrebbe essere più veloce di tutte le sfumature che derivano dalle routine CLR e farle funzionare correttamente.


0

Se hai accesso ai testi delle query (nel senso che puoi modificarli), puoi aggiungere commenti unici a quelli a cui sei interessato:

select /* myUniqueQuery123 */ whatever from somewhere ...

quindi cerca myUniqueQuery123nella cache del piano anziché nell'intero testo della query:

... where st.text like '%myUniqueQuery123%'

PS. Non testato

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.