SQL Every combination of ID's - sql

Quite a tricky scenario. I have a table as below. Basically I want to get all combinations of ranges from each RangeSet in SQL Server 2012.
Best I show an example of structure and desired output. The problem is the number of RangeSetID's can be dynamic and the number of RangeID's can be dynamic in each range set
RangeID RangeSetID
------------------
1 4
2 4
3 4
4 4
5 2
6 2
7 2
8 2
9 2
10 2
11 1
12 1
13 1
14 1
15 1
16 1
17 3
18 3
19 3
20 3
I need the output to recursively create the below dataset of rates:
1 5 11 17 (first from range4, first from range2, first from range1, first from range3)
1 5 11 18 (first from range4, first from range2, first from range1, second from range3)
1 5 11 19 (first from range4, first from range2, first from range1, third from range3)
1 5 11 20 (first from range4, first from range2, first from range1, fourth from range3)
1 5 12 17 (first from range4, first from range2, second from range1, first from range3)
1 5 12 18 (first from range4, first from range2, second from range1, second from range3)
1 5 12 19
1 5 12 20
And so on until I reach the last RangeID from each RangeSetID and result in
4 10 16 20 (last from range4, last from range2, last from range1, last from range3)
Which will ultimately result in the below where RateID 1 is showing the first result vertically to allow for the dynamic number of RangeSetID's
RateID RangeID
------------------
1 1
1 5
1 11
1 17
2 1
2 5
2 11
2 18
This should result in 11,000 rows (approx). I have tried CROSS JOIN's etc but I cannot get this working at all.
Any geniuses out there please?
Thanks

Guess this should help. Happy coding!
;WITH CTE
AS
(
SELECT * FROM (
SELECT ROW_NUMBER() over (order by [RangeID1] , [RangeID2], [RangeID3], [RangeID4]) as 'RateID', [RangeID1] , [RangeID2], [RangeID3], [RangeID4] FROM
(
select A.RangeID as [RangeID1], B.RangeID as [RangeID2], C.RangeID as [RangeID3], D.RangeID as [RangeID4]
from [Range] as A
inner join [Range] as B on (A.RangeID <= B.RangeID)
inner join [Range] as C on (B.RangeID <= C.RangeID)
inner join [Range] as D on (C.RangeID <= D.RangeID)
where A.RangeSetID <> B.RangeSetID
and B.RangeSetID <> C.RangeSetID
and C.RangeSetID <> D.RangeSetID
) as A) T
UNPIVOT ( RangeID FOR N IN ([RangeID1] , [RangeID2], [RangeID3], [RangeID4] ))P
)
SELECT RateID, RangeID
FROM CTE

