SQL server varchar.toArray() or string.toArray() method - sql

I want to convert my string data to array in sql server.
I tried it like below.
SELECT '223456789' AS SerialOriginalCode
-- SerialOriginalCode 223456789
DECLARE #tbl_SerialOriginalVerion TABLE(ID INT, SerialOriginalCode INT);
INSERT INTO #tbl_SerialOriginalVerion VALUES
(1, 2),
(2, 2),
(3, 3),
(4, 4),
(5, 5),
(6, 6),
(7, 7),
(8, 8),
(9, 9);
SELECT * FROM #tbl_SerialOriginalVerion
But it is manual way, it is not the way of programmatic convertion as it needs me to key in every insert value for each line.
Could someone please suggest me more elegent way?

DECLARE #InputText AS VARCHAR(MAX) = '223456789'
DECLARE #Pos Int = 1
DECLARE #End Int
DECLARE #TextLength Int = DATALENGTH(#InputText)
DECLARE #Array TABLE
(
TokenID Int PRIMARY KEY IDENTITY(1,1),
Match Varchar(MAX)
)
-- Exit function if no text is passed in.
IF #TextLength <> 0
BEGIN
WHILE #Pos <= #TextLength BEGIN
INSERT #Array (Match) VALUES (SUBSTRING(#InputText,#Pos,1))
SET #Pos = #Pos + 1
END
END
SELECT * FROM #Array

Try this INSERT using number from master..spt_values
SELECT '223456789' AS SerialOriginalCode
-- SerialOriginalCode 223456789
DECLARE #tbl_SerialOriginalVerion TABLE(ID INT, SerialOriginalCode INT);
INSERT INTO #tbl_SerialOriginalVerion
SELECT number + 1, SUBSTRING(t.SerialOriginalCode, sv.number + 1, 1)
FROM (SELECT '223456789' AS SerialOriginalCode) t
INNER JOIN master..spt_values sv ON sv.number < LEN(t.SerialOriginalCode)
WHERE sv.[type] = 'P'
SELECT * FROM #tbl_SerialOriginalVerion
Output:
ID SerialOriginalCode
1 2
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9

