Superar la limitación de longitud de caracteres LIKE

13

Al leer esta limitación de longitud de caracteres LIKE aquí, parece que no puedo enviar un texto de más de ~ 4000 caracteres en una cláusula LIKE.

Estoy tratando de obtener el plan de consulta del caché del plan de consulta para una consulta en particular.

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 '?'

si la consulta dentro de the LIKEes más larga que 4000 caracteres, entonces obtengo 0 resultados incluso si mi consulta está en el plan de caché. (Esperaba al menos un error).

¿Hay alguna forma de solucionar este problema o hacerlo de manera diferente? Tengo consultas que pueden ser> 10000caracteres largos y parece que no puedo encontrarlas con el LIKE.

Dan Dinu
fuente
2
Romper el texto tal vez ... ya que no debería tener muchas consultas tal y como entre sí:where st.text like '%MY_QUERY%CHARS%' ESCAPE '?'
scsimon
44
¿Realmente tiene textos de consulta que son idénticos para 4,000 caracteres y luego difieren?
Martin Smith
@ MartinSmith sí, tengo consultas como esa.
Dan Dinu

Respuestas:

9

No parece que esto pueda resolverse en T-SQL puro ya que CHARINDEXni PATINDEXpermite usar más de 8000 bytes en la cadena "para buscar" (es decir, un máximo de 8000 VARCHARo 4000 NVARCHARcaracteres). Esto se puede ver en las siguientes pruebas:

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 consultas devuelven el siguiente error:

Msg 8152, Nivel 16, Estado 10, Línea xxxxx La
cadena o los datos binarios se truncarían.

Y, al reducir la consulta 7000en cualquiera de esas consultas, 3999se elimina el error. Un valor de 4000en ambos casos también producirá un error (debido al N'Z'carácter adicional al principio).

SIN EMBARGO, esto se puede lograr usando SQLCLR. Es bastante simple crear una función escalar que acepte dos parámetros de entrada de tipo NVARCHAR(MAX).

El siguiente ejemplo ilustra esta capacidad usando la versión gratuita de la biblioteca SQL # SQLCLR (que creé, pero String_Contains está nuevamente disponible en la versión gratuita :-).

Los String_Contains escalares UDF tiene actualmente el @SearchValueparámetro de entrada como NVARCHAR(4000)en lugar de NVARCHAR(MAX)(No debe haber pensado que la gente estaría buscando para cadenas de más de 4000 caracteres ;-), pero que es muy fácil cambiar al hacer el siguiente cambio de una sola vez (después de SQL # ha sido instalado, por supuesto):

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

PREPARAR

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

PRUEBAS

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

Tenga en cuenta que String_Contains está utilizando una comparación sensible a todo (mayúsculas, acento, Kana y ancho).

Solomon Rutzky
fuente
2

Debido a que también solicitó enfoques alternativos, otra forma de encontrar un plan específico es buscarlo plan_hash, alterando su consulta de la siguiente manera:

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

La forma más rápida que he encontrado para obtener el QueryHashvalor para buscar es pegar la consulta en cuestión en una ventana de consulta y luego mostrar el plan de ejecución estimado. Lea la salida XML y busque el QueryHashatributo en el StmtSimpleelemento y esto debería darle lo que necesita. Conecte el valor de QueryHash en la consulta anterior y esperemos que tenga lo que está buscando.

Aquí hay algunas capturas de pantalla que muestran cómo obtener rápidamente el QueryHashvalor en caso de que lo explique mal.

Visualizar plan de ejecución estimado

ingrese la descripción de la imagen aquí

Mostrar plan de ejecución XM ...

ingrese la descripción de la imagen aquí

Buscar valor de QueryHash

ingrese la descripción de la imagen aquí

Obviamente, el truco no funcionará si la consulta que está buscando difiere de la consulta para la que está mostrando el Plan de ejecución estimado, pero esto puede ser más rápido que todos los matices que vienen con las rutinas CLR y hacer que funcionen correctamente.

John Eisbrener
fuente
0

Si tiene acceso a los textos de consulta (lo que significa que puede modificarlos), puede agregar comentarios únicos a aquellos que le interesan:

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

luego busque myUniqueQuery123en la caché del plan en lugar del texto completo de la consulta:

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

PD. No probado

mustaccio
fuente