# t-sql - combinatorics

I'm trying to find all possible character combinations in a variable length string.

For example: '--' would have 2^n = 2^2 = 4 possibilities, 'x-', '-x', 'xx', '--'

I think that essentially I need to loop through c(2,2) + c(2,1) + c(2,0) where c(n,r) = n! / (r! * (n-r)!) but I'm having trouble getting things to work in with a cte. So far everything starts to break down with you add characters to the string.

Using a numbers table -

``````declare @s varchar(15)
set @s = '--'

;with subset as (
select cast(stuff(@s,number,1,'x') as varchar(15)) as token,
cast('.' + cast(number as char(1)) + '.' as varchar(11)) as permutation,
cast(1 as int) as iteration ,
number
from numbers where number between 1 and len(@s)
union
select @s, '.0.', 1, 0
) ,

combination as (
select  cast(stuff(token,n.number,1,'x') as varchar(15)) as token ,
CAST(permutation+CAST(n.number AS CHAR(1))+'.' AS VARCHAR(11)) AS permutation,
iteration + 1 as iteration,
n.number
from subset s   inner join numbers n on substring(s.permutation,2,1) = n.number + 1
where n.number between 1 and len(@s)
)

select * from subset union combinations
``````

This returns

``````token           permutation iteration   number
--------------- ----------- ----------- -----------
--              .0.         1           0
x-              .1.         1           1
-x              .2.         1           2
xx              .2.1.       2           1
``````

I can't figure out how to get it working past two characters ( '---', '----' ...) Maybe I'm looking at this wrong any pointers would be greatly appreciated. Sorry about the sql i know it's pretty ugly with lots of errors. Even after days of research my knowledge of cte's is horrid.

• Sources of inspiration: 1, 2. Commented Jan 24, 2012 at 23:04
• What do you need this for? What you're generating just looks like a bit mask that's being incremented by 1: `000`, `001`, `010`, `011`, etc. Commented Jan 25, 2012 at 0:26
• Why do you need to do this? And why are you tying to do it in SQL?
– BalamBalam
Commented Jan 25, 2012 at 2:23
• I thought about a bit mask. Unfortunately our 3rd party db stores responses to questionnaires by character position in a string. 'x_x' would be a response of options 1 and 3. I used an auxillary number table and a function to pull the responses as a comma seperated string. Another option I pondered was pulling a table with every possible combination and looking up the responses based of a corresponding key. I'm probably wrong but it seems this might be faster.
– sarov
Commented Jan 25, 2012 at 4:05
• So you want to convert a string of `x---x` into a list of numbers like `1 5` that corresponds to the positions of the `x`s. Is that correct? Commented Jan 25, 2012 at 19:11

Here's a totally different approach that seems to work for this case. Expand and customize it as needed. Change the value of @l to control the number of bits/questions in the output.

``````CREATE FUNCTION dbo.bin_val(@val int, @trim bit)
RETURNS varchar(max)
AS
BEGIN
DECLARE @hval varchar(256) = REPLACE(CONVERT(varchar, CAST(@val AS varbinary), 1), '0x', '')
DECLARE @bval varchar(256) = ''
DECLARE @i int = 1
WHILE @i <= LEN(@hval)
BEGIN
SET @bval = @bval + CASE SUBSTRING(@hval, @i, 1)
WHEN '0' THEN '0000'
WHEN '1' THEN '0001'
WHEN '2' THEN '0010'
WHEN '3' THEN '0011'
WHEN '4' THEN '0100'
WHEN '5' THEN '0101'
WHEN '6' THEN '0110'
WHEN '7' THEN '0111'
WHEN '8' THEN '1000'
WHEN '9' THEN '1001'
WHEN 'A' THEN '1010'
WHEN 'B' THEN '1011'
WHEN 'C' THEN '1100'
WHEN 'D' THEN '1101'
WHEN 'E' THEN '1110'
WHEN 'F' THEN '1111'
END
SET @i = @i + 1
END

