0
votes

T-SQL - Compter des caractères uniques dans une variable

Objectif: Compter le nombre de caractères distincts dans une variable la solution la plus rapide possible.

DECLARE @String1 NVARCHAR(4000) = N'1A^' ; --> output = 3
DECLARE @String2 NVARCHAR(4000) = N'11' ; --> output = 1
DECLARE @String3 NVARCHAR(4000) = N'*' ; --> output = 1
DECLARE @String4 NVARCHAR(4000) = N'*A-zz' ; --> output = 4


2 commentaires

Voir cette question, c'est pour MySQL, mais je crois que cela fonctionnera: Stackoverflow.com/questions/29967280/...


Merci pour la suggestion Kevin. Acclamations ~


5 Réponses :


6
votes

en utilisant ngrams8k comme base, vous pouvez modifier le paramètre d'entrée vers un NvarchaRar (4000) CODE> et modifier le DataLength code>, rendant ngramsn4k code >. Ensuite, vous pouvez l'utiliser pour diviser la chaîne en caractères individuels et les compter: xxx pré>

modifié ngrams8k code>: p> p>

IF OBJECT_ID('dbo.NGramsN4k','IF') IS NOT NULL DROP FUNCTION dbo.NGramsN4k;
GO
CREATE FUNCTION dbo.NGramsN4k
(
  @string nvarchar(4000), -- Input string 
  @N      int            -- requested token size
)
/****************************************************************************************
Purpose:
 A character-level N-Grams function that outputs a contiguous stream of @N-sized tokens 
 based on an input string (@string). Accepts strings up to 8000 varchar characters long.
 For more information about N-Grams see: http://en.wikipedia.org/wiki/N-gram. 

Compatibility: 
 SQL Server 2008+, Azure SQL Database

Syntax:
--===== Autonomous
 SELECT position, token FROM dbo.NGrams8k(@string,@N);

--===== Against a table using APPLY
 SELECT s.SomeID, ng.position, ng.token
 FROM dbo.SomeTable s
 CROSS APPLY dbo.NGrams8K(s.SomeValue,@N) ng;

Parameters:
 @string  = The input string to split into tokens.
 @N       = The size of each token returned.

Returns:
 Position = bigint; the position of the token in the input string
 token    = varchar(8000); a @N-sized character-level N-Gram token

Developer Notes:  
 1. NGrams8k is not case sensitive

 2. Many functions that use NGrams8k will see a huge performance gain when the optimizer
    creates a parallel execution plan. One way to get a parallel query plan (if the 
    optimizer does not chose one) is to use make_parallel by Adam Machanic which can be 
    found here:
 sqlblog.com/blogs/adam_machanic/archive/2013/07/11/next-level-parallel-plan-porcing.aspx

 3. When @N is less than 1 or greater than the datalength of the input string then no 
    tokens (rows) are returned. If either @string or @N are NULL no rows are returned.
    This is a debatable topic but the thinking behind this decision is that: because you
    can't split 'xxx' into 4-grams, you can't split a NULL value into unigrams and you 
    can't turn anything into NULL-grams, no rows should be returned.

    For people who would prefer that a NULL input forces the function to return a single
    NULL output you could add this code to the end of the function:

    UNION ALL 
    SELECT 1, NULL
    WHERE NOT(@N > 0 AND @N <= DATALENGTH(@string)) OR (@N IS NULL OR @string IS NULL)

 4. NGrams8k can also be used as a tally table with the position column being your "N" 
    row. To do so use REPLICATE to create an imaginary string, then use NGrams8k to split
    it into unigrams then only return the position column. NGrams8k will get you up to 
    8000 numbers. There will be no performance penalty for sorting by position in 
    ascending order but there is for sorting in descending order. To get the numbers in
    descending order without forcing a sort in the query plan use the following formula:
    N = <highest number>-position+1. 

 Pseudo Tally Table Examples:
    --===== (1) Get the numbers 1 to 100 in ascending order:
    SELECT N = position 
    FROM dbo.NGrams8k(REPLICATE(0,100),1);

    --===== (2) Get the numbers 1 to 100 in descending order:
    DECLARE @maxN int = 100;
    SELECT N = @maxN-position+1
    FROM dbo.NGrams8k(REPLICATE(0,@maxN),1)
    ORDER BY position;

 5. NGrams8k is deterministic. For more about deterministic functions see:
    https://msdn.microsoft.com/en-us/library/ms178091.aspx

Usage Examples:
--===== Turn the string, 'abcd' into unigrams, bigrams and trigrams
 SELECT position, token FROM dbo.NGrams8k('abcd',1); -- unigrams (@N=1)
 SELECT position, token FROM dbo.NGrams8k('abcd',2); -- bigrams  (@N=2)
 SELECT position, token FROM dbo.NGrams8k('abcd',3); -- trigrams (@N=3)

--===== How many times the substring "AB" appears in each record
 DECLARE @table TABLE(stringID int identity primary key, string varchar(100));
 INSERT @table(string) VALUES ('AB123AB'),('123ABABAB'),('!AB!AB!'),('AB-AB-AB-AB-AB');

 SELECT string, occurances = COUNT(*) 
 FROM @table t
 CROSS APPLY dbo.NGrams8k(t.string,2) ng
 WHERE ng.token = 'AB'
 GROUP BY string;

----------------------------------------------------------------------------------------
Revision History:
 Rev 00 - 20140310 - Initial Development - Alan Burstein
 Rev 01 - 20150522 - Removed DQS N-Grams functionality, improved iTally logic. Also Added
                     conversion to bigint in the TOP logic to remove implicit conversion
                     to bigint - Alan Burstein
 Rev 03 - 20150909 - Added logic to only return values if @N is greater than 0 and less 
                     than the length of @string. Updated comment section. - Alan Burstein
 Rev 04 - 20151029 - Added ISNULL logic to the TOP clause for the @string and @N 
                     parameters to prevent a NULL string or NULL @N from causing "an 
                     improper value" being passed to the TOP clause. - Alan Burstein
****************************************************************************************/
RETURNS TABLE WITH SCHEMABINDING AS RETURN
WITH 
L1(N) AS 
(
  SELECT 1
  FROM (VALUES    -- 90 NULL values used to create the CTE Tally table
        (NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),
        (NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),
        (NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),
        (NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),
        (NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),
        (NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),
        (NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),
        (NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),
        (NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)
       ) t(N)
),
iTally(N) AS                                   -- my cte Tally table
(
  SELECT TOP(ABS(CONVERT(BIGINT,((DATALENGTH(ISNULL(@string,N''))/2)-(ISNULL(@N,1)-1)),0)))
    ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -- Order by a constant to avoid a sort
  FROM L1 a CROSS JOIN L1 b                    -- cartesian product for 8100 rows (90^2)
)
SELECT
  position = N,                                -- position of the token in the string(s)
  token    = SUBSTRING(@string,CAST(N AS int),@N)  -- the @N-Sized token
