Преодолеть, как ограничение длины символа

13

Прочитав это ограничение длины символа LIKE здесь, похоже, что я не могу отправить текст длиной более ~ 4000 символов в предложении LIKE.

Я пытаюсь получить план запроса из кэша плана запроса для конкретного запроса.

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

если запрос внутри LIKEпеременной длиннее 4000 символов, я получаю 0 результатов, даже если мой запрос находится в плане кэширования. (Я ожидал, по крайней мере, erorr).

Есть ли способ обойти эту проблему или сделать это по-другому? У меня есть запросы, которые могут быть 10000длиной > символов, и похоже, что я не могу найти их с помощью LIKE.

Дан Дину
источник
2
Возможно, where st.text like '%MY_QUERY%CHARS%' ESCAPE '?'
разбейте
4
У вас на самом деле есть тексты запросов, которые идентичны для 4000 символов, а затем отличаются?
Мартин Смит
@MartinSmith да, у меня есть такие запросы.
Дан Дину

Ответы:

9

Похоже, что это не может быть решено в чистом T-SQL, так как не позволяет и CHARINDEXне PATINDEXпозволяет использовать более 8000 байтов в строке «для поиска» (т. Е. Не более 8000 VARCHARили 4000 NVARCHARсимволов). Это можно увидеть в следующих тестах:

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

Оба этих запроса возвращают следующую ошибку:

Сообщение 8152, уровень 16, состояние 10, строка xxxxx
Строка или двоичные данные будут обрезаны.

И, уменьшая 7000в любом из этих запросов, чтобы 3999избавиться от ошибки. Значение 4000в обоих случаях также будет ошибочным (из-за дополнительного N'Z'символа в начале).

ОДНАКО, это может быть достигнуто с помощью SQLCLR. Довольно просто создать скалярную функцию, которая принимает два входных параметра типа NVARCHAR(MAX).

Следующий пример иллюстрирует эту возможность с помощью бесплатной версии библиотеки SQL # SQLCLR (которую я создал, но String_Contains снова доступен в бесплатной версии :-).

String_Contains скалярных UDF в настоящее время имеют @SearchValueвход параметры , как NVARCHAR(4000)вместо того , чтобы NVARCHAR(MAX)(я не должен подумать , люди будут искать для строк более 4000 символов ;-) но это очень легко изменить, сделав следующее изменение одноразового (после SQL # было установлено, конечно):

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

НАСТРОИТЬ

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

ИСПЫТАНИЯ

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

Помните, что String_Contains использует сравнение с учетом всего (регистр, акцент, кана и ширина).

Соломон Руцкий
источник
2

Поскольку вы также просили альтернативные подходы, другой способ найти конкретный план - это найти его plan_hash, изменив ваш запрос следующим образом:

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

Самый быстрый способ найти искомое QueryHashзначение - это вставить нужный запрос в окно запроса, а затем отобразить примерный план выполнения. Прочитайте вывод XML и найдите QueryHashатрибут в StmtSimpleэлементе, и это должно дать вам то, что вам нужно. Вставьте значение QueryHash в запрос выше, и, надеюсь, у вас будет то, что вы ищете.

Вот несколько скриншотов, показывающих, как быстро получить QueryHashзначение в случае, если я плохо его объясняю.

Просмотр предполагаемого плана выполнения

введите описание изображения здесь

Показать план выполнения XM ...

введите описание изображения здесь

Поиск значения QueryHash

введите описание изображения здесь

Очевидно, что хитрость не сработает, если запрос, который вы ищете, отличается от запроса, для которого вы отображаете Предполагаемый план выполнения, но это может быть быстрее, чем все нюансы, которые идут с подпрограммами CLR и заставляют их работать должным образом.

Джон Айсбренер
источник
0

Если у вас есть доступ к текстам запроса (то есть вы можете изменить их), вы можете добавить уникальные комментарии к тем, кто вас интересует:

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

затем ищите myUniqueQuery123в кэше планов вместо всего текста запроса:

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

PS. Не проверено

mustaccio
источник