IF @trim = 1
SET @bval = RIGHT(@bval, LEN(@bval) - ISNULL(NULLIF(CHARINDEX('1', @bval), 0), LEN(@bval)) + 1)

RETURN @bval
END

GO

DECLARE @l int = 8
SELECT
number,
RIGHT(REPLACE(REPLACE(dbo.bin_val(number, 0), '1', 'X'), '0', '-'), @l)
FROM master..spt_values
WHERE type = 'P'
AND number <= POWER(2, @l) - 1
``````
• It took me a second to wrap my head around this solution. I really like it. Clean and efficient. I ran it against a 14 character response string (16,384 combinations) and it ripped right through them before the key was up. Commented Jan 26, 2012 at 17:55
• Yeah, it's just a simple decimal-to-binary converter, and a query that feeds the standard master..spt_values numbers table through it, then converts 1 and 0 to X and -. Of course, it only works for simple yes/no type questions. You'd have to make an arbitrary base converter to handle more than that.
– db2
Commented Jan 26, 2012 at 21:09

Suppose you have a auxiliary Numbers table with integer numbers.

``````DECLARE @s VARCHAR(5);
SET @s = 'ABCDE';

WITH Subsets AS (
SELECT CAST(SUBSTRING(@s, Number, 1) AS VARCHAR(5)) AS Token,
CAST('.'+CAST(Number AS CHAR(1))+'.' AS VARCHAR(11)) AS Permutation,
CAST(1 AS INT) AS Iteration
FROM dbo.Numbers WHERE Number BETWEEN 1 AND 5
UNION ALL
SELECT CAST(Token+SUBSTRING(@s, Number, 1) AS VARCHAR(5)) AS Token,
CAST(Permutation+CAST(Number AS CHAR(1))+'.' AS VARCHAR(11)) AS
Permutation,
s.Iteration + 1 AS Iteration
FROM Subsets s JOIN dbo.Numbers n ON s.Permutation NOT LIKE
'%.'+CAST(Number AS CHAR(1))+'.%' AND s.Iteration < 5 AND Number
BETWEEN 1 AND 5
--AND s.Iteration = (SELECT MAX(Iteration) FROM Subsets)
)
SELECT * FROM Subsets
WHERE Iteration = 5
ORDER BY Permutation

Token Permutation Iteration
----- ----------- -----------
ABCDE .1.2.3.4.5. 5
ABCED .1.2.3.5.4. 5
ABDCE .1.2.4.3.5. 5
(snip)
EDBCA .5.4.2.3.1. 5
EDCAB .5.4.3.1.2. 5
EDCBA .5.4.3.2.1. 5
``````

I managed to get the cte solution working. It does a good job of providing the character combinations but crawls creating the token strings. I've included the code below. The solution that db2 provided is the winner. It creates the token strings incredibly fast and is pretty clever.

``````declare @s varchar(15)
set @s = '--';

with anchor as (
select    n.number as id ,
cast(stuff(@s,n.number,1,'x') as varchar(15)) as token ,
cast('.' + cast(n.number as char(2)) + '.' as varchar(35)) as permutation
from numbers n
where number between 1 and len(@s)
),
cte as (
select    id as max_id ,
cast(stuff(@s,id,1,'x') as varchar(15)) as token ,
cast('.' + rtrim(cast(id as char(2))) + '.' as varchar(35)) as permutation ,
cast(1 as int) as iteration
from anchor

union all

select    a.id as max_id ,
cast(dbo.genresponse(c.permutation + cast(a.id as char(2)) + '.',len(@s)) as varchar(15)) as token ,
a.token,
cast(c.permutation +  cast(a.id as char(2)) + '.' as varchar(35)) as permutation,
c.iteration + 1
from cte c
inner join anchor a  on   a.id > c.max_id and c.permutation not like ('%.' + cast(a.id as varchar(35)) + '.%')
)

select * from cte
``````