FROM iTally
WHERE @N > 0 AND @N <= (DATALENGTH(@string)/2);    -- Protection against bad parameter values 


5 commentaires

Note latérale: Vous n'avez pas besoin de 90 nulls dans votre L1 (n) , seulement 64 (depuis 64 ^ 2 = 4096) ...


Vrai, @zoharpeled; C'était la partie "paresseuse" de moi changer la fonction. Je ne voulais pas faire tout le travail pour l'OP;)


C'est parfait! Merci un bouquet et des acclamations!


@Zoharpeled Voir ma version ci-dessous et ici: Stackoverflow.com/Questtions/47082399/...


@Alanburstein Vous devez soumettre cela comme modification supplémentaire à votre article de SSC existant. Avoir à la fois un varchar et NVARCHAR pourrait être vraiment important pour les personnes; surtout comme ils peuvent être pris en charge par DataLength .



2
votes

Prenez une copie de NGRAMS8K et vous pouvez faire ceci: xxx pré>

retours: strong> p> xxx pré>

mis à jour Juste une mise à jour rapide basée sur la poste et les commentaires de @ Larnu. Je n'ai pas remarqué que l'OP s'occupait d'UNICODE E.G. Nvarchars. J'ai créé une version Nvarchar (4000) ici - similaire à ce que @larnu a posté ci-dessus. Je viens de mettre à jour le jeton de retour pour utiliser LATIN1_Generaleral_bin Collation. P>

