question

Matt Whitfield avatar image
Matt Whitfield asked

Permutations in T-SQL

Ok, so here's one that has me thinking at the moment, I thought I'd share and see if you guys had any good ideas...

CREATE TABLE #temp (
    [ID] [int] NOT NULL PRIMARY KEY CLUSTERED
)

INSERT INTO [#temp] ([ID]) VALUES (1)
INSERT INTO [#temp] ([ID]) VALUES (2)
INSERT INTO [#temp] ([ID]) VALUES (3)
INSERT INTO [#temp] ([ID]) VALUES (4)

SELECT [t1].[ID] AS Value1,
       [t2].[ID] AS Value2,
       [t3].[ID] AS Value3,
       [t4].[ID] AS Value4
  FROM #temp t1 INNER JOIN 
       #temp t2 ON [t2].[ID] != [t1].[ID] INNER JOIN 
       #temp t3 ON [t3].[ID] != [t1].[ID] AND [t3].[ID] != [t2].[ID] INNER JOIN 
       #temp t4 ON [t4].[ID] != [t1].[ID] AND [t4].[ID] != [t2].[ID] AND [t4].[ID] != [t3].[ID]
 ORDER BY [t1].[ID], [t2].[ID], [t3].[ID], [t4].[ID]

The code above works out the permutations for the set (1, 2, 3, 4) - i.e. it returns a result set which contains all possible orderings of those four values. However, it's pretty inflexible - it doesn't support any number other than 4 elements, and it doesn't support asking for the number of possible sub-sets (i.e. how many different permutations of three numbers from 4 sets are there).

So - any thoughts on how this might be best achieved?

t-sqlchallengeset-based
6 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Matt Whitfield avatar image Matt Whitfield ♦♦ commented ·
Thanks - you can thank visual studio 2008 though - if it didn't spend so much time hanging and compiling really slowly, I'm sure my score would be much less!
1 Like 1 ·
Fatherjack avatar image Fatherjack ♦♦ commented ·
congrats on 8k. Epic effort. Thanks for being around.
0 Likes 0 ·
Grant Fritchey avatar image Grant Fritchey ♦♦ commented ·
WHOOP! 8k. Well done.
0 Likes 0 ·
Matt Whitfield avatar image Matt Whitfield ♦♦ commented ·
Thanks mate! :)
0 Likes 0 ·
TimothyAWiseman avatar image TimothyAWiseman commented ·
@Matt, so you are saying if you switched to Python you wouldn't answer so many questions? Stick with VS then!
0 Likes 0 ·
Show more comments
Rob Farley avatar image
Rob Farley answered

Sure, a CTE. Construct a single field which contains the permutation, and then do a WHERE Perm NOT LIKE '%,' + N + ',%' (or something like that). Then filter it to the ones that are long enough (perhaps have a permlength field).

It reminds me of a palindrome puzzle that Itzik posed a while back. Adam Machanic, Steve Kass and I were deemed the "winners".

Edit to show code...

I've gone for the ordered variety as per a recent comment (it's also a bit easier potentially, as I'm checking the number on the front). My dbo.nums table goes from 1 to as high as you like. To make life interesting, I used items as just even numbers - ten of them. This gives 210 permutations, of 4 items out of 10. The first is 2,4,6,8. The last is 14,16,18,20. I unpivot the data so that it's configurable by setting the items CTE and by @numitems. I hope you like it. It didn't take long for me to throw together (maybe 5 minutes? - longer to write this paragraph I think). On my old machine, the code here was under one-second, but when I put 25 items and chose 6, it took a bit longer (26 seconds).

Also, to just count how many there are, I changed the final select to count out of permlist. This told me that there were 177100 entries for 6 from 25. It told me this in 6 seconds on my old machine. Of course, I could've worked this out much quicker using the original formula for this type of thing, as given at http://www.mathsisfun.com/combinatorics/combinations-permutations-calculator.html

7 out of 25 took 20 seconds to tell me there were 480700, and 8 took 51 seconds to tell me 1081575.

If order wasn't important, then I'd've done some different testing to see if I'd used the value before. If repetitions were allowed, I could've taken many different approaches.

There are better (faster) algorithms for doing this - I just went for simple. There are many heuristics that could be used to speed things up, such as not putting the smallest number on the front if there are more numbers to be added to the list.

declare @numitems int;
set @numitems = 4;

with items as
(select *
from dbo.nums
where num <= 20 and num % 2 = 0)
,
perms as 
(select cast(cast(num as binary(4)) as varbinary(max)) as perm, 1 as numentries
from items
union all
-- 'Prepend any that are smaller than the front value.'
select cast(n.num as binary(4)) + p.perm, p.numentries + 1
from perms p
join items n on n.num < cast(substring(perm,1,4) as int)
where p.numentries < @numitems
)
,permlist as
(
select 
   --'Arbitrary numbering. Order by perm if a proper order 
   --of permutations is required'
   row_number() over (order by (select 1)) as permnum, 
   perm
from perms
--'Permutations must be complete, not finishing early'
where numentries = @numitems
)
select 
   p.permnum,
   n.num as itemnum,
   cast(substring(p.perm,4*n.num-3,4) as int) as item
from permlist p
join dbo.nums n on n.num <= @numitems
;
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Scot Hauder avatar image
Scot Hauder answered

For the permutation part you can take first distinct x values over all the permutations, so one approach for 4 take 2:

CREATE TABLE #temp (
    [ID] [int] NOT NULL PRIMARY KEY CLUSTERED
)

INSERT INTO [#temp] ([ID]) VALUES (1)
INSERT INTO [#temp] ([ID]) VALUES (2)
INSERT INTO [#temp] ([ID]) VALUES (3)
INSERT INTO [#temp] ([ID]) VALUES (4)

;WITH cte AS (
SELECT [t1].[ID] AS Value1,
       [t2].[ID] AS Value2,
       [t3].[ID] AS Value3,
       [t4].[ID] AS Value4
  FROM #temp t1 INNER JOIN 
       #temp t2 ON [t2].[ID] != [t1].[ID] INNER JOIN 
       #temp t3 ON [t3].[ID] != [t1].[ID] AND [t3].[ID] != [t2].[ID] INNER JOIN 
       #temp t4 ON [t4].[ID] != [t1].[ID] AND [t4].[ID] != [t2].[ID] AND [t4].[ID] != [t3].[ID]
       )
SELECT DISTINCT Value1,Value2
FROM cte
ORDER BY Value1,Value2
3 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Scot Hauder avatar image Scot Hauder commented ·
What I'd really like to see is how to produce the permutations without first calculating all the combinations which quickly gets prohibitive with, say, more than 15-20 items.
0 Likes 0 ·
Matt Whitfield avatar image Matt Whitfield ♦♦ commented ·
+1 - but do bear in mind that what you have done there is create the *permutations* for the full set and then taken a distinct on a subset, creating the permutations for a smaller set. Combinations are different - in combinations the order doesn't matter - so (1,2) and (2,1) are the same in combinations. That'll be the next challenge :)
0 Likes 0 ·
Scot Hauder avatar image Scot Hauder commented ·
correct, perms not combs
0 Likes 0 ·
Peso avatar image
Peso answered
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Cyborg avatar image
Cyborg answered
Using dynamic SQL we can solve the problem CREATE TABLE #temp ( [ID] VARCHAR(2) NOT NULL PRIMARY KEY CLUSTERED ) INSERT INTO #temp ([ID]) VALUES (1) INSERT INTO #temp ([ID]) VALUES (2) INSERT INTO #temp ([ID]) VALUES (3) INSERT INTO #temp ([ID]) VALUES (4) INSERT INTO #temp ([ID]) VALUES (5) INSERT INTO #temp ([ID]) VALUES (6) declare @sql NVARCHAR(MAX), @sql2 NVARCHAR(MAX), @Parm NVARCHAR(30) SET @sql = N'SELECT @SQL2 = ''SELECT ''+STUFF((SELECT '', T''+CONVERT(VARCHAR,ROW_NUMBER() OVER(order BY ID))+''.ID'' as ''data()'' FROM #temp FOR XML PATH('''')),1,2,'''')'+' +'' ''+ (SELECT ''FROM ''+STUFF((SELECT '', #Temp T''+CONVERT(VARCHAR,ROW_NUMBER() OVER(order BY ID)) ''data()'' FROM #temp FOR XML PATH('''')),1,2,''''))' SET @Parm = N'@SQL2 varchar(MAX) OUTPUT' EXECUTE sp_executesql @SQL, @Parm, @SQL2=@SQL2 OUTPUT EXEC (@SQL2)
2 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Scot Hauder avatar image Scot Hauder commented ·
it's fine, at first I thought you were trying to find the permutations not the combinations
1 Like 1 ·
Cyborg avatar image Cyborg commented ·
what it is? Scot Hauder
0 Likes 0 ·

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.