SQL: comment get tous les caractères distincts dans une colonne, sur toutes les lignes

Existe-t-il un moyen élégant dans SQL Server pour find tous les caractères distincts dans une seule colonne varchar (50), sur toutes les lignes?

Points bonus si cela peut être fait sans sliders 🙂

Par exemple, dites que mes données contiennent 3 lignes:

productname ----------- product1 widget2 nicknack3 

L'inventaire distinct des caractères serait "productwigenka123"

Étant donné que votre colonne est varchar, cela signifie qu'elle ne peut stocker que des caractères des codes 0 à 255, sur n'importe quelle page de code que vous avez. Si vous utilisez uniquement la plage de codes ASCII 32-128, vous pouvez simplement voir si vous avez un des caractères 32-128, un par un. La requête suivante fait cela, en regardant dans sys.objects.name:

 with cteDigits as ( select 0 as Number union all select 1 as Number union all select 2 as Number union all select 3 as Number union all select 4 as Number union all select 5 as Number union all select 6 as Number union all select 7 as Number union all select 8 as Number union all select 9 as Number) , cteNumbers as ( select U.Number + T.Number*10 + H.Number*100 as Number from cteDigits U cross join cteDigits T cross join cteDigits H) , cteChars as ( select CHAR(Number) as Char from cteNumbers where Number between 32 and 128) select cteChars.Char as [*] from cteChars cross apply ( select top(1) * from sys.objects where CHARINDEX(cteChars.Char, name, 0) > 0) as o for xml path(''); 

Voici une requête qui renvoie chaque caractère comme une ligne distincte, avec le nombre d'occurrences. En supposant que votre table s'appelle 'Produits'

 WITH ProductChars(aChar, remain) AS ( SELECT LEFT(productName,1), RIGHT(productName, LEN(productName)-1) FROM Products WHERE LEN(productName)>0 UNION ALL SELECT LEFT(remain,1), RIGHT(remain, LEN(remain)-1) FROM ProductChars WHERE LEN(remain)>0 ) SELECT aChar, COUNT(*) FROM ProductChars GROUP BY aChar 

Pour les combiner tous en une seule ligne, (comme indiqué dans la question), changez le SELECT final en

 SELECT aChar AS [text()] FROM (SELECT DISTINCT aChar FROM ProductChars) base FOR XML PATH('') 

Ce qui précède utilise un bon hack que j'ai trouvé ici , qui émule le GROUP_CONCAT de MySQL.

Le premier niveau de récursivité est déroulé de sorte que la requête ne renvoie pas de strings vides dans la sortie.

Utilisez ceci (doit fonctionner sur n'importe quel SGBDR compatible CTE):

 create table prod as select xv from (values('product1'),('widget2'),('nicknack3')) as x(v); 

Test de requête:

 with a as ( select v, '' as x, 0 as n from prod union select v, subssortingng(v,n+1,1) as x, n+1 as n from a where n < len(v) ) select v, x, n from a -- where n > 0 order by v, n 

Dernière requête:

 with a as ( select v, '' as x, 0 as n from prod union select v, subssortingng(v,n+1,1) as x, n+1 as n from a where n < len(v) ) select distinct x from a where n > 0 order by x 

Version Oracle:

 with a(v,x,n) as ( select v, '' as x, 0 as n from prod union all select v, substr(v,n+1,1) as x, n+1 as n from a where n < length(v) ) select distinct x from a where n > 0 

Si vous avez une table Numbers ou Tally qui contient une list séquentielle d'entiers, vous pouvez faire quelque chose comme:

 Select Distinct '' + Subssortingng(Products.ProductName, N.Value, 1) From dbo.Numbers As N Cross Join dbo.Products Where N.Value <= Len(Products.ProductName) For Xml Path('') 

Si vous utilisez SQL Server 2005 et au-delà, vous pouvez générer votre table Numbers à la volée en utilisant un CTE:

 With Numbers As ( Select Row_Number() Over ( Order By c1.object_id ) As Value From sys.columns As c1 Cross Join sys.columns As c2 ) Select Distinct '' + Subssortingng(Products.ProductName, N.Value, 1) From Numbers As N Cross Join dbo.Products Where N.Value <= Len(Products.ProductName) For Xml Path('')