ALTER FUNCTION dbo.NGramsN4K
(
  @string nvarchar(4000), -- Input string
  @N      int             -- requested token size
)
/****************************************************************************************
Purpose:
 A character-level N-Grams function that outputs a contiguous stream of @N-sized tokens 
 based on an input string (@string). Accepts strings up to 4000 nvarchar characters long.
 For more information about N-Grams see: http://en.wikipedia.org/wiki/N-gram. 

Compatibility: 
 SQL Server 2008+, Azure SQL Database

Syntax:
--===== Autonomous
 SELECT position, token FROM dbo.NGramsN4K(@string,@N);

--===== Against a table using APPLY
 SELECT s.SomeID, ng.position, ng.token
 FROM dbo.SomeTable s
 CROSS APPLY dbo.NGramsN4K(s.SomeValue,@N) ng;

Parameters:
 @string  = The input string to split into tokens.
 @N       = The size of each token returned.

Returns:
 Position = bigint; the position of the token in the input string
 token    = nvarchar(4000); a @N-sized character-level N-Gram token

Developer Notes:  
 1. NGramsN4K is not case sensitive

 2. Many functions that use NGramsN4K will see a huge performance gain when the optimizer
    creates a parallel execution plan. One way to get a parallel query plan (if the 
    optimizer does not chose one) is to use make_parallel by Adam Machanic which can be 
    found here:
 sqlblog.com/blogs/adam_machanic/archive/2013/07/11/next-level-parallel-plan-porcing.aspx

 3. When @N is less than 1 or greater than the datalength of the input string then no 
    tokens (rows) are returned. If either @string or @N are NULL no rows are returned.
    This is a debatable topic but the thinking behind this decision is that: because you
    can't split 'xxx' into 4-grams, you can't split a NULL value into unigrams and you 
    can't turn anything into NULL-grams, no rows should be returned.

    For people who would prefer that a NULL input forces the function to return a single
    NULL output you could add this code to the end of the function:

    UNION ALL 
    SELECT 1, NULL
    WHERE NOT(@N > 0 AND @N <= DATALENGTH(@string)) OR (@N IS NULL OR @string IS NULL);

 4. NGramsN4K is deterministic. For more about deterministic functions see:
    https://msdn.microsoft.com/en-us/library/ms178091.aspx

Usage Examples:
--===== Turn the string, 'abcd' into unigrams, bigrams and trigrams
 SELECT position, token FROM dbo.NGramsN4K('abcd',1); -- unigrams (@N=1)
 SELECT position, token FROM dbo.NGramsN4K('abcd',2); -- bigrams  (@N=2)
 SELECT position, token FROM dbo.NGramsN4K('abcd',3); -- trigrams (@N=3)

--===== How many times the substring "AB" appears in each record
 DECLARE @table TABLE(stringID int identity primary key, string nvarchar(100));
 INSERT @table(string) VALUES ('AB123AB'),('123ABABAB'),('!AB!AB!'),('AB-AB-AB-AB-AB');

 SELECT string, occurances = COUNT(*) 
 FROM @table t
 CROSS APPLY dbo.NGramsN4K(t.string,2) ng
 WHERE ng.token = 'AB'
 GROUP BY string;

------------------------------------------------------------------------------------------
Revision History:
 Rev 00 - 20170324 - Initial Development - Alan Burstein
 Rev 01 - 20191108 - Added Latin1_General_BIN collation to token output - Alan Burstein
*****************************************************************************************/
RETURNS TABLE WITH SCHEMABINDING AS RETURN
WITH
L1(N) AS
(
  SELECT 1 FROM (VALUES -- 64 dummy values to CROSS join for 4096 rows
        ($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),
        ($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),
        ($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),
        ($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($)) t(N)
),
iTally(N) AS 
(
  SELECT 
  TOP (ABS(CONVERT(BIGINT,((DATALENGTH(ISNULL(@string,''))/2)-(ISNULL(@N,1)-1)),0)))
    ROW_NUMBER() OVER (ORDER BY (SELECT NULL))    -- Order by a constant to avoid a sort
  FROM L1 a CROSS JOIN L1 b                       -- cartesian product for 4096 rows (16^2)
)
SELECT
  position = N,                                   -- position of the token in the string(s)
  token    = SUBSTRING(@string COLLATE Latin1_General_BIN,CAST(N AS int),@N) -- the @N-Sized token
FROM iTally
WHERE @N > 0  -- Protection against bad parameter values:
AND   @N <= (ABS(CONVERT(BIGINT,((DATALENGTH(ISNULL(@string,''))/2)-(ISNULL(@N,1)-1)),0)));


3 commentaires

Soyez prudent avec votre propre fonction ici, Alan; L'OP utilise nvarchar et ngrams8k utilise DataLength et attend un varchar (8000) . :) une valeur comme decondi @ string5 nvarchar (4000) = n 'ᡣᓡ'; retournerait incorrectement la valeur 1 .


(bien que sur ma collation, donc " ngamsn4k "; mais ngrams8k retournerait 1 en raison des caractères renvoyés "code>" " plutôt que la collation les traite comme le même)


C'est un génial f (x) alan !! Merci pour le partage. À votre santé!



1
votes

Vous pouvez le faire de manière native dans SQL Server à l'aide de CTE et de manipulation de chaîne: xxx pré>

Exes exemples: p>

SET @TestString = N'*A-zz';
{execute code}
LetterCount = 4

SET @TestString = N'1A^';
{execute code}
LetterCount = 3

SET @TestString = N'1';
{execute code}
LetterCount = 1

SET @TestString = N'*';
{execute code}
LetterCount = 1


2 commentaires

AVERTISSEMENT, lorsque l'entrée est un NvarchaRar (4000) Cela échouera, car il se tient, avec une chaîne de plus de 100 caractères.


Merci pour une suggestion Martin. À votre santé!



1
votes
CREATE TABLE #STRINGS(
    STRING1 NVARCHAR(4000)
)

INSERT INTO #STRINGS (
    STRING1
)
VALUES
(N'1A^'),(N'11'),(N'*'),(N'*A-zz')


