Surmonter la limitation de longueur de caractère LIKE

13

En lisant cette limitation de longueur de caractères LIKE ici, il semble que je ne puisse pas envoyer un texte de plus de ~ 4000 caractères dans une clause LIKE.

J'essaie de récupérer le plan de requête dans le cache du plan de requête pour une requête particulière.

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 requête à l'intérieur de la LIKEest plus longue que 4000 caractères, j'obtiens 0 résultat même si ma requête est dans le plan de cache. (Je m'attendais à au moins une erreur).

Existe-t-il un moyen de contourner ce problème ou de le faire différemment? J'ai des requêtes qui peuvent être> 10000longues et il semble que je ne puisse pas les trouver avec le LIKE.

Dan Dinu
la source
2
Décomposer le texte peut-être ... car vous ne devriez pas avoir beaucoup de requêtes exactement comme les autres:where st.text like '%MY_QUERY%CHARS%' ESCAPE '?'
scsimon
4
Avez-vous réellement des textes de requête identiques pour 4 000 caractères, puis différents?
Martin Smith
@MartinSmith oui, j'ai des requêtes comme ça.
Dan Dinu

Réponses:

9

Il ne semble pas que cela puisse être résolu en T-SQL pur car CHARINDEXni PATINDEXautoriser ni utiliser plus de 8000 octets dans la chaîne "à rechercher" (c'est-à-dire max de 8000 VARCHARou 4000 NVARCHARcaractères). Cela peut être vu dans les tests suivants:

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

Ces deux requêtes renvoient l'erreur suivante:

Msg 8152, niveau 16, état 10, ligne xxxxx La
chaîne ou les données binaires seraient tronquées.

Et, la réduction de l'une 7000ou l'autre de ces requêtes pour 3999éliminer l'erreur. Une valeur de 4000dans les deux cas entraînera également une erreur (en raison du N'Z'caractère supplémentaire au début).

CEPENDANT, cela peut être accompli en utilisant SQLCLR. Il est assez simple de créer une fonction scalaire qui accepte deux paramètres d'entrée de type NVARCHAR(MAX).

L'exemple suivant illustre cette capacité en utilisant la version gratuite du bibliothèque SQL # SQLCLR (que j'ai créée, mais String_Contains est à nouveau disponible dans la version gratuite :-).

L' UDF scalaire String_Contains a actuellement le paramètre d'@SearchValue entrée au NVARCHAR(4000)lieu de NVARCHAR(MAX)(je ne dois pas avoir pensé que les gens chercheraient des chaînes de plus de 4000 caractères ;-) mais c'est très facile à changer en effectuant le changement unique suivant (après SQL # a été installé, bien sûr):

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

INSTALLER

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

TESTS

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

Veuillez garder à l'esprit que String_Contains utilise une comparaison tout sensible (cas, accent, Kana et largeur).

Solomon Rutzky
la source
2

Parce que vous avez également demandé des approches alternatives, une autre façon de trouver un plan spécifique est de le rechercher plan_hash, en modifiant votre requête comme suit:

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

Le moyen le plus rapide que j'ai trouvé pour obtenir la QueryHashvaleur à rechercher est de coller la requête en question dans une fenêtre de requête, puis d'afficher le plan d'exécution estimé. Lisez la sortie XML et recherchez leQueryHash attribut dans l' StmtSimpleélément et cela devrait vous donner ce dont vous avez besoin. Branchez la valeur QueryHash dans la requête ci-dessus et nous espérons que vous devriez avoir ce que vous recherchez.

Voici quelques captures d'écran montrant comment obtenir rapidement QueryHash valeur au cas où je l'expliquerais mal.

Afficher le plan d'exécution estimé

entrez la description de l'image ici

Afficher le plan d'exécution XM ...

entrez la description de l'image ici

Rechercher la valeur QueryHash

entrez la description de l'image ici

De toute évidence, l'astuce ne fonctionnera pas si la requête que vous recherchez diffère de la requête pour laquelle vous affichez le plan d'exécution estimé, mais cela peut être plus rapide que toutes les nuances fournies avec les routines CLR et les faire fonctionner correctement.

John Eisbrener
la source
0

Si vous avez accès aux textes de requête (ce qui signifie que vous pouvez les modifier), vous pouvez ajouter des commentaires uniques à ceux qui vous intéressent:

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

puis recherchez myUniqueQuery123dans le cache du plan au lieu du texte de requête entier:

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

PS. Pas testé

mustaccio
la source