Superar limitação de comprimento de caractere LIKE

13

Ao ler esta limitação de comprimento de caractere LIKE aqui, parece que não consigo enviar um texto com mais de ~ 4000 caracteres em uma cláusula LIKE.

Estou tentando buscar o plano de consulta do cache do plano de consulta para uma consulta específica.

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 a consulta dentro do LIKEarquivo for maior que 4000 caracteres, obterá 0 resultado, mesmo que minha consulta esteja no plano de cache. (Eu estava esperando pelo menos um erro).

Existe uma maneira de solucionar esse problema ou fazer diferente? Tenho consultas que podem ser 10000longas > e parece que não consigo encontrá-las com o LIKE.

Dan Dinu
fonte
2
Quebrar o texto talvez ... desde que você não deve ter muitas consultas exatamente como o outro:where st.text like '%MY_QUERY%CHARS%' ESCAPE '?'
scsimon
4
Você realmente tem textos de consulta idênticos para 4.000 caracteres e depois diferentes?
Martin Smith
@ MartinSmith sim, eu tenho consultas como essa.
Dan Dinu

Respostas:

9

Parece que isso não pode ser resolvido no T-SQL puro, pois CHARINDEXnem PATINDEXpermite o uso de mais de 8000 bytes na string "procurar" (ou seja, no máximo 8000 VARCHARou 4000 NVARCHARcaracteres). Isso pode ser visto nos seguintes testes:

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

Ambas as consultas retornam o seguinte erro:

A mensagem 8152, nível 16, estado 10, linha xxxxx
Seqüência de caracteres ou dados binários seria truncada.

Além disso, reduzir o número 7000de consultas para 3999eliminar o erro. Um valor de 4000ambos os casos também apresentará erro (devido ao N'Z'caractere extra no início).

No entanto, isso pode ser feito usando o SQLCLR. É bastante simples criar uma função escalar que aceite dois parâmetros de entrada do tipo NVARCHAR(MAX).

O exemplo a seguir ilustra essa capacidade usando a versão gratuita do biblioteca SQL # SQLCLR (que eu criei, mas String_Contains está novamente disponível na versão Free :-).

O UDF escalar String_Contains atualmente possui o @SearchValueparâmetro de entrada como em NVARCHAR(4000)vez deNVARCHAR(MAX) (I não deve ter pensado que as pessoas estariam procurando por cordas de mais de 4000 caracteres ;-) mas isso é muito fácil de mudança, fazendo a seguinte alteração de uma só vez (depois de SQL # foi instalado, é claro):

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

CONFIGURAÇÃO

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

TESTES

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

Lembre-se de que String_Contains está usando uma comparação sensível a tudo (maiúsculas e minúsculas, acento, Kana e largura).

Solomon Rutzky
fonte
2

Como você também solicitou abordagens alternativas, outra maneira de encontrar um plano específico é procurá-lo plan_hash, alterando sua consulta da seguinte maneira:

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

A maneira mais rápida que encontrei para obter o QueryHashvalor a pesquisar é colar a consulta em questão em uma janela de consulta e exibir o plano de execução estimado. Leia a saída XML e procure o QueryHashatributo no StmtSimpleelemento, e isso deve fornecer o que você precisa. Conecte o valor QueryHash na consulta acima e esperamos que você tenha o que procura.

Aqui estão algumas capturas de tela mostrando como obter rapidamente o QueryHashvalor, caso eu esteja explicando mal.

Exibir plano de execução estimado

insira a descrição da imagem aqui

Mostrar plano de execução XM ...

insira a descrição da imagem aqui

Pesquisar valor QueryHash

insira a descrição da imagem aqui

Obviamente, o truque não funcionará se a consulta que você procura for diferente da consulta para a qual você está exibindo o Plano de Execução Estimado, mas isso pode ser mais rápido do que todas as nuances que acompanham as rotinas CLR e fazem com que elas funcionem corretamente.

John Eisbrener
fonte
0

Se você tiver acesso aos textos da consulta (o que significa que pode modificá-los), poderá adicionar comentários exclusivos àqueles em que estiver interessado:

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

em seguida, procure myUniqueQuery123no cache do plano em vez do texto inteiro da consulta:

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

PS. Não testado

mustaccio
fonte