;WITH CTE_T AS (
    SELECT DISTINCT
        S.STRING1
        ,SUBSTRING(S.STRING1, V.number + 1, 1) AS Val
    FROM
        #STRINGS S
    INNER JOIN
        [master]..spt_values V
        ON V.number < LEN(S.STRING1)
    WHERE
        V.[type] = 'P'
)
SELECT
    T.STRING1
    ,COUNT(1) AS CNT
FROM
    CTE_T T
GROUP BY
    T.STRING1

1 commentaires

Merci pour une suggestion Bob. À votre santé!



2
votes

Voici une autre alternative utilisant la puissance de la table de comptabilisation. Il a été appelé le "couteau suisse de l'armée de T-SQL". Je garde une table de manœuvre comme une vue sur mon système qui le rend incroyablement rapide.

select count(distinct substring(s.String1, A.N, 1))
    , s.String1
from @Something s
CROSS APPLY (SELECT TOP(LEN(s.String1)) t.N FROM cteTally t) A(N)
group by s.String1


6 commentaires

Merci pour une suggestion Sean. J'aime cette approche de table non-F (x) Tally Tally. À votre santé!


@ 007, si vous ne voulez ni une fonction ni une vue, vous pouvez même l'utiliser avec le CTE en avance sur votre requête ( Tally-on-the-mouche ).


Sean, grande approche, +1 de mon côté. Mieux encore mieux pourrait être une table de nombres physiques indexés, mais c'était quelque chose à vérifier ... et une autre suggestion pourrait être d'utiliser cross applicatif (sélectionnez le dessus (len (S.String1)) TN de CTetally T) A ( N) . Je peux imaginer qu'un top () -clause fonctionne plus vite dans ce cas ... à nouveau quelque chose à vérifier :-)


@Shnugo éteignant la jointure pour une croix s'applique fait une amélioration plus importante que celle que j'aurais soupçonné. Le tableau indexé physique fait un plan d'exécution plus simple et un peu d'amélioration de l'original. Mais le vrai gagnant utilise Cross Appliquer. Je suis normalement en phase avec éviter les jointures triangulaires comme celle-ci et c'est un bon exemple de la raison pour laquelle ils devraient être évités. Merci pour le nudge.


@Seanlange Très bonnes conclusions! Pour ajouter une autre idée: je changerais de vue sur un ITVF, en prenant un @lowerboundary et un @upperboundary comme paramètres. Cela modifierait le TOP TOP à l'endroit où la liste d'exécution est créée. Je ne sais pas si le moteur est suffisamment intelligent pour réduire suffisamment l'ensemble de Tally ...


Dans Cette réponse J'ai utilisé Sélectionnez Haut (@ End- @ Start +1) (Row_Number () sur (commande Par (Sélectionnez NULL)) + @Start -1) * @step Pour calculer le Top et ajouter une taille d'étape (si vous auriez besoin du nombre de numéros ou de quelque chose comme ça. ..