This sort of works, but it's way overcomplicated and could do with some tweaking. Note that I changed your sample data, to include gaps to test this works properly.
DECLARE #table TABLE (
range_id INT,
range_set_id INT);
INSERT INTO #table SELECT 1, 4;
INSERT INTO #table SELECT 2, 4;
INSERT INTO #table SELECT 3, 4;
INSERT INTO #table SELECT 5, 4;
INSERT INTO #table SELECT 8, 2;
INSERT INTO #table SELECT 10, 2;
INSERT INTO #table SELECT 17, 2;
INSERT INTO #table SELECT 18, 2;
INSERT INTO #table SELECT 19, 2;
INSERT INTO #table SELECT 20, 2;
INSERT INTO #table SELECT 21, 1;
INSERT INTO #table SELECT 23, 1;
INSERT INTO #table SELECT 28, 1;
INSERT INTO #table SELECT 29, 1;
INSERT INTO #table SELECT 30, 1;
INSERT INTO #table SELECT 33, 1;
INSERT INTO #table SELECT 35, 3;
INSERT INTO #table SELECT 38, 3;
INSERT INTO #table SELECT 39, 3;
INSERT INTO #table SELECT 40, 3;
--Work out the order of the range_set_ids
WITH ordered AS (
SELECT
range_set_id,
range_id,
ROW_NUMBER() OVER (PARTITION BY range_set_id ORDER BY range_id) AS sequential_id
FROM
#table),
ranges AS (
SELECT
range_set_id,
MIN(range_id) AS range_id
FROM
#table
GROUP BY
range_set_id),
range_order AS (
SELECT
range_set_id,
ROW_NUMBER() OVER (ORDER BY range_id) AS order_id
FROM
ranges),
set_count AS (
SELECT
MAX(order_id) AS max_order_id
FROM
range_order),
start_and_end AS (
SELECT
o.range_set_id,
o.order_id,
MIN(range_id) AS min_range_id,
MAX(range_id) AS max_range_id,
COUNT(range_id) AS iterations
FROM
range_order o
INNER JOIN #table t ON t.range_set_id = o.range_set_id
GROUP BY
o.range_set_id,
o.order_id),
toggles AS (
SELECT
s.range_set_id,
s.order_id,
s.iterations AS toggle
FROM
start_and_end s
CROSS JOIN set_count c
WHERE
s.order_id = c.max_order_id
UNION ALL
SELECT
s.range_set_id,
s.order_id,
t.toggle * (s.iterations) AS toggle
FROM
toggles t
INNER JOIN start_and_end s ON s.order_id = t.order_id - 1
WHERE
s.order_id > 0),
toggle_count AS (
SELECT
MAX(toggle * s.iterations) AS max_toggle
FROM
toggles t
CROSS JOIN set_count c
INNER JOIN start_and_end s ON s.order_id = c.max_order_id),
all_combos AS (
SELECT
1 AS rate_set_id,
o.range_set_id,
1 AS sequential_id,
o.order_id,
lt.toggle AS reset_toggle,
ISNULL(t.toggle, 1) AS increment_toggle,
1 AS current_toggle
FROM
range_order o
CROSS JOIN set_count c
INNER JOIN toggles lt ON lt.order_id = o.order_id
LEFT JOIN toggles t ON t.order_id = o.order_id + 1
UNION ALL
SELECT
a.rate_set_id + 1,
a.range_set_id,
CASE
WHEN a.current_toggle = a.reset_toggle THEN 1 --flip back at the end
WHEN a.current_toggle % a.increment_toggle != 0 THEN a.sequential_id --something lower is still toggling
ELSE a.sequential_id + 1 --toggle
END,
a.order_id,
a.reset_toggle,
a.increment_toggle,
CASE
WHEN a.current_toggle < a.reset_toggle THEN a.current_toggle + 1
ELSE 1
END
FROM
all_combos a
CROSS JOIN set_count sc
CROSS JOIN toggle_count tc
WHERE
a.rate_set_id < tc.max_toggle)
SELECT
a.rate_set_id,
a.range_set_id,
o.range_id
FROM
all_combos a
INNER JOIN ordered o ON o.range_set_id = a.range_set_id AND o.sequential_id = a.sequential_id
ORDER BY
a.rate_set_id,
a.order_id
OPTION (MAXRECURSION 0);

Implemented same logic in dynamic query. This should work for you, I guess
declare #i int = 1;
declare #count int = 0;
declare #cols varchar(max) = '';
declare #select varchar(max) = 'select ';
declare #join varchar(max);
declare #where varchar(max);
declare #query varchar(max);
declare #range varchar(100);
declare #prevrange varchar(100);
declare #rangeid varchar(100);
select #count =count(distinct RangeSetID) from [Range];
while #count > 0
begin
set #range = 'Range' + cast(#i as varchar(max));
set #rangeid = 'RangeID' + cast(#i as varchar(max));
set #cols = #cols + #rangeid + ', ';
set #select = #select + #range + '.RangeID as '+#rangeid + ', ';
if #i = 1
begin
set #join = ' from [Range] as ' + #range;
set #where = 'where ' + #range + '.RangeSetID <> ';
end
else
begin
set #prevrange = 'Range' + cast((#i - 1) as varchar(max));
set #join = #join + ' inner join [Range] as ' + #range + ' on (' + #prevrange + '.RangeID <= ' + #range + '.RangeID)';
if(#count = 1)
set #where = #where + #range+ '.RangeSetID';
else
set #where = #where + #range+ '.RangeSetID and '+ #range+ '.RangeSetID <> ';
end
set #i = #i + 1;
set #count = #count - 1;
end
set #query = '
;WITH CTE
AS
(
SELECT * FROM (
SELECT ROW_NUMBER() over (order by '+ SUBSTRING(#cols, 0, LEN(#cols)) + ') as ''RateID'', '+ SUBSTRING(#cols, 0, LEN(#cols)) +' FROM
(
' + SUBSTRING(#select, 0, LEN(#select)) + char(13) + #join + char(13) + #where + '
) as A) T
UNPIVOT ( RangeID FOR N IN ('+(SUBSTRING(#cols, 0, LEN(#cols))) +' ))P
)
SELECT RateID, RangeID
FROM CTE
';
exec (#query);

Related

SQL Function - Fuzzy Matching with Levenshtein Distance Algorithm - Return Lowest Value Only

Problem: Need SQL function to return the 'lowest' matching value using the Levenshtein algorithm.
Code:
CREATE FUNCTION ufn_levenshtein(#s1 nvarchar(3999), #s2 nvarchar(3999))
RETURNS int
AS
BEGIN
DECLARE #s1_len int, #s2_len int
DECLARE #i int, #j int, #s1_char nchar, #c int, #c_temp int
DECLARE #cv0 varbinary(8000), #cv1 varbinary(8000)
SELECT
#s1_len = LEN(#s1),
#s2_len = LEN(#s2),
#cv1 = 0x0000,
#j = 1, #i = 1, #c = 0
WHILE #j <= #s2_len
SELECT #cv1 = #cv1 + CAST(#j AS binary(2)), #j = #j + 1
WHILE #i <= #s1_len
BEGIN
SELECT
#s1_char = SUBSTRING(#s1, #i, 1),
#c = #i,
#cv0 = CAST(#i AS binary(2)),
#j = 1
WHILE #j <= #s2_len
BEGIN
SET #c = #c + 1
SET #c_temp = CAST(SUBSTRING(#cv1, #j+#j-1, 2) AS int) +
CASE WHEN #s1_char = SUBSTRING(#s2, #j, 1) THEN 0 ELSE 1 END
IF #c > #c_temp SET #c = #c_temp
SET #c_temp = CAST(SUBSTRING(#cv1, #j+#j+1, 2) AS int)+1
IF #c > #c_temp SET #c = #c_temp
SELECT #cv0 = #cv0 + CAST(#c AS binary(2)), #j = #j + 1
END
SELECT #cv1 = #cv0, #i = #i + 1
END
RETURN #c
END
IF OBJECT_ID('tempdb..#ExistingCustomers') IS NOT NULL
DROP TABLE #ExistingCustomers;
CREATE TABLE #ExistingCustomers
(
Customer VARCHAR(255),
ID INT
)
INSERT #ExistingCustomers SELECT 'Ed''s Barbershop', 1002
INSERT #ExistingCustomers SELECT 'GroceryTown', 1003
INSERT #ExistingCustomers SELECT 'Candy Place', 1004
INSERT #ExistingCustomers SELECT 'Handy Man', 1005
IF OBJECT_ID('tempdb..#POTENTIALCUSTOMERS') IS NOT NULL
DROP TABLE #POTENTIALCUSTOMERS;
CREATE TABLE #POTENTIALCUSTOMERS(Customer VARCHAR(255));
INSERT #POTENTIALCUSTOMERS SELECT 'Eds Barbershop'
INSERT #POTENTIALCUSTOMERS SELECT 'Grocery Town'
INSERT #POTENTIALCUSTOMERS SELECT 'Candy Place'
INSERT #POTENTIALCUSTOMERS SELECT 'Handee Man'
INSERT #POTENTIALCUSTOMERS SELECT 'The Apple Farm'
INSERT #POTENTIALCUSTOMERS SELECT 'Ride-a-Long Bikes'
SELECT A.Customer,
b.ID,
b.Customer as cust,
dbo.ufn_levenshtein(REPLACE(A.Customer, ' ', ''), REPLACE(B.Customer, ' ', '')) as ValueLev
FROM #POTENTIALCUSTOMERS a
LEFT JOIN #ExistingCustomers b ON dbo.ufn_levenshtein(REPLACE(A.Customer, ' ', ''), REPLACE(B.Customer, ' ', '')) < 15;
This returns:
What I would like to return:
Explanation: The results are the 'lowest' values from the Levenshtein algorithm. There are two rows where the Levenshtein scores are the same The Apple Farm and Ride-a-Long Bikes, in which case any of the values is fine, just as long as it is one value.
References:
SQL Fuzzy Join - MSSQL
http://www.kodyaz.com/articles/fuzzy-string-matching-using-levenshtein-distance-sql-server.aspx
You can use CTE to get the result you want if you partition by the potential customer and use the ValueLev to order the results:
;WITH CTE AS
(
SELECT RANK() OVER (PARTITION BY a.Customer ORDER BY dbo.ufn_levenshtein(REPLACE(A.Customer, ' ', ''), REPLACE(B.Customer, ' ', '')) ASC) AS RowNbr,
A.Customer,
b.ID,
b.Customer as cust,
dbo.ufn_levenshtein(REPLACE(A.Customer, ' ', ''), REPLACE(B.Customer, ' ', '')) as ValueLev
FROM #POTENTIALCUSTOMERS a
LEFT JOIN #ExistingCustomers b ON dbo.ufn_levenshtein(REPLACE(A.Customer, ' ', ''), REPLACE(B.Customer, ' ', '')) < 15
)
SELECT Customer,
MIN(ID) AS ID,
MIN(cust) AS cust,
ValueLev
FROM CTE
WHERE CTE.RowNbr = 1
GROUP BY Customer, ValueLev
As you don't mind which result is returned in the case of duplicate ValueLev, use GROUP BY and MIN to scale the results down to one per potential customer.
Output:
Customer ID cust ValueLev
Candy Place 1004 Candy Place 0
Grocery Town 1003 GroceryTown 0
Eds Barbershop 1002 Ed's Barbershop 1
Handee Man 1005 Handy Man 2
The Apple Farm 1004 Candy Place 9
Ride-a-Long Bikes 1003 Candy Place 14

List combination of numbers which have same sum

**N** is a prositive number
Need list of scenarios which have sum equal N
For example if N=4
ScenarioId Value
---------- -----
1 1
1 1
1 1
1 1
2 2
2 1
2 1
3 2
3 2
4 3
4 1
5 4
above list is required. If you sum by ScenarioId all sum must equal to N
UPDATE
Here is my own solution. however, I am not sure about the multiplication of two different number sets would not be equal at any time.
My current question is
Is there any possibilities a + b + c = d + e + f and a * b * c = d * e * f
Test link is here
DECLARE #N int = 4;
SELECT
[Value] = CAST(number + 1 as tinyint)
INTO #Values
FROM master.dbo.spt_values
WHERE number < #N
AND [Type] = 'p'
;WITH COMBINATIONS AS(
SELECT ScenarioKey = CAST(NULL AS nvarchar(MAX)), [Value], Total = 0, Multipication = 1, MemeberCount = 0
FROM #Values
UNION ALL
SELECT ScenarioKey = ISNULL(S.ScenarioKey, '') + IIF(S.ScenarioKey IS NULL, '', N'-') + CAST(P.[Value] AS nvarchar(10)), S.[Value], Total = S.Total + P.[Value], Multipication = S.Multipication * P.[Value], MemeberCount = MemeberCount + 1
FROM #Values P
JOIN COMBINATIONS AS S ON S.Total < S.[Value]
),
SCENARIOS AS(
SELECT
ScenarioKey
,ScenarioId = ROW_NUMBER() OVER(ORDER BY ScenarioKey)
,[Value]
FROM
(
SELECT
ScenarioKey
,[Value]
,Multipication
,MemeberCount
-- this will prevent dublications. because 1 * 2 * 3 = 3 * 2 * 1
-- however, I am not sure about multipication of two different number sets would not be equal any time
,RowNo = ROW_NUMBER() OVER(PARTITION BY [Value],Multipication,MemeberCount ORDER BY [Value],ScenarioKey)
FROM COMBINATIONS
WHERE Total = #N
) X
WHERE RowNo = 1 AND [Value] = #N
)
SELECT
R.ScenarioId
,[Value] = S.[value]
FROM SCENARIOS R
CROSS APPLY (SELECT [value] FROM STRING_SPLIT(R.ScenarioKey, '-')) S
DROP TABLE #Values
It's too long for comment, so I post this as an answer. I want to note, that this is a static example, but I hope it can be easily translated as a dynamic statement.
Steps are written as comments in the statement:
WITH rcte AS
(
-- Recursive query to generate all numbers from 1 to 4
SELECT 0 AS Number
UNION ALL
SELECT Number + 1
FROM rcte
WHERE Number < 4
), permutations AS (
-- All possible permutations with sum equal to 4
-- There is additional column DuplicateMarker.
-- It will be used later, because 0,0,0,4 and 0,4,0,0 are the same
SELECT
t1.Number AS Number1,
t2.Number AS Number2,
t3.Number AS Number3,
t4.Number AS Number4,
CONCAT(LTRIM(STR(t1.Number)), '.', LTRIM(STR(t2.Number)), '.', LTRIM(STR(t3.Number)), '.', LTRIM(STR(t4.Number))) AS DuplicateMarker
FROM rcte t1, rcte t2, rcte t3, rcte t4
WHERE (t1.Number + t2.Number + t3.Number + t4.Number) = 4
), duplicates AS (
-- Get data with splitted DuplicateMarker column
SELECT *
FROM permutations
CROSS APPLY (SELECT [value] FROM STRING_SPLIT(DuplicateMarker, '.')) t
), results AS (
-- Get unique combinations
-- WITHIN GROUP (ORDER BY) will order strings and 0.0.0.4 and 0.4.0.0 will be the same
SELECT DISTINCT STRING_AGG([value], '.') WITHIN GROUP (ORDER BY [value]) AS ScenarioValue
FROM duplicates
GROUP BY Number1, Number2, Number3, Number4
)
SELECT
DENSE_RANK() OVER (ORDER BY r.ScenarioValue) AS ScenarioID,
s.[value]
FROM results r
CROSS APPLY (SELECT [value] FROM STRING_SPLIT(r.ScenarioValue, '.')) s
WHERE [value] <> '0'
Output:
ScenarioID value
1 4
2 1
2 3
3 2
3 2
4 1
4 1
4 2
5 1
5 1
5 1
5 1
Update:
Thanks to #AndriyM's comment, I've made some changes and now you can eliminate string manipulations:
WITH rcte AS
(
-- Recursive query to generate all numbers from 0 to 4
SELECT 0 AS Number
UNION ALL
SELECT Number + 1
FROM rcte
WHERE Number < 4
), combinations AS (
-- All different combinations with sum equal to 4
SELECT
t1.Number AS Number1,
t2.Number AS Number2,
t3.Number AS Number3,
t4.Number AS Number4,
ROW_NUMBER() OVER (ORDER BY t1.Number, t2.Number, t3.Number, t4.NUmber) AS ScenarioID
FROM rcte t1, rcte t2, rcte t3, rcte t4
WHERE
((t1.Number + t2.Number + t3.Number + t4.Number) = 4) AND
(t1.Number <= t2.Number) AND
(t2.Number <= t3.Number) AND
(t3.Number <= t4.Number)
)
SELECT c.ScenarioID, v.[value]
FROM combinations c
CROSS APPLY (VALUES (c.NUmber1), (c.Number2), (c.Number3), (c.Number4)) AS v ([value])
WHERE v.[value] > 0
Update 2:
Approach using dynamic statement - probably not the best approach, but is based on statement from first update:
-- Set your #n value
DECLARE #n int
SET #n = 4
-- Declarations
DECLARE #combinationsSelect nvarchar(max)
DECLARE #combinationsRowNumber nvarchar(max)
DECLARE #combinationsFrom nvarchar(max)
DECLARE #combinationsWhere1 nvarchar(max)
DECLARE #combinationsWhere2 nvarchar(max)
DECLARE #combinationsValues nvarchar(max)
SET #combinationsSelect = N''
SET #combinationsRowNumber = N''
SET #combinationsFrom = N''
SET #combinationsValues = N''
SET #combinationsWhere1 = N''
SET #combinationsWhere2 = N''
-- Generate dynamic parts of the statement
;WITH numbers AS
(
SELECT 1 AS Number
UNION ALL
SELECT Number + 1
FROM Numbers
WHERE Number < #n
)
SELECT
#combinationsSelect = #combinationsSelect + N', t' + LTRIM(STR(Number)) + N'.Number AS Number' + LTRIM(STR(Number)),
#combinationsRowNumber = #combinationsRowNumber + N', t' + LTRIM(STR(Number)) + N'.Number',
#combinationsValues = #combinationsValues + N', (c.Number' + LTRIM(STR(Number)) + N')',
#combinationsFrom = #combinationsFrom + N', rcte t' + LTRIM(STR(Number)),
#combinationsWhere1 = #combinationsWhere1 + N'+ t' + LTRIM(STR(Number)) + N'.Number ',
#combinationsWhere2 = #combinationsWhere2 +
CASE
WHEN Number = 1 THEN N''
ELSE N'AND (t' + LTRIM(STR(Number-1)) + N'.Number <= t' + + LTRIM(STR(Number)) + N'.Number) '
END
FROM
numbers
SET #combinationsSelect = STUFF(#combinationsSelect, 1, 2, N'')
SET #combinationsRowNumber = STUFF(#combinationsRowNumber, 1, 2, N'')
SET #combinationsValues = STUFF(#combinationsValues, 1, 2, N'')
SET #combinationsFrom = STUFF(#combinationsFrom, 1, 2, N'')
SET #combinationsWhere1 = STUFF(#combinationsWhere1, 1, 2, N'')
SET #combinationsWhere2 = STUFF(#combinationsWhere2, 1, 4, N'')
-- Dynamic statement
DECLARE #stm nvarchar(max)
SET #stm =
N'WITH rcte AS (
SELECT 0 AS Number
UNION ALL
SELECT Number + 1
FROM rcte
WHERE Number < ' + LTRIM(STR(#n)) +
N'), combinations AS (
SELECT ' +
#combinationsSelect +
N', ROW_NUMBER() OVER (ORDER BY ' + #combinationsRowNumber + N') AS ScenarioID
FROM ' + #combinationsFrom +
N' WHERE ((' + #combinationsWhere1 + N') = ' + LTRIM(STR(#n)) + ') AND ' + #combinationsWhere2 +
N')
SELECT c.ScenarioID, v.[value]
FROM combinations c
CROSS APPLY (VALUES ' + #combinationsValues + N') AS v ([value])
WHERE v.[value] > 0'
-- Execute dynamic statement
EXEC (#stm)
If you have sample data like below
You can write query like below
Declare #N int =4
Select T.*
From #T T
cross apply (
select S, SUM(V) Total
From #T
Group By S) Totals
Where Totals.Total=#N and T.S = Totals.S

SQL Server - Return All possible combinations of a 4 digit number passed to a stored procedure

I have a task to write a stored procedure or a function to return all possible combinations of a 4 digit number.
For example, if I pass 1234 to the stored procedure or function, it should return 4 digit numbers (all possible combinations), like
1123, 1112, 1324, 1342, 2134, 2234
and so on.
It can be of 4 digits only.
I have been doing this with using LIKE operator:
select *
from Table
where mynumber like '%1%'
and mynumber like '%2%'
and mynumber like '%3%'
and mynumber like '%4%'
but the problem is, I have hardcoded the numbers 1,2,3 and 4.
The number can be anything.
And these many LIKE operators can also impact the performance on a large table.
Can anybody give me some generic query to get the combinations?
Thanks in advance.
You can use a cross join:
with digits as (
select substring(num, 1, 1) as d union all
select substring(num, 2, 1) as d union all
select substring(num, 3, 1) as d union all
select substring(num, 4, 1) as d
)
select (d1.d + d2.d + d3.d + d4.d)
from digits d1 cross join
digits d2 cross join
digits d3 cross join
digits d4;
Note: This assumes that the number is a string (based on the fact that you use like in your question).
First you need to be able to break a four-digit number into separate digits. I suggest using a table variable and the modulus operator. Assuming we have an integer input named #input, we can break it into its digits using this:
DECLARE #Digits Table(Number int)
INSERT INTO #Digits(Number)
VALUES (#input % 10),
(#input / 10 % 10),
(#input / 100 % 10),
(#input / 1000 % 10)
Now we have a table with four rows, one row per digit.
To create a combination of four digits, we need to include the table four times, meaning we need three joins. The joins have to be set up so no digit is duplicated. Thus our FROM and JOIN clauses will look like this:
FROM #Digits D1
JOIN #Digits D2 ON D2.Number <> D1.Number
JOIN #Digits D3 ON D3.Number <> D1.Number
AND D3.Number <> D2.Number
JOIN #Digits D4 ON D4.Number <> D1.Number
AND D4.Number <> D2.Number
AND D4.Number <> D3.Number
Now to take the values and make a new, four-digit integer:
SELECT Number = D1.Number * 1000
+ D2.Number * 100
+ D3.Number * 10
+ D4.Number
The complete solution:
CREATE PROC Combine(#input AS int)
AS
BEGIN
DECLARE #Digits Table(Number int)
;
INSERT INTO #Digits(Number)
VALUES (#input % 10),
(#input / 10 % 10),
(#input / 100 % 10),
(#input / 1000 % 10)
;
SELECT Number = D1.Number * 1000
+ D2.Number * 100
+ D3.Number * 10
+ D4.Number
FROM #Digits D1
JOIN #Digits D2 ON D2.Number <> D1.Number
JOIN #Digits D3 ON D3.Number <> D1.Number
AND D3.Number <> D2.Number
JOIN #Digits D4 ON D4.Number <> D1.Number
AND D4.Number <> D2.Number
AND D4.Number <> D3.Number
ORDER BY Number
;
END
Usage:
EXEC Combine 1234
Resultset:
Number
------
1234
1243
1324
1342
1423
1432
2134
2143
2314
2341
2413
2431
3124
3142
3214
3241
3421
4123
4132
4213
4231
4312
4321
24 row(s)
Click here to run the above code on RexTester
Improving #GordonLinoff's answer, you can add an additional column in you CTE so that you can only make sure that each number is only used once:
declare #num varchar(max);
set #num = '1234';
with numCTE as (
select SUBSTRING(#num, 1,1) as col, 1 as cnt union
select SUBSTRING(#num, 2,1) as col, 3 as cnt union
select SUBSTRING(#num, 3,1) as col, 9 as cnt union
select SUBSTRING(#num, 4,1) as col, 27 as cnt
)
select DISTINCT (a1.col+a2.col+a3.col+a4.col)
from numCTE a1
cross join numCTE a2
cross join numCTE a3
cross join numCTE a4
where a1.cnt + a2.cnt + a3.cnt + a4.cnt = 40
Additionally, you can remove the WHERE to allow each number to be used more than once.
Don't forget the DISTINCT keyword. :)
You can try this.
select * from Table where mynumber like '%[1234][1234][1234][1234]%'
if it should only be 4 digit
select * from Table where mynumber like '[1234][1234][1234][1234]'
Also, you can use [1-4] instead of [1234]
Here's query to return all combinations of four digits (characters in general):
select A.col + B.col + C.col + D.col [Combinations] from
(values ('1'),('2'),('3'),('4')) as A(col) cross join
(values ('1'),('2'),('3'),('4')) as B(col) cross join
(values ('1'),('2'),('3'),('4')) as C(col) cross join
(values ('1'),('2'),('3'),('4')) as D(col)
Taking inspiration from this answer:
WITH n AS (
SELECT n FROM (VALUES (1), (2), (3), (4)) n (n)
) SELECT ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n
FROM n ones, n tens, n hundreds, n thousands
You can define a table in your stored procedure will all possible combinations but using letters for codding:
DECLARE #Combinations TABLE
(
[value] CHAR(4)
);
INSERT INTO #Combinations ([value])
VALUES ('AAAA')
,('AAAB')
,('AAAC')
,('AAAD')
...
Then update every latter with the input number:
DECLARE #Numner1 TINYINT = 2
,#Numner2 TINYINT = 5
,#Numner3 TINYINT = 1
,#Numner4 TINYINT = 3;
UPDATE #Combinations
SET [value] = REPLACE([value], 'A', #Numner1);
UPDATE #Combinations
SET [value] = REPLACE([value], 'B', #Numner2);
UPDATE #Combinations
SET [value] = REPLACE([value], 'C', #Numner3);
UPDATE #Combinations
SET [value] = REPLACE([value], 'D', #Numner4);
Then just join the table with your table:
select *
from Table A
INNER JOIN #Combinations B
ON A.[mynumber] = B.[value];
Try This approach
DECLARE #Num INT = 5432
;WITH CTE
AS
(
SELECT
SeqNo = 1,
Original = CAST(#Num AS VARCHAR(20)),
Num = SUBSTRING(CAST(#Num AS VARCHAR(20)),1,1)
UNION ALL
SELECT
SeqNo = SeqNo+1,
Original,
Num = SUBSTRING(Original,SeqNo+1,1)
FROM CTE
WHERE SeqNo < LEN(Original)
)
SELECT
MyStr = C1.Num+C2.Num+C3.Num+C4.Num
FROM CTE C1
CROSS JOIN CTE C2
CROSS JOIN CTE C3
CROSS JOIN CTE C4
WHERE
(
C1.SeqNo <> C2.SeqNo
AND
C3.SeqNo <> C4.SeqNo
AND
C4.SeqNo <> C1.SeqNo
AND
C2.SeqNo <> C3.SeqNo
AND
C1.SeqNo <> C3.SeqNo
AND
C4.SeqNo <> C2.SeqNo
)
ORDER BY 1
My Result
MyStr
2345
2354
2435
2453
2534
2543
3245
3254
3425
3452
3524
3542
4235
4253
4325
4352
4523
4532
5234
5243
5324
5342
5423
5432
Please try this. SET BASED Approach to generate all Possible combinations of a number-
IF OBJECT_ID('Tempdb..#T') IS NOT NULL
DROP TABLE tempdb..#T
DECLARE # AS INT = 1234
IF LEN(#) <= 7
BEGIN
DECLARE #str AS VARCHAR(100)
SET #str = CAST(# AS VARCHAR(100))
DECLARE #cols AS VARCHAR(100) = ''
SELECT DISTINCT SUBSTRING(#str,NUMBER,1) n INTO #T FROM MASTER..spt_values WHERE number > 0 AND number <= LEN(#)
SELECT #cols = #cols + r
FROM ( SELECT DISTINCT CONCAT(', o',number,'.n') r FROM MASTER..spt_values WHERE number > 0 AND number <= (LEN(#)-1) )q
DECLARE #ExecStr AS VARCHAR(1000) = ''
SET #ExecStr = 'SELECT CAST(CONCAT( a.n' + #cols + ' ) AS INT) Combinations FROM #T a'
SELECT #ExecStr = #ExecStr + r FROM
(
SELECT DISTINCT CONCAT(' CROSS APPLY ( SELECT * FROM #T b' , number , ' WHERE ( b' , number, '.n' , ' <> a.n ) ',
CASE WHEN number = 1 then ''
WHEN number = 2 then ' AND ( b2.n <> o1.n )'
WHEN number = 3 then ' AND ( b3.n <> o1.n ) AND ( b3.n <> o2.n ) '
WHEN number = 4 then ' AND ( b4.n <> o1.n ) AND ( b4.n <> o2.n ) AND ( b4.n <> o3.n ) '
WHEN number = 5 then ' AND ( b5.n <> o1.n ) AND ( b5.n <> o2.n ) AND ( b5.n <> o3.n ) AND ( b5.n <> o4.n ) '
WHEN number = 6 then ' AND ( b6.n <> o1.n ) AND ( b6.n <> o2.n ) AND ( b6.n <> o3.n ) AND ( b6.n <> o4.n ) AND ( b6.n <> o5.n ) '
END
,') o' , number ) r FROM
MASTER..spt_values
WHERE number > 0 AND number <= (LEN(#)-1)
)p
EXEC (#ExecStr)
END
IF OBJECT_ID('tempdb..#T') IS NOT NULL
DROP TABLE tempdb..#T
OUTPUT
1432
1342
1423
1243
1324
1234
2431
2341
2413
2143
2314
2134
3421
3241
3412
3142
3214
3124
4321
4231
4312
4132
4213
4123
from - https://msbiskills.com/2016/05/20/sql-puzzle-generate-possible-combinations-of-a-number-puzzle/
You can try following alternative SQL Script as well
declare #param varchar(4) = '1234'
;with combination as (
select
distinct rn = DENSE_RANK() over (Order By num), num
from (
select substring(#param,1,1) as num
union all
select substring(#param,2,1)
union all
select substring(#param,3,1)
union all
select substring(#param,4,1)
) t
)
select
c1.num, c2.num, c3.num, c4.num,
cast(c1.num as char(1)) + cast(c2.num as char(1)) + cast(c3.num as char(1)) + cast(c4.num as char(1)) as number
from combination c1, combination c2, combination c3, combination c4
It produces 256 numbers for 4 digits
Actually this code is from SQL code which returns non-repeatable combinations in SQL of given set of items, but modified it to enable repeats of items in the output

Convert Row to Column based on data in SQL

The below image is my table
The below excel is the design for output.
My table contains 12 columns for each month and an year column. An item, there it can be available in multiple years and data for each month.
year- itemcode- jan- feb
2014- pqr- 12- 11
2015- pqr- 4- 8
I need to generated the below output. For an item available for multiple years
output needs to list in the following manner.
ItemCode- Jan14- Feb14- Mar14-... Dec14- Jan15- Feb15-... Dec15
pqr- 12- 11- 4- 8-
How can I able to achieve this.
I tried different methods after googling. But I am not able to get proper input for solving this.
At present I am trying some solutions found in SO. It will be very helpful someone can give some inputs. Thanks in advance.
You'll need to use dymanic SQL...
Basically assuming a table called #tbl (with a little bit of sample data - ive only done 3 months but extend to 12!)
CREATE TABLE #tbl ([ItemCode] NVARCHAR(20), [Year] INT, Jan INT, Feb INT, Mar INT)
INSERT #tbl ( ItemCode, Year, Jan, Feb, Mar )
VALUES ( 'pqr', 2014, 12, 11, 7 ), ( 'pqr', 2015, 4, 8, 0 ),
( 'xyz', 2015, 7, 1, 0 ), ( 'abc', 2013, 63, 23, 12 ), ( 'abc', 2015, 63, 23, 12 )
we want to generate a query that looks like
SELECT tbase.ItemCode
, ISNULL(t13.Jan,0) AS 'Jan-13', ISNULL(t13.Feb,0) AS 'Feb-13', ISNULL(t13.Mar,0) AS 'Mar-13'
, ISNULL(t14.Jan,0) AS 'Jan-14', ISNULL(t14.Feb,0) AS 'Feb-14', ISNULL(t14.Mar,0) AS 'Mar-14'
, ISNULL(t15.Jan,0) AS 'Jan-15', ISNULL(t15.Feb,0) AS 'Feb-15', ISNULL(t15.Mar,0) AS 'Mar-15'
FROM
(SELECT DISTINCT(ItemCode) AS ItemCode FROM #tbl) AS tbase
LEFT JOIN (SELECT * FROM #tbl AS t13 WHERE YEAR = 2013) AS t13 ON t13.ItemCode = tbase.ItemCode
LEFT JOIN (SELECT * FROM #tbl AS t14 WHERE YEAR = 2014) AS t14 ON t14.ItemCode = tbase.ItemCode
LEFT JOIN (SELECT * FROM #tbl AS t15 WHERE YEAR = 2015) AS t15 ON t15.ItemCode = tbase.ItemCode
And results like:
ItemCode Jan-13 Feb-13 Mar-13 Jan-14 Feb-14 Mar-14 Jan-15 Feb-15 Mar-15
abc 63 23 12 0 0 0 63 23 12
pqr 0 0 0 12 11 7 4 8 0
xyz 0 0 0 0 0 0 7 1 0
As you can see from the query - the 2 things to buld up are the lines , ISNULL(t13.Jan,0)... and LEFT JOIN (SELECT ...
we can do this by declaring 2 NVARCHAR(MAX) variables (one for the select, one for the from) and building them up inside a while loop while iterating through the available years.
ie something like...
DECLARE #select NVARCHAR(MAX);
DECLARE #from NVARCHAR(MAX);
DECLARE #years TABLE(yr INT);
DECLARE #year INT;
DECLARE #yearName NVARCHAR(2)
INSERT #years
SELECT DISTINCT [Year] FROM #tbl
SELECT #year = MIN(yr) FROM #years
SELECT #yearName = RIGHT(CAST(#year AS NVARCHAR(4)),2)
SELECT #select = 'SELECT tbase.ItemCode'
SELECT #from = 'FROM (SELECT DISTINCT(ItemCode) AS ItemCode FROM #tbl) AS tbase '
WHILE EXISTS (SELECT NULL FROM #years WHERE yr = #year)
BEGIN
SELECT #yearName = RIGHT(CAST(#year AS NVARCHAR(4)),2)
SELECT #select = #select + CHAR(13) + CHAR(10)
+ ', ISNULL(t' + #yearName + '.Jan,0) AS [Jan-' + #yearName + '],'
+ ' ISNULL(t' + #yearName + '.Feb,0) AS [Feb-' + #yearName + '],'-- +9 more
+ ' ISNULL(t' + #yearName + '.Mar,0) AS [Mar-' + #yearName + '] '
SELECT #from = #from + CHAR(13) + CHAR(10)
+ 'LEFT JOIN (SELECT * FROM #tbl AS t' + #yearName
+ ' WHERE [Year] = ' + CAST(#year AS NVARCHAR(4)) + ') AS t' + #yearName
+ ' ON t' + #yearName + '.ItemCode = tbase.ItemCode '
SELECT #year = #year + 1
END
DECLARE #sql NVARCHAR(MAX)
SELECT #sql = #select + CHAR(13) + CHAR(10) + #from
EXEC (#sql)
All you need to do is extend this to the full 12 months and you're done!
NB - I've assumed that there is at least 1 entry for every year. If you have a gap where there is a year in the middle of your range with no entries whatsoever you'd need to do a minor modification to the WHILE loop - ie WHILE #year <= (SELECT MAX(Year) FROM #years)
Suppose you have the following table :
select 'AA' as ItemCode,2014 as year, 23 as Jan, 55 as Feb, 55 as Mar,565 as Apr,656 as May,
343 as Jun,54 as Jul,23 as Aug,66 as Sep,645 as Oct,32 as Nov,66 as Dec
into dbo.test ;
insert into dbo.test select 'AA',2015,554,456,3,54,756,98,2,765,24,876,34,66
union select 'BB',2014,45,56,3,54,756,98,2,765,24,876,34,66
union select 'BB',2015,45,56,3,54,756,98,2,765,24,876,34,66;
With dynamic sql, do
declare #sql nvarchar(1000);
declare #sql2 nvarchar(1000);
declare #year int;
declare #first_year int;
declare c cursor for select distinct year from dbo.test;
open c;
FETCH NEXT FROM c into #year
if ##FETCH_STATUS = 0
begin
select #sql='select test'+convert(varchar,#year)+'.ItemCode';
select #sql = #sql+',test'+CONVERT(varchar,#year)+'.Jan as Jan'+CONVERT(varchar,#year)+',test'
+CONVERT(varchar,#year)+'.Feb as Feb'+CONVERT(varchar,#year)+',test'
+CONVERT(varchar,#year)+'.Mar as Mar'+CONVERT(varchar,#year);
select #sql2='test test'+CONVERT(varchar,#year);
select #first_year=#year;
end;
FETCH NEXT FROM c into #year
WHILE ##FETCH_STATUS = 0
begin
select #sql = #sql+',test'+CONVERT(varchar,#year)+'.Jan as Jan'+CONVERT(varchar,#year)+',test'
+CONVERT(varchar,#year)+'.Feb as Feb'+CONVERT(varchar,#year)+',test'
+CONVERT(varchar,#year)+'.Mar as Mar'+CONVERT(varchar,#year);
select #sql2=#sql2+' inner join test test'+CONVERT(varchar,#year)+' on test'+CONVERT(varchar,#year)+'.ItemCode=test'+CONVERT(varchar,#first_year)+'.ItemCode and test'+CONVERT(varchar,#year)+'.year='+CONVERT(varchar,#year);
FETCH NEXT FROM c into #year
end;
close c;
deallocate c;
select #sql=#sql+' FROM '+#sql2 + ' AND test'+convert(varchar,#first_year)+'.year='+CONVERT(varchar,#year);
print #sql
EXECUTE sp_executesql #sql;
Or, with standard SQL, something like this
select test2014.ItemCode,test2014.Jan as Jan2014,test2014.Feb as Feb2014,test2015.Jan as Jan2015,test2015.Feb as Feb2015
from test test2014 inner join test test2015 on test2014.ItemCode=test2015.ItemCode
where test2014.year=2014 and test2015.year=2015;

create while loop with cte

how to create sql server cte from a while loop
my loop like this
declare #ind as int
declare #code as nvarchar
set #ind = 0
while #ind < 884
begin
select #ind = #ind + 1
--here execute Procedure
--and set return value to variable
set #code = cast (#ind as nvarchar)
end
If you need table:
;WITH Sec(Number) AS
(
SELECT 0 AS Number
UNION ALL
SELECT Number + 1
FROM Sec
WHERE Number < 884
)
SELECT * FROM Sec
OPTION(MAXRECURSION 0)
If you need one string:
;WITH Sec(Number) AS
(
SELECT 0 AS Number
UNION ALL
SELECT Number + 1
FROM Sec
WHERE Number < 884
)
SELECT STUFF(a.[Str], 1, 1, '')
FROM
(
SELECT (SELECT ',' + CAST(Number AS NVARCHAR(3))
FROM Sec
FOR XML PATH(''), TYPE
).value('.','varchar(max)') AS [Str]
) AS a
OPTION(MAXRECURSION 0)
Below query selects values from 0 to 884:
;WITH T(Num)AS
(
SELECT 0
UNION ALL
SELECT Num+1 FROM T WHERE T.Num < 884
)SELECT Num FROM T
OPTION (MAXRECURSION 0);