I would recommend that you do this in your application. SQL is optimised for Set based operations and does not handle recursive procedural tasks like like these.
If you want to do this in SQL, you can find the LEN of your string and then recursively find the next character. Something like this
Query
DECLARE #SerialOriginalCode VARCHAR(20) = '223456789'
DECLARE #tbl_SerialOriginalVerion TABLE(ID INT, SerialOriginalCode INT);
DECLARE #len INT = LEN(#SerialOriginalCode)
;WITH CTE as
(
SELECT 1 as ID,CONVERT(INT,SUBSTRING(#SerialOriginalCode,1,1)) as CharInt
UNION ALL
SELECT ID + 1,CONVERT(INT,SUBSTRING(#SerialOriginalCode,ID + 1,1))
FROM CTE WHERE LEN(#SerialOriginalCode) >= ID + 1
)
INSERT INTO #tbl_SerialOriginalVerion(ID,SerialOriginalCode)
SELECT * FROM CTE;
SELECT * FROM #tbl_SerialOriginalVerion
Output
ID SerialOriginalCode
1 2
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9

With cte and Apply:
Declare #s nvarchar(9) = '223456789'
;with cte as(
select n from (values(1),(2),(3),(4),(5),(6),(7),(8),(9)) as t(n))
select * from cte
cross apply(select substring(#s, cte.n, 1) as c) ca
Output:
n c
1 2
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
Or you can do it with tally tables:
declare #s varchar(100) = '223456789'
;with t as(select row_number() over(order by (select null)) rn from
(values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) t1(n) cross join
(values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) t2(n))
select substring(#s, rn, 1) from t
where rn <= len(#s)
Fiddle http://sqlfiddle.com/#!3/9eecb7db59d16c80417c72d1/570

Related

How to insert string value into SQL Server table by separating by comma

int QcEditorId = 21
string Freelancer = '2,3,4,5'
I want to insert this value into the SQL Server table like
QcEditorId Freelancer
----------------------
21 2
21 3
21 4
21 5
Please help me with a query or stored procedure
String_split starting in SQL server 2016 have ability to split your strings
Example:
declare #table table (QcEditorId int, Freelancer varchar (100))
insert into #table
select 21, '2,3,4,5'
declare #freelancer varchar(100)
= (select freelancer from #table)
select QcEditorId,x.value Name from #table
cross apply(
select * from string_split(#freelancer,',') ) x
-- Use this function to split strings and , as delimiter
-- or for previous versions, create a table valued function , a lot
----available in web
Example:
CREATE FUNCTION [dbo].[splitstring] ( #stringToSplit VARCHAR(MAX) )
RETURNS
#returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN
DECLARE #name NVARCHAR(255)
DECLARE #pos INT
WHILE CHARINDEX(',', #stringToSplit) > 0
BEGIN
SELECT #pos = CHARINDEX(',', #stringToSplit)
SELECT #name = SUBSTRING(#stringToSplit, 1, #pos-1)
INSERT INTO #returnList
SELECT #name
SELECT #stringToSplit = SUBSTRING(#stringToSplit, #pos+1, LEN(#stringToSplit)-#pos)
END
INSERT INTO #returnList
SELECT #stringToSplit
RETURN
END
Solution:
declare #table table (QcEditorId int, Freelancer varchar (100))
insert into #table
select 21, '2,3,4,5'
declare #freelancer varchar(100)
= (select freelancer from #table)
select QcEditorId,x.Name from #table
cross apply(
select * from [dbo].[SplitString](#freelancer) ) x
DECLARE #FLXML AS XML
SET #FLXML = cast(('<a>'+replace(#FreelancerId,',' ,'</a><a>')
+'</a>') AS XML)
INSERT INTO [QMT_UserMaping](QcEditor_ID,Freelancer_ID)
SELECT #QCId,A.VALUE('.', 'varchar(max)')
FROM #FLXML.nodes('a') AS FN(a)
SQL User Defined Split Function
ALTER FUNCTION [dbo].[fnSplitString]
(
#string NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #output TABLE(splitdata NVARCHAR(MAX))
BEGIN
DECLARE #start INT, #end INT
SELECT #start = 1, #end = CHARINDEX(#delimiter, #string)
WHILE #start < LEN(#string) + 1 BEGIN
IF #end = 0
SET #end = LEN(#string) + 1
INSERT INTO #output (splitdata)
VALUES(SUBSTRING(#string, #start, #end - #start))
SET #start = #end + 1
SET #end = CHARINDEX(#delimiter, #string, #start)
END
RETURN
END
INSERT Command
INSERT INTO YourTable(QcEditorId,Freelancer)
SELECT 21,splitdata FROM [dbo].[fnSplitString]('2,3,4,5' ,',' )
STRING_SPLIT (MSSQL Server 2016)
no need custom user defined function
INSERT INTO YourTable(QcEditorId,Freelancer)
SELECT 21,value FROM STRING_SPLIT('2,3,4,5' ,',' )
I have a SQL Table like this:
| SomeID | OtherID | Data
+----------------+-------------+-------------------
| abcdef-..... | cdef123-... | 18,20,22
| abcdef-..... | 4554a24-... | 17,19
| 987654-..... | 12324a2-... | 13,19,20
Is there a query where I can perform a query like SELECT OtherID, SplitData WHERE SomeID = 'abcdef-.......' that returns individual rows, like this?
| OtherID | SplitData
+-------------+-------------------
| cdef123-... | 18
| cdef123-... | 20
| cdef123-... | 22
| 4554a24-... | 17
| 4554a24-... | 19
Basically split my data at the comma into individual rows?
create table Testdata(SomeID int, OtherID int, Data varchar(max))
insert Testdata select 1, 9, '18,20,22'
insert Testdata select 2, 8, '17,19'
insert Testdata select 3, 7, '13,19,20'
insert Testdata select 4, 6, ''
--The query
;with tmp(SomeID, OtherID, DataItem, Data) as (
select SomeID, OtherID, LEFT(Data, CHARINDEX(',',Data+',')-1),
STUFF(Data, 1, CHARINDEX(',',Data+','), '')
from Testdata
union all
select SomeID, OtherID, LEFT(Data, CHARINDEX(',',Data+',')-1),
STUFF(Data, 1, CHARINDEX(',',Data+','), '')
from tmp
where Data > ''
)
select SomeID, OtherID, DataItem
from tmp
order by SomeID
-- OPTION (maxrecursion 0)
-- normally recursion is limited to 100. If you know you have very long
-- strings, uncomment the option
Output
SomeID OtherID DataItem
1 9 18
1 9 20
1 9 22
2 8 17
2 8 19
3 7 13
3 7 19
3 7 20
4 6
9 11 1
9 11 2
9 11 3
9 11 4
etc.

Split set of values into 5 groups each group should have sum(count) evenly

SQL Server to split set of values into 5 groups each group should have sum(count) evenly distributed.
Table contains only 2 columns rid and count.
create table t1(rid int, count int)
insert into t1
values (1, 4567), (2, 3256), (3, 5678), (4, 934),
(5, 1099), (6, 3990), (7, 780), (8, 6784),
(9, 7854), (10, 435), (11, 3455), (12, 4897),
(13, 8849), (14, 1019), (15, 2387)
Actual table is
rid count
---------
1 4567
2 3256
3 5678
4 934
5 1099
6 3990
7 780
8 6784
9 7854
10 435
11 3455
12 4897
13 8849
14 1019
15 2387
I need to divide the values into into 5 groups dynamically, each group should have sum(count) evenly distributed
The sum of the columns is equivalent to 55500. I need to divide the sum by 55500/5=11100. we need to divide the values in to 5 groups, each group should have sum(count) evenly distributed, equivalent to 11110 (approximately)
I would start with 5 randomly chosen groups:
select t.*,
ntile(5) over (order by newid()) as grp
from t;
The sums should be pretty close. If you have a lot of records and the counts are reasonably distributed, then an nth sample usually does a pretty good job:
select t.*,
(row_number() over (order by count) % 5) as grp
from t;
If you have a situation where you have very disparate sizes for count and you need the optimal solution, then you have a hard problem.
You can try this script.
;WITH CTE AS (
SELECT * ,
RN = ROW_NUMBER() OVER (ORDER BY [count] DESC)
FROM T1
)
,CTE2 AS (
SELECT *,
RN2 = ROW_NUMBER() OVER(ORDER BY CEILING( RN / 5.00 ), (( 1 - CEILING( RN / 5.00 )) * [COUNT] ) DESC )
FROM CTE
)
SELECT
CTE2.rid,
CTE2.[count],
((RN2+1)%5) +1 GroupIndex,
SUM(CTE2.[count]) OVER (PARTITION BY ((RN2+1)%5)) CmlTotal
FROM CTE2
Result:
rid count GroupIndex CmlTotal
----------- ----------- -------------------- -----------
3 5678 1 10687
6 3990 1 10687
14 1019 1 10687
5 1099 2 10563
1 4567 2 10563
12 4897 2 10563
15 2387 3 11671
10 435 3 11671
13 8849 3 11671
9 7854 4 11890
7 780 4 11890
2 3256 4 11890
11 3455 5 11173
4 934 5 11173
8 6784 5 11173
Here's my go at it, what I've done is created a temp table with an identity column and an extra column ([Group]). The numbers are inserted in descending size order. Then, I've written a LOOP which inserts Groups 1 to 5 next to the largest 5 numbers in the [Group] column, then flips and inserts Groups 5 to 1 against the next 5 largest numbers, then it flips again and so on until it reaches the end of the table.
CREATE TABLE #T1
(
RID INT IDENTITY(1,1),
[Count] INT,
[Group] INT
)
INSERT INTO #T1 ([Count])
SELECT [Count] FROM T1 ORDER BY [Count] DESC
GO
DECLARE #ROWCOUNT INT = 1
WHILE #ROWCOUNT <= (SELECT MAX(RID) FROM #T1)
BEGIN
DECLARE #COUNT INT = (SELECT TOP 1 [COUNT]
FROM #T1 WHERE [GROUP] IS NULL ORDER BY [COUNT] DESC)
DECLARE #GROUP INT = 1
WHILE #GROUP <=5
BEGIN
UPDATE #T1 SET [GROUP] = #GROUP WHERE [COUNT] = #COUNT
SET #COUNT = (SELECT TOP 1 [COUNT] FROM #T1 WHERE [GROUP] IS NULL ORDER BY [COUNT] DESC)
SET #GROUP = #GROUP + 1
SET #ROWCOUNT = #ROWCOUNT +1
END
SET #GROUP = #GROUP - 1
WHILE #GROUP > 0
BEGIN
UPDATE #T1 SET [GROUP] = #GROUP WHERE [COUNT] = #COUNT
SET #COUNT = (SELECT TOP 1 [COUNT] FROM #T1 WHERE [GROUP] IS NULL ORDER BY [COUNT] DESC)
SET #GROUP = #GROUP - 1
SET #ROWCOUNT = #ROWCOUNT +1
END
END
The code below just demonstrates the actual numbers in each of the five groups and also shows the variance away from the sum of numbers divided by five.
DECLARE #AVGROUP INT = (SELECT SUM([COUNT])/5 FROM #T1);
WITH CTE (SUMCOUNT) AS
(
SELECT SUM([COUNT]) FROM #T1 WHERE [GROUP] = 1
UNION
SELECT SUM([COUNT]) FROM #T1 WHERE [GROUP] = 2
UNION
SELECT SUM([COUNT]) FROM #T1 WHERE [GROUP] = 3
UNION
SELECT SUM([COUNT]) FROM #T1 WHERE [GROUP] = 4
UNION
SELECT SUM([COUNT]) FROM #T1 WHERE [GROUP] = 5
)
,
CTE1 (SUMCOUNT,VARIANCE) AS
(
SELECT SUMCOUNT,#AVGROUP-SUMCOUNT FROM CTE
)
SELECT * FROM CTE1
Is this accurate enough? In other words, does a variance range of 1274 seem to be evenly distributed enough with these numbers? I think it might be possible to make it more accurate if required, if this is accurate enough, then fine.
Below is a code which shows how the Groups are comprised:
DECLARE #AVGROUP INT = (SELECT SUM([COUNT])/5 FROM #T1);
WITH CTE ([GROUP],N1,N2,N3,SUMCOUNT) AS
(
SELECT '1',
(SELECT [COUNT] FROM #T1 WHERE RID = 1),
(SELECT [COUNT] FROM #T1 WHERE RID = 10),
(SELECT [COUNT] FROM #T1 WHERE RID = 11),
(SELECT SUM([COUNT]) FROM #T1 WHERE [GROUP] = 1)
UNION
SELECT '2',
(SELECT [COUNT] FROM #T1 WHERE RID = 2),
(SELECT [COUNT] FROM #T1 WHERE RID = 9),
(SELECT [COUNT] FROM #T1 WHERE RID = 12),
(SELECT SUM([COUNT]) FROM #T1 WHERE [GROUP] = 2)
UNION
SELECT '3',
(SELECT [COUNT] FROM #T1 WHERE RID = 3),
(SELECT [COUNT] FROM #T1 WHERE RID = 8),
(SELECT [COUNT] FROM #T1 WHERE RID = 13),
(SELECT SUM([COUNT]) FROM #T1 WHERE [GROUP] = 3)
UNION
SELECT '4',
(SELECT [COUNT] FROM #T1 WHERE RID = 4),
(SELECT [COUNT] FROM #T1 WHERE RID = 7),
(SELECT [COUNT] FROM #T1 WHERE RID = 14),
(SELECT SUM([COUNT]) FROM #T1 WHERE [GROUP] = 4)
UNION
SELECT '5',
(SELECT [COUNT] FROM #T1 WHERE RID = 5),
(SELECT [COUNT] FROM #T1 WHERE RID = 6),
(SELECT [COUNT] FROM #T1 WHERE RID = 15),
(SELECT SUM([COUNT]) FROM #T1 WHERE [GROUP] = 5)
)
,
CTE1 ([GROUP],N1,N2,N3,SUMCOUNT,VARIANCE) AS
(
SELECT [GROUP],N1,N2,N3,SUMCOUNT,#AVGROUP-SUMCOUNT FROM CTE
)
SELECT * FROM CTE1

Add column with row number

I want to add a column to my select showing a set of number from say 1 to 4.
Example:
Select * gives me
Id Transaction
1 10
2 11
3 12
4 13
5 14
6 15
I want to add a column called "Flow". The result should be like this.
Id Transaction Flow
1 10 1
2 11 2
3 12 3
4 13 4
5 14 1
6 15 2
In this example the flow is from 1-4. Could be 1-n.
No particular relation between Id and Flow is needed.
If you're using SQL Server or other DBMS that allows ROW_NUMBER, you could do this:
CREATE TABLE #Tbl(Id INT, [Transaction] INT);
INSERT INTO #Tbl VALUES
(1, 10), (2, 11), (3, 12), (4, 13), (5, 14), (6, 15);
DECLARE #N INT = 4;
SELECT *,
Flow = 1 + ((ROW_NUMBER() OVER(ORDER BY Id) - 1) % #N)
FROM #Tbl
DROP TABLE #Tbl;
If you are using mySql.
Query
set #r := 0;
select Id, `Transaction`,
#r := (#r % 4) + 1 as Flow
from your_table_name
order by Id;
Demo
EDIT
Following sql query can be used irrespective of rdbms.
Query
select *, (
select ((count(*) - 1) % 4) + 1 as Flow
from your_table_name t2
where t1.Id >= t2.Id
) as Flow
from your_table_name t1;

Running total with maximum limit

I have a simple question but its kinda difficult for me to solve it.
I would like to have sum up a specific column till it reached a limit and resets it self. (SQL 2012)
Lets say the limit is 50
- List item
- Value Total
- 10 10
- 20 30
- 30 60
- 40 50 (60-limit) + the current row value
- 2 2
- 3 5
- 10 15
- 25 40
- 15 55
- 5 10 (55-limit) + the current row value
Thank you very much.
This should do it if you have SQL Server 2012 or later:
DECLARE #Table TABLE (Id INT, ListItem INT);
INSERT INTO #Table VALUES (1, 10);
INSERT INTO #Table VALUES (2, 20);
INSERT INTO #Table VALUES (3, 30);
INSERT INTO #Table VALUES (4, 40);
INSERT INTO #Table VALUES (5, 2);
INSERT INTO #Table VALUES (6, 3);
INSERT INTO #Table VALUES (7, 10);
INSERT INTO #Table VALUES (8, 25);
INSERT INTO #Table VALUES (9, 15);
INSERT INTO #Table VALUES (10, 5);
WITH RunningTotal AS (
SELECT Id, ListItem, SUM(ListItem) OVER (ORDER BY Id) % 50 AS RT FROM #Table)
SELECT
rt.Id,
rt.ListItem,
CASE WHEN rt.RT < rt2.RT THEN rt.RT + 50 ELSE rt.RT END AS RunningTotal
FROM
RunningTotal rt
LEFT JOIN RunningTotal rt2 ON rt2.Id = rt.Id - 1
ORDER BY
rt.Id;
The tricky bit is allowing the numbers to overflow the 50 one time, otherwise this would be trivial.
Results are:
Id LI RunningTotal
1 10 10
2 20 30
3 30 60
4 40 50
5 2 2
6 3 5
7 10 15
8 25 40
9 15 55
10 5 10
create table running_totals
(
id int identity(1,1),
val int
)
insert into running_totals
select 1 union all
select 20 union all
select 10 union all
select 30 union all
select 50 union all
select 10 union all
select 11 union all
select 22 union all
select 40 union all
select 60 union all
select 20 union all
select 10 union all
select 15
declare cur_run_tot cursor for select id,val from running_totals order by id asc
declare #id int ,#val int,#runtot int
open cur_run_tot
create table #RunTot
(
id int,val int, runtot int
)
fetch next from cur_run_tot into #id,#val
while ##FETCH_STATUS = 0
begin
if #runtot is null or #runtot+#val > 50
set #runtot = #val
else
set #runtot = #runtot+ #val
insert into #RunTot
select #id,#val,#runtot
fetch next from cur_run_tot into #id,#val
end
select id as ID, val as Current_Value, runtot as Running_Total from #RunTot
drop table #RunTot
deallocate cur_run_tot

dynamic PIVOT query with SQL Server

I have the following table Account
Accountid Calcul
1 27+23+12
4 5+9+12
7 7+12+20
I am looking to get the following table AccountTemp
Accountid AccountCode
1 27
1 23
1 12
4 5
4 9
4 12
7 7
7 12
7 20
declare #account table(Accountid int, Calcul varchar(20))
--AccountTemp
insert #account values(1, '27+23+12')
insert #account values(4,'5+9+12')
insert #account values(7,'7+12+20')
create table #accounttemp(Accountid int, AccountCode int)
insert #accounttemp(Accountid, AccountCode)
SELECT Accountid, t.c.value('.', 'INT') AccountCode
FROM (
SELECT Accountid, CAST('<t>' +
REPLACE(Calcul, '+', '</t><t>') + '</t>' AS XML) x
FROM #account
) a
CROSS APPLY x.nodes('/t') t(c)
select * from #accounttemp
drop table #accounttemp
Result:
Accountid AccountCode
1 27
1 23
1 12
4 5
4 9
4 12
7 7
7 12
7 20
You could do this:
Create a split function like this:
CREATE FUNCTION [dbo].[Split]
(
#String NVARCHAR(4000),
#Delimiter NCHAR(1)
)
RETURNS TABLE
AS
RETURN
(
WITH Split(stpos,endpos)
AS(
SELECT 0 AS stpos, CHARINDEX(#Delimiter,#String) AS endpos
UNION ALL
SELECT endpos+1, CHARINDEX(#Delimiter,#String,endpos+1)
FROM Split
WHERE endpos > 0
)
SELECT 'Id' = ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
'Data' = SUBSTRING(#String,stpos,COALESCE(NULLIF(endpos,0),LEN(#String)+1)-stpos)
FROM Split
)
GO
Then do a query like this:
SELECT
tbl.Accountid,
split.AccountCode
FROM
#tbl AS tbl
CROSS APPLY
(
SELECT
CAST(calc.Data AS INT) AS AccountCode
FROM
dbo.split(tbl.Calcul,'+') as calc
) as split
Output:
Accountid AccountCode
1 27
1 23
1 12
4 5
4 9
4 12
7 7
7 12
7 20
Reference:
Split a string to a table using T-SQL
CROSS APPLY Explained