LIKE文字の長さ制限を克服する


13

ここでこのLIKE文字の長さの制限を読むと、LIKE句で〜4000文字より長いテキストを送信できないように見えます。

特定のクエリのクエリプランキャッシュからクエリプランをフェッチしようとしています。

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になります。(少なくともエロルが予想されていました)。

この問題を回避する方法はありますか?> 10000charsの長さのクエリがあり、> で検索できないようLIKEです。


2
おそらく、テキストをwhere st.text like '%MY_QUERY%CHARS%' ESCAPE '?'
分割してください

4
実際、4,000文字で同一のクエリテキストがありますが、その後は異なりますか?
マーティンスミス

@MartinSmithはい、そのようなクエリがあります。
ダンディヌ

回答:


9

これは純粋なT-SQLでは解決できCHARINDEXないPATINDEXようです。「検索する」文字列(つまり、最大8000 VARCHARまたは4000 NVARCHAR文字)で8000バイト以上を使用できないためです。これは、次のテストで確認できます。

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を使用して実現できます。typeの2つの入力パラメーターを受け入れるスカラー関数を作成するのは非常に簡単NVARCHAR(MAX)です。

次の例は、SQL# SQLCLRライブラリの無料版を使用してこの機能を示しています(私が作成しましたが、String_Containsは再び無料版で利用可能です:-)。

String_ContainsスカラーUDFは、現在持っている@SearchValueように、入力のparamをNVARCHAR(4000)代わりにNVARCHAR(MAX)(私は人々が検索されるだろうと思っていてはならないため、SQLの#の後に4000文字以上の文字列;-)それは、次の1回限りの変更を行うことにより、変化に非常に簡単です(もちろんインストールされています):

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。未検証

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.