This nested cursor loop is looping forever - sql-server

I have a table that contains desired table names, column names, column data types, and column data length that I want to create a new database with.
right now I am working nesting some cursors to print out each unique table name and their accompanying column names.
when I run the below code it loops forever. What am I missing?
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE spC1 CURSOR
FOR
SELECT DISTINCT tblNm
FROM tblDef
DECLARE #curTabNm AS varchar(60) = ''
DECLARE #curColNm as varchar(60) = ''
DECLARE #Outer_Loop AS int = 0
OPEN spC1
FETCH NEXT FROM spC1 INTO #curTabNm
WHILE #Outer_Loop = 0
PRINT #curTabNm
BEGIN
DECLARE spC2 CURSOR
FOR
SELECT colNm FROM tblDef
WHERE tblNm = #curTabNm
open spC2
FETCH NEXT FROM spC2 INTO #curColNm
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT #curColNm
FETCH NEXT FROM spC2 INTO #curColNm
END
CLOSE spC2
DEALLOCATE spC2
FETCH NEXT FROM spC1 INTO #curTabNm
SET #Outer_Loop = 1
END
CLOSE spC1
DEALLOCATE spC1
END

The first step here is to create consumable ddl and sample data. I left off some of the datatypes that you said are not yet defined from your client.
if OBJECT_ID('tempdb..#Something') is not null
drop table #Something
create table #something
(
tblNm sysname
, colNm sysname
, colTyp varchar(20)
, colLen int
, colReq bit
)
insert #something(tblNm, colNm, colTyp, colLen, colReq)
values
('account', 'studentNum', 'String', 15, 0)
,('account', 'employNum', 'String', 15, 0)
,('account', 'firstName', 'String', 35, 0)
,('account', 'lastName', 'String', 40, 0)
,('assignment', 'vSource', 'String', 255, 0)
,('assignment', 'schoolNum', 'String', 7, 1)
,('assignment', 'calendarName', 'String', 30, 1)
,('assignment', 'courseNum', 'String', 13, 1)
Now that we have data to work with we can work on the actual query here. This uses the FOR XML trick to generate a delimited list. In this case the delimited list is our columns. We then use some grouping in the outer query to get each table. The code looks like this.
declare #SQL nvarchar(max) = ''
select #SQL = #SQL + 'create table [' + tblNm + '](' + STUFF((select ', [' + s2.colNm + '] ' + Case s2.colTyp when 'String' then 'varchar' end
+ isnull('(' + convert(varchar(4), s2.colLen) + ') ', ' ')
+ case s2.colReq when 1 then 'NOT ' else '' end + 'NULL'
from #something s2
where s2.tblNm = s.tblNm
order by s2.colNm
FOR XML PATH('')), 1,1, '') + ');'
from #something s
group by s.tblNm
select #SQL
--exec sp_executesql #SQL
That is it. The whole thing. When you are happy with the contents of the #SQL variable you can simply uncomment the execute statement and it will create your tables.
As stated previously the load on the system is not likely going to be huge for using cursors here but once you learn this type of technique it really is a lot simpler than cursors and it is far less code.

Check this out
BEGIN
DECLARE spC1 CURSOR
FOR
SELECT DISTINCT tblNm
FROM tblDef
DECLARE #curTabNm AS varchar(60) = ''
DECLARE #curColNm as varchar(60) = ''
DECLARE #Outer_Loop AS int = 0
OPEN spC1
FETCH NEXT FROM spC1 INTO #curTabNm
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE spC2 CURSOR FOR
SELECT colNm FROM tblDef
WHERE tblNm = #curTabNm
open spC2
FETCH NEXT FROM spC2 INTO #curColNm
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT #curColNm
FETCH NEXT FROM spC2 INTO #curColNm
END
CLOSE spC2
DEALLOCATE spC2
FETCH NEXT FROM spC1 INTO #curTabNm
END
CLOSE spC1
DEALLOCATE spC1
END

Ah it was at lines
WHILE #Outer_Loop = 0
PRINT #curTabNm
are in opposite order. whoops!

tblNm colNm colTyp colLen colReq
account personID Decimal NULL 0
account studentNum String 15 0
account employNum String 15 0
account firstName String 35 0
account lastName String 40 0
account birthDate String NULL 0
account accountNumber Decimal NULL 0
account accountPIN String 50 0
account reminder String 5 0
account remAmount String 5 0
account blockConcessions String 5 0
account blockAlaCarte String 5 0
account singlePurch String 5 0
account eligibility String 20 0
account certifiedType String 20 0
account startDate String NULL 0
account endDate String NULL 0
account beginningBalance String 12 0
account vSource String 255 0
assignment vSource String 255 0
assignment schoolNum String 7 1
assignment calendarName String 30 1
assignment courseNum String 13 1
assignment sectionNum Int 5 1
assignment groupName String 50 1
assignment assignmentName String 50 1
assignment abbreviation String 5 1
assignment assignmentSeq String 5 0
assignment totalPoints String 5 1
assignment curveName String 80 0
assignment dueDate String NULL 1
assignment assignedDate String NULL 0
assignment multiplier String 6 0
assignment description String 255 0
assignment objectives String 255 0
assignment references String 255 0
assignmentScore schoolNum String 7 1
assignmentScore calendarName String 30 1
assignmentScore courseNum String 13 1
assignmentScore sectionNum Int 5 1
assignmentScore groupName String 50 1
assignmentScore assignmentName String 50 1
assignmentScore studentNum String 15 1
assignmentScore score String 20 1
assignmentScore flag String 20 0
assignmentScore comments String 250 0
assignmentScore vSource String 255 0
attendance vSource String 255 0
attendance schoolNum String 7 1
attendance calendarName String 30 1
attendance studentNum String 15 1
attendance date DateTime NULL 1
attendance scheduleSeq String 3 0
attendance periodSeq Int 3 0
attendance code String NULL 1
attendance comments String 50 0
attendanceExcuse schoolNum String 7 1
attendanceExcuse calendarName String 30 1
attendanceExcuse code String NULL 1
attendanceExcuse description String 50 1
attendanceExcuse status String 20 1
attendanceExcuse excuse String 20 1
attendanceExcuse vSource String 255 0
behaviorEvent vSource String 255 0
behaviorEvent eventType String 100 1
behaviorEvent incidentID Int 0 1
behaviorEvent eventID Int 50 1
behaviorEvent referralEmployNum String 15 0
behaviorEvent adminEmployNum String 15 0
behaviorEvent weaponCode String 2 0
behaviorEvent calendarName String 30 1
behaviorResolution eventID String 50 1
behaviorResolution studentNum String 15 1
behaviorResolution resolutionDate DateTime NULL 1
behaviorResolution resolutionTime String 20 1
behaviorResolution endDate DateTime NULL 0
behaviorResolution ParentInvolvementComment String 255 0
behaviorResolution ParentCode Int NULL 0
behaviorResolution AssignedAlternateEducation String 50 0
behaviorResolution AdjudicationCode String 50 0
behaviorResolution ArrestCode String 50 0
behaviorResolution LLEOffice String 50 0
behaviorResolution LLENotified String 50 0
behaviorResolution Duration Int NULL 0
behaviorResolution adminEmployNum String 50 0
behaviorResolution DisciplinaryActionComment String 255 0
behaviorResolution resolutionType String 50 0
behaviorResolution vSource String 255 0
behaviorRole vSource String 255 0
behaviorRole OffenderType String NULL 0
behaviorRole injury String NULL 1
behaviorRole relationshipToSchool String NULL 1
behaviorRole InfractionComment String NULL 0
behaviorRole ReceivedServices Boolean NULL 0
behaviorRole WeaponCount Int NULL 0
behaviorRole WeaponDetectedMethod String NULL 0
behaviorRole victimType String NULL 0
behaviorRole eventID String 50 1
behaviorRole studentNum String 15 1
behaviorRole role String 15 1
behaviorRole demerits String 5 0
bus number String 10 1
bus description String 250 0
bus contracted String 5 0
bus vSource String 255 0
busRider vSource String 255 0
busRider schoolNum String 7 1
busRider calendarName String 30 1
busRider studentNum String 15 1
busRider inBusNumber String 10 0
busRider inTime String 20 0
busRider inBusStop String 20 0
busRider outBusNumber String 10 0
busRider outTime String 20 0
busRider outBusStop String 20 0
busRider lateBus String 10 0
busRider transMiles String 7 0

Related

How to get date ranges according to date data inside database table

I have a #TMP table filled with dates with its corresponding remarks. It looks like this:
Date isworkdays isweekdays
2019-08-16 1 1
2019-08-17 0 0
2019-08-18 0 0
2019-08-19 1 1
2019-08-20 1 1
2019-08-21 1 1
2019-08-22 1 1
2019-08-23 1 1
2019-08-24 0 0
2019-08-25 0 0
2019-08-26 1 1
2019-08-27 1 1
2019-08-28 1 1
2019-08-29 1 1
2019-08-30 1 1
2019-08-31 0 0
2019-09-01 0 0
2019-09-02 1 1
2019-09-03 1 1
2019-09-04 1 1
2019-09-05 1 1
2019-09-06 1 1
2019-09-07 0 0
2019-09-08 0 0
2019-09-09 1 1
2019-09-10 1 1
2019-09-11 1 1
2019-09-12 1 1
2019-09-13 1 1
2019-09-14 0 0
2019-09-15 0 0
2019-09-16 1 1
2019-09-17 1 1
2019-09-18 1 1
2019-09-19 1 1
2019-09-20 1 1
2019-09-21 0 0
2019-09-22 0 0
2019-09-23 1 1
2019-09-24 1 1
2019-09-25 1 1
2019-09-26 1 1
2019-09-27 1 1
2019-09-28 0 0
2019-09-29 0 0
2019-09-30 1 1
Date column are obviously a series of dates from 2019-08-16 to 2019-10-16
isworkingdays column indicates "1" if the date is from Monday to Friday, and "0" if its Saturday and Sunday. isweekdays column indicates "1" if the date is not holiday and "0" if its holiday.
I want to count the days betweem 2019-08-16 to 2019-09-16 only to those dates with isworkdays = 1, and for Saturday and Sunday count it as 1
This is what I have done so far
declare #userinput date
SELECT SUM(IsWorkDay) AS DayCount FROM AMIFIN..PDC T
LEFT JOIN dbo.#TMP c ON c.[Date] <= T.Check_Date
WHERE CAST(#userinput AS Date) <= CAST((c.[Date]) as date) AND c.IsWorkDay = 1
but it returns 3952743 days
Conditional aggregation should work here:
SELECT
SUM(CASE WHEN isworkdays = 1 THEN 1 ELSE 0.5 END) AS DayCount
FROM AMIFIN..PDC T
LEFT JOIN dbo.#TMP c
ON c.[Date] <= T.Check_Date
WHERE
#userinput < c.[Date];
This idea here is to count 1 for a weekday, and 1/2 for a weekend day. The half counting logic is necessary, because it could be that we end up counting an odd number of weekend days.
I have figured it out. Thanks to all who posted an answer it gives me a lot of idea.
Here is my solution:
Declare #cnt INT = 0
DECLARE #postponedate date = cast('2019-08-20' as date); --Will be the current date of postponement
DECLARE #checkdate date = cast('2019-08-27' as date); --Date of the check
Declare #fdate date = (select [Date] FROM #TMP WHERE [Date] = #checkdate); --dates to be compared
WHILE #postponedate <= #fdate
BEGIN
DECLARE #IsWorkDay INT = (select IsWorkDay FROM #TMP WHERE cast([Date] as date) = #postponedate)
DECLARE #IsWeekDay INT = (select IsWeekDay FROM #TMP WHERE cast([Date] as date) = #postponedate)
SET #cnt = CASE WHEN #IsWorkDay = 1 THEN
#cnt + 1
ELSE
#cnt + 0.5
END
SET #postponedate = DATEADD(Day, 1, #postponedate)
PRINT #postponedate
PRINT #cnt
END
I think your query will be like this:
SELECT SUM(DATEDIFF(DAY, date, date) + 1) AS Total
FROM tbl_TMP
WHERE date >= '2019-08-16' AND date <= '2019-09-30'
AND isworkdays=1 AND isweekdays=1
GROUP BY isworkdays,isweekdays
Note: "+1" because DATEDIFF returns No. of days in INT DataType

Adding two dynamic columns in the table through stored procedure and these columns must have data based on condition

I am trying to add two dynamic columns HeaderText and IsShowHeader to my table through a stored procedure.
In the first column, the first row must have text as Header1 and after 8 rows text must be Header2, then again after 8 rows text must be Header3 and so on.
In the second column value must be 1, and next 8 rows must have 0, the 9th row value must be 1 again, then the next 8 rows must have 0 like this...
ALTER PROCEDURE [dbo].[SkipRow]
AS
BEGIN
SELECT RID
,FirstName
,LastName
,(CASE WHEN X.[Row#]%9=0 And [X].[Row#]=0 THEN 1 ELSE
0 END)As IsShowHeader
,(COUNT(*) OVER ()) as TotalRows FROM
(
SELECT
*,ROW_NUMBER() OVER(ORDER BY RID) AS [Row#]
FROM Mytable1 WITH(NOLOCK)
)X
End
Output:
HeaderText IsShowHeader
1 Header1 1
2 Null 0
3 Null 0
4 Null 0
5 Null 0
6 Null 0
7 Null 0
8 Null 0
9 Null 0
10 Header2 1
11 Null 0
12 Null 0
13 Null 0
14 Null 0
15 Null 0
16 Null 0
17 Null 0
18 Null 0
19 Header3 1
you already have the [Row#], use the modulus operator % to get every 9 rows
HeaderText = case when ([Row#] - 1) % 9 = 0
then 'Header' + convert(varchar(10), ([Row#] - 1) / 9 + 1)
end,
IsShowHeader = case when ([Row#] - 1) % 9 = 0
then 1 else 0 end
You can try this:
SELECT M.id,
M.HeaderText,
CASE WHEN M.HeaderText IS NOT NULL THEN 1 ELSE 0 END AS IsShowHeader
FROM
(
SELECT P.id,
CASE
WHEN P.HeaderText IS NOT NULL THEN
P.HeaderText + CAST(P.IndexNumber AS VARCHAR(10))
ELSE
NULL
END AS HeaderText
FROM
(
SELECT K.id,
HeaderText,
COUNT(K.HeaderText) OVER (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS IndexNumber
FROM
(
SELECT id,
CASE
WHEN (id - 1) % 9 = 0 THEN 'Header' ELSE NULL
END AS HeaderText
FROM dbo.test
) AS K
) AS P
) AS M;
I have illustrate the scenario step by step, however you can rewrite it in a simple way like the answer that #Squirrel posted.

How can I set the value of a field in a SELECT based on a parameter?

What I would like is to have a SELECT that sets the value of a field called Current based on the value of the table and of two variables which are declare as inputs to a stored procedure with the select as follows:
DECLARE #AdminTestId INT = 111
DECLARE #UserTestId INT = null
AdminTestId UserTestId Current
111 1
111 1
222 0
333 0
DECLARE #AdminTestId INT = 111
DECLARE #UserTestId INT = 457
AdminTestId UserTestId Current
111 456 0
111 457 1
123 0
Some help and advice would be appreciated.
It looks like you've got some logic where Current should only be set to 1 if UserTestId is null OR matches the UserTestId column. You can use a case statement in your SQL query like this:
DECLARE #AdminTestId INT = 111
DECLARE #UserTestId INT = 457
SELECT
AdminTestId,
UserTestId
CASE
WHEN (#AdminTestId IS NULL AND #UserTestId IS NULL) THEN 0
WHEN (#AdminTestId = AdminTestId AND #UserTestId = UserTestId) THEN 1
WHEN (#AdminTestId = AdminTestId AND #UserTestId IS NULL) THEN 1
WHEN (#AdminTestId IS NULL AND #UserTestId = UserTestId) THEN 1
ELSE 0
END AS IsCurrent
SELECT
...
CAST(CASE
WHEN t.AdminTestID = #AdminTestID
AND (t.UserTestID = #UserTestID
OR #UserTestID is NULL AND t.UserTestID is NULL)
THEN 1
ELSE 0
END as bit) as Current
FROM ...

Formulating Code and SQL Query

I have a Table in DB named 'Retail'
CustomerID Itemset
1 1
1 3
1 7
2 6
2 7
3 4
... ...
I want to write this table in Datatable 'Matrix' where the Rows are Itemset={1,2,3,4,5,6....,k} and Columns are CustomerID={1,2,3,4,...,x}
and the rows are 1 if the 'Itemset' belongs to the CustomerID.
The output I want is like..
1 2 3 4 5 6 7 ............. x
1 1 0 1 0 0 0 1 0 0 0 0 0 0 0 0
2 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0
3 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0
4
5
6 /And so on!
7
8
9
`
I tried to code it but the problem is in VS Parameters for SQL Query don't work with Loops.
This is my code-
objConnection.Open()
Dim matrix As DataTable = New DataTable("Retail")
intcount = 0
For intcount = 1 To noofCustomerID_col
matrix.Columns.Add(intcount, GetType(Integer))
Next
Dim i As Integer
Dim workRow As DataRow
Dim Boolin As Boolean
For i = 1 To 9
ObjCommand.CommandText = "Select count(*) from Retail Where CustomerID=#in and Itemset=#in"
ObjCommand.Parameters.AddWithValue("#in", i) {{I Get Error here as I can't Loop Parameters}}
Boolin = ObjCommand.ExecuteScalar()
workRow = matrix.NewRow()
workRow(0) = i
workRow(1) = Boolin
matrix.Rows.Add(workRow)
Next
Kindly Help. I know this code can completely be wrong and it's okay if you can suggest totally different way of doing it. I've been stuck for quite some time now! Thanks.
If any clarification is needed I shall explain as many times needed in the comments.
Like Tab Alleman suggested, SQL PIVOT can come handy here.
Dynamic SQL is also needed.
DECLARE #DynamicColumn NVARCHAR(MAX),
#DynamicQuery NVARCHAR(MAX)
SELECT #DynamicColumn = SELECT '[' + CONVERT(NVARCHAR, CustomerID) + '],'
FROM Retail AS Customer
ORDER BY CustomerID
FOR XML PATH('')
SELECT #DynamicColumn = SUBSTRING(#DynamicColumn, 0, DATALENGTH(#DynamicColumn) / 2)--REMOVE EXTRA ","
SELECT #DynamicQuery = 'SELECT *
FROM ( SELECT CustomerID,
ItemSet
FROM Retail) AS D
PIVOT
(
COUNT([CustomerID])
FOR Itemset IN (' + #DynamicQuery + ')
) AS P'
EXECUTE(#DynamicQuery)

How could I create combinations of removed dashes in SQL Server string?

I've been looking for a table-valued function for SQL Server that can do the following:
Input string: A-B-C-D (or any length string with dash-separated characters, like XX-W2-ZZZ-AAA-777-888)
Output strings (all combinations of removed dashes):
ABCD, A-BCD, AB-CD, ABC-D, A-B-CD, AB-C-D, A-B-C-D
I've noticed the pattern follows a binary counter with respect to which dash should be removed to generate the combinations. In the example above, you could remove the dashes associated with the 0 positions of 000, 001, 010, 011, 100, 101, 110, and 111. However I don't see how to do this in SQL Server. Have any of you tackled this challenge before? Thank you!
For this first section, I'm going to split the string into table / columns using XML.
DECLARE #Test TABLE
( ID INT,
NAME VARCHAR(MAX)
)
INSERT INTO #Test
VALUES( 1, 'XX-W2-ZZZ-AAA-777-888' )
DECLARE #ColSplit TABLE
(
Id int,
Col1 VARCHAR(MAX),
Col2 VARCHAR(MAX),
Col3 VARCHAR(MAX),
Col4 VARCHAR(MAX),
Col5 VARCHAR(MAX),
Col6 VARCHAR(MAX),
Col7 VARCHAR(MAX),
Col8 VARCHAR(MAX)
)
;WITH FormSplitXML
AS
(
Select Id, Name,
CONVERT(XML,'<r><n>' + REPLACE(Name, '-', '</n><n>') + '</n></r>') AS X
FROM #Test
)
INSERT INTO #ColSplit
SELECT Id,
i.value('n[1]','varchar(100)') AS Col1,
i.value('n[2]','varchar(100)') AS Col2,
i.value('n[3]','varchar(100)') AS Col3,
i.value('n[4]','varchar(100)') AS Col4,
i.value('n[5]','varchar(100)') AS Col5,
i.value('n[6]','varchar(100)') AS Col6,
i.value('n[7]','varchar(100)') AS Col7,
i.value('n[8]','varchar(100)') AS Col8
FROM FormSplitXML Spt
CROSS APPLY Spt.X.nodes('/r') x(i)
This forms a table with this output:
SELECT * FROM #ColSplit
Id Col1 Col2 Col3 Col4 Col5 Col6 Col7 Col8
1 XX W2 ZZZ AAA 777 888 NULL NULL
Next I am going to make a bit array table of all of the possible bit combinations:
DECLARE #BitTable TABLE
(
v int,
V1 BIT,
V2 BIT,
V3 BIT,
V4 BIT,
V5 BIT,
V6 BIT,
V7 BIT,
V8 BIT
)
Declare #t table (v integer not null primary key, check(v >= 0));
;WITH
a AS (SELECT 1 AS i UNION ALL SELECT 1),
b AS (SELECT 1 AS i FROM a AS x, a AS y),
c AS (SELECT 1 AS i FROM b AS x, b AS y),
d AS (SELECT 1 AS i FROM c AS x, c AS y),
e AS (SELECT 1 AS i FROM d AS x, d AS y),
f AS (SELECT 1 AS i FROM e AS x, e AS y),
numbers AS
(
SELECT TOP(255)
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS number
FROM f
)
Insert into #t
SELECT number FROM numbers
INSERT INTO #BitTable
( v, V1, V2, V3, V4, V5, V6, V7, V8 )
SELECT
v,
CONVERT(BIT, v & 1) AS V1,
CONVERT(BIT, v & 2) AS V2,
CONVERT(BIT, v & 4) AS V3,
CONVERT(BIT, v & 8) AS V4,
CONVERT(BIT, v & 16) AS V5,
CONVERT(BIT, v & 32) AS V6,
CONVERT(BIT, v & 64) AS V7,
CONVERT(BIT, v & 128) AS V8
FROM
#t
Here is the output of the bittable (only the first 10 rows, it keeps going to 255)
SELECT * FROM #BitTable
v V1 V2 V3 V4 V5 V6 V7 V8
1 1 0 0 0 0 0 0 0
2 0 1 0 0 0 0 0 0
3 1 1 0 0 0 0 0 0
4 0 0 1 0 0 0 0 0
5 1 0 1 0 0 0 0 0
6 0 1 1 0 0 0 0 0
7 1 1 1 0 0 0 0 0
8 0 0 0 1 0 0 0 0
9 1 0 0 1 0 0 0 0
10 0 1 0 1 0 0 0 0
Now using the bit table and the split columns, I am going to put a string together of all of the possible combinations:
SELECT
bt.*,
t.*,
CASE WHEN bt.V1 = 1 THEN ISNULL(t.Col1,'') + '-' ELSE t.Col1 END +
CASE WHEN bt.V2 = 1 THEN ISNULL(t.Col2,'') + '-' ELSE ISNULL(t.Col2,'') END +
CASE WHEN bt.V3 = 1 THEN ISNULL(t.Col3,'') + '-' ELSE ISNULL(t.Col3,'') END +
CASE WHEN bt.V4 = 1 THEN ISNULL(t.Col4,'') + '-' ELSE ISNULL(t.Col4,'') END +
CASE WHEN bt.V5 = 1 THEN ISNULL(t.Col5,'') + '-' ELSE ISNULL(t.Col5,'') END +
CASE WHEN bt.V6 = 1 THEN ISNULL(t.Col6,'') + '-' ELSE ISNULL(t.Col6,'') END +
CASE WHEN bt.V7 = 1 THEN ISNULL(t.Col7,'') + '-' ELSE ISNULL(t.Col7,'') END +
CASE WHEN bt.V8 = 1 THEN ISNULL(t.Col8,'') + '-' ELSE ISNULL(t.Col8,'') END
FROM #BitTable bt
CROSS JOIN #ColSplit t
Here is the output (snipped to 10 rows, it goes to 255):
v V1 V2 V3 V4 V5 V6 V7 V8 Id Col1 Col2 Col3 Col4 Col5 Col6 Col7 Col8 (No column name)
1 1 0 0 0 0 0 0 0 1 XX W2 ZZZ AAA 777 888 NULL NULL XX-W2ZZZAAA777888
2 0 1 0 0 0 0 0 0 1 XX W2 ZZZ AAA 777 888 NULL NULL XXW2-ZZZAAA777888
3 1 1 0 0 0 0 0 0 1 XX W2 ZZZ AAA 777 888 NULL NULL XX-W2-ZZZAAA777888
4 0 0 1 0 0 0 0 0 1 XX W2 ZZZ AAA 777 888 NULL NULL XXW2ZZZ-AAA777888
5 1 0 1 0 0 0 0 0 1 XX W2 ZZZ AAA 777 888 NULL NULL XX-W2ZZZ-AAA777888
6 0 1 1 0 0 0 0 0 1 XX W2 ZZZ AAA 777 888 NULL NULL XXW2-ZZZ-AAA777888
7 1 1 1 0 0 0 0 0 1 XX W2 ZZZ AAA 777 888 NULL NULL XX-W2-ZZZ-AAA777888
8 0 0 0 1 0 0 0 0 1 XX W2 ZZZ AAA 777 888 NULL NULL XXW2ZZZAAA-777888
9 1 0 0 1 0 0 0 0 1 XX W2 ZZZ AAA 777 888 NULL NULL XX-W2ZZZAAA-777888
10 0 1 0 1 0 0 0 0 1 XX W2 ZZZ AAA 777 888 NULL NULL XXW2-ZZZAAA-777888
Here you go, hope it helps.
I was trying to find something that did not involve multiple loops and would be more set-based. A CROSS JOIN is good for combinations as that is what a Cross Join / Cartesian-product is. But I still had to resort to Dynamic SQL due to the variable nature of how many dashes there might be. "But you can't do Dynamic SQL in a function!" I keep hearing that, yet I am not entirely convinced due to the following SQLCLR TVF. It constructs a query that, for the example input of A-B-C-D, looks like:
SELECT CONCAT(tab1.part, tab2.part, tab3.part, tab4.part) AS [Combinations]
FROM (SELECT N'A') tab1(part)
CROSS JOIN (SELECT N'B' UNION ALL SELECT N'-B') tab2(part)
CROSS JOIN (SELECT N'C' UNION ALL SELECT N'-C') tab3(part)
CROSS JOIN (SELECT N'D' UNION ALL SELECT N'-D') tab4(part)
This dynamic construction makes use of the pattern of combinations being:
FirstElement + {Cartesian Product of no-dash and preceding-dash versions of remaining elements}
The .Net / C# code:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Text;
using Microsoft.SqlServer.Server;
public class TVF
{
public static void ReturnCombo(object Combination, out SqlString Combo)
{
Combo = (string)Combination;
}
[Microsoft.SqlServer.Server.SqlFunction(FillRowMethodName = "ReturnCombo",
TableDefinition = "Combo NVARCHAR(500)", DataAccess = DataAccessKind.Read)]
public static IEnumerable GetCombinations([SqlFacet(MaxSize = -1)] SqlString DashedThingy)
{
List<string> _Combinations = new List<string>();
int _PartNum = 0;
StringBuilder _FirstPart = new StringBuilder("SELECT CONCAT(tab1.part");
StringBuilder _SecondPart = new StringBuilder(") AS [Combinations]\n");
foreach (string _Part in DashedThingy.Value.Split(new char[1] { '-' }))
{
_PartNum++;
if (_PartNum == 1)
{
_SecondPart.Append("FROM (SELECT N'").Append(_Part).AppendLine("') tab1(part)");
}
else
{
_FirstPart.Append(", tab").Append(_PartNum).Append(".part");
_SecondPart.Append("CROSS JOIN (SELECT N'").Append(_Part);
_SecondPart.Append("' UNION ALL SELECT N'-").Append(_Part);
_SecondPart.Append("') tab").Append(_PartNum).AppendLine("(part)");
}
}
SqlConnection _Connection = new SqlConnection("Context Connection = true;");
SqlCommand _Command = new SqlCommand();
_Command.Connection = _Connection;
_Command.CommandType = CommandType.Text;
_Command.CommandText = _FirstPart.ToString() + _SecondPart.ToString();
SqlDataReader _Reader = null;
try
{
_Connection.Open();
_Reader = _Command.ExecuteReader();
while (_Reader.Read())
{
_Combinations.Add(_Reader.GetString(0));
}
}
catch
{
throw;
}
finally
{
if (_Reader != null && !_Reader.IsClosed)
{
_Reader.Close();
}
if (_Connection != null && _Connection.State != ConnectionState.Closed)
{
_Connection.Close();
}
}
return _Combinations;
}
}
And the SQL to create it is:
CREATE ASSEMBLY [GetCombinations]
AUTHORIZATION [dbo]
FROM 'C:\path\to\DLL'
GO
CREATE FUNCTION [dbo].[GetCombinations]
(#DashedThingy NVARCHAR (MAX))
RETURNS TABLE ([Combo] NVARCHAR (500) NULL)
AS EXTERNAL NAME [GetCombinations].[TVF].[GetCombinations];
This is fully dynamic so it can handle any number of dashes. It finds the missing combination that is not in the example output (A-BC-D) and handles the other example easily:
SELECT * FROM dbo.GetCombinations('XX-W2-ZZZ-AAA-777-888');
Returns:
Combo
---------
XXW2ZZZAAA777888
XXW2-ZZZAAA777888
XXW2ZZZ-AAA777888
XXW2-ZZZ-AAA777888
XXW2ZZZAAA-777888
XXW2-ZZZAAA-777888
XXW2ZZZ-AAA-777888
XXW2-ZZZ-AAA-777888
XX-W2ZZZAAA777888
XX-W2-ZZZAAA777888
XX-W2ZZZ-AAA777888
XX-W2-ZZZ-AAA777888
XX-W2ZZZAAA-777888
XX-W2-ZZZAAA-777888
XX-W2ZZZ-AAA-777888
XX-W2-ZZZ-AAA-777888
XXW2ZZZAAA777-888
XXW2-ZZZAAA777-888
XXW2ZZZ-AAA777-888
XXW2-ZZZ-AAA777-888
XXW2ZZZAAA-777-888
XXW2-ZZZAAA-777-888
XXW2ZZZ-AAA-777-888
XXW2-ZZZ-AAA-777-888
XX-W2ZZZAAA777-888
XX-W2-ZZZAAA777-888
XX-W2ZZZ-AAA777-888
XX-W2-ZZZ-AAA777-888
XX-W2ZZZAAA-777-888
XX-W2-ZZZAAA-777-888
XX-W2ZZZ-AAA-777-888
XX-W2-ZZZ-AAA-777-888
This is a method I found to work inside MSSQL and accept any count of dashes. Main table-value function:
-- =============================================
-- Description: Outputs all possible combinations of dash and no dash in a string
-- Test: Select * From dbo.[fnDashCombinations]('A')
-- Test: Select * From dbo.[fnDashCombinations]('A-B')
-- Test: Select * From dbo.[fnDashCombinations]('AB-CD-EF-11-22')
-- =============================================
ALTER FUNCTION [dbo].[fnDashCombinations]
(
#InputText VARCHAR(50)
)
RETURNS #output TABLE(ModelName VARCHAR(50))
BEGIN
--Get the count of dashes
DECLARE #DashCount INT
SET #DashCount = (select len(#InputText) - len(replace(#InputText, '-', '')))
--Count through the dashes
DECLARE #OuterIterator INT
DECLARE #InnerIterator INT
DECLARE #OuterIteratorLimit INT
DECLARE #DashesAsBinary VARCHAR(50)
DECLARE #DashLocation INT
DECLARE #TextTemp VARCHAR(50)
SET #OuterIteratorLimit = POWER(2, #DashCount) - 1
SET #OuterIterator = 0
WHILE #OuterIterator < #OuterIteratorLimit
BEGIN
--Convert the dash count into the equivalent binary string
SET #TextTemp = #InputText
SET #DashesAsBinary = dbo.fnBinaryString(#OuterIterator, #DashCount)
SET #DashLocation = 0
SET #InnerIterator = 0
--Loop thru #DashesAsBinary and remove the dash if there's a zero
WHILE #InnerIterator < #DashCount
BEGIN
SET #DashLocation = CHARINDEX('-', #TextTemp, #DashLocation + 1)
IF SUBSTRING(#DashesAsBinary, #InnerIterator + 1, 1) = '0'
BEGIN --Replace with underscore for now to keep string length constant
SET #TextTemp = dbo.fnReplaceCharAtPos(#TextTemp, #DashLocation, '_')
END
SET #InnerIterator = #InnerIterator + 1
END
INSERT INTO #output SELECT REPLACE(#TextTemp, '_', '') --Finally remove extra chars
SET #OuterIterator = #OuterIterator + 1
END
RETURN
END
Additional scalar function that is called to convert the iterator into a binary string, like 3 = '011' (found most of this code elsewhere):
ALTER FUNCTION [dbo].[fnBinaryString] (#IncomingNumber int, #MinChars int)
RETURNS varchar(200)
as
BEGIN
DECLARE #BinNumber VARCHAR(200)
SET #BinNumber = ''
WHILE #IncomingNumber <> 0
BEGIN
SET #BinNumber = SUBSTRING('0123456789', (#IncomingNumber % 2) + 1, 1) + #BinNumber
SET #IncomingNumber = #IncomingNumber / 2
END
if (LEN(#BinNumber) < #MinChars)
SET #BinNumber = REPLICATE('0', #MinChars - LEN(#BinNumber)) + #BinNumber
RETURN #BinNumber
END
Additional scalar function that is called to replace a character at a position, used in this case to replace a dash with an underscore (found this code elsewhere):
ALTER FUNCTION [dbo].[fnReplaceCharAtPos](#Str varchar(8000),#Pos int, #Chr char(1))
RETURNS varchar(8000) AS
BEGIN
declare #Res varchar(8000)
set #Res=left(#Str,#Pos-1) + #Chr + right(#Str,len(#Str)-#Pos)
return #Res
END

Resources