dynamic PIVOT query with SQL Server - sql

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

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.

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

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

SQL count ids in fields

I have a table contains IDs in field. It looks like:
FieldName
-------------------------
1,8,2,3,4,10,5,9,6,7
-------------------------
1,8
-------------------------
1,8
I need to count these IDs to get result:
ID | Count
---|------
1 | 3
8 | 3
2 | 1
3 | 1
Any ideas?
Thanks!
Try this :
Declare #demo table(FieldName varchar(100))
insert into #demo values('1,8,2,3,4,10,5,9,6,7')
insert into #demo values('1,8')
insert into #demo values('1,8')
select ID, COUNT(id) ID_count from
(SELECT
CAST(Split.a.value('.', 'VARCHAR(100)') AS INT) AS ID
FROM
(
SELECT CAST ('<M>' + REPLACE(FieldName, ',', '</M><M>') + '</M>' AS XML) AS ID
FROM #demo
) AS A CROSS APPLY ID.nodes ('/M') AS Split(a)) q1
group by ID
I like Devart's answer because of the faster execution. Here is a modified earlier answer to suite your need :
Declare #col varchar(200)
SELECT
#col=(
SELECT FieldName + ','
FROM #demo c
FOR XML PATH('')
);
;with demo as(
select cast(substring(#col,1,charindex(',',#col,1)-1) AS INT) cou,charindex(',',#col,1) pos
union all
select cast(substring(#col,pos+1,charindex(',',#col,pos+1)-pos-1)AS INT) cou,charindex(',',#col,pos+1) pos
from demo where pos<LEN(#col))
select cou ID, COUNT(cou) id_count from demo
group by cou
Try this one -
Query:
SET NOCOUNT ON;
DECLARE #temp TABLE (txt VARCHAR(8000))
INSERT INTO #temp (txt)
VALUES ('1,8,2,3,4,10,5,9,6,7'), ('1,8'), ('1,8')
SELECT
t.ID
, [Count] = COUNT(1)
FROM (
SELECT
ID =
SUBSTRING(
t.string
, number + 1
, ABS(CHARINDEX(',', t.string, number + 1) - number - 1)
)
FROM (
SELECT string = (
SELECT ',' + txt
FROM #temp
FOR XML PATH(N''), TYPE, ROOT).value(N'root[1]', N'NVARCHAR(MAX)')
) t
CROSS JOIN [master].dbo.spt_values n
WHERE [type] = 'p'
AND number <= LEN(t.string) - 1
AND SUBSTRING(t.string, number, 1) = ','
) t
GROUP BY t.ID
ORDER BY [Count] DESC
Output:
ID Count
----- -----------
1 3
8 3
9 1
10 1
2 1
3 1
4 1
5 1
6 1
7 1
Query cost:

Produce all row groups having a SUM between two values

I have table as below(products)
id name quality weight
1 Demir-1 ST-1 10
2 Demir-2 ST-2 7
3 Demir-3 ST-1 20
4 Demir-2 ST-3 8
5 Demir-1 ST-3 6
6 Demir-4 ST-2 10
7 Demir-2 ST-2 12
8 Demir-1 ST-1 15
9 Demir-1 ST-3 10
10 Demir-3 ST-3 5
11 Demir-2 ST-2 5
Now if a user wants to get list of products which sum of weight will between 20 and 25 and name='Result-2' then the result should be as below. All combination must be shown where sum of weight between 20 and 25.
id name quality weight
4 Demir-2 ST-3 8
7 Demir-2 ST-2 12
11 Demir-2 ST-2 5
------------------------------
2 Demir-2 ST-2 7
4 Demir-2 ST-3 8
11 Demir-2 ST-2 5
------------------------------
------------------------------
1 Demir-1 ST-1 10
8 Demir-1 ST-1 15
You can see that the sum(weight) is 25.
DECLARE #tblProducts TABLE(Id INT IDENTITY, ProductName varchar(50),Quality varchar(50), ProductWeight int)
INSERT INTO #tblProducts SELECT 'Demir-1','ST-1',10
INSERT INTO #tblProducts SELECT 'Demir-2','ST-2',7
INSERT INTO #tblProducts SELECT 'Demir-3','ST-1',20
INSERT INTO #tblProducts SELECT 'Demir-2','ST-3',8
INSERT INTO #tblProducts SELECT 'Demir-1','ST-3',6
INSERT INTO #tblProducts SELECT 'Demir-4','ST-2',10
INSERT INTO #tblProducts SELECT 'Demir-2','ST-2' ,12
INSERT INTO #tblProducts SELECT 'Demir-1','ST-1',15
INSERT INTO #tblProducts SELECT 'Demir-1','ST-3',10
INSERT INTO #tblProducts SELECT 'Demir-3','ST-3' ,5
INSERT INTO #tblProducts SELECT 'Demir-2' ,'ST-2 ',5
;WITH Cte (Id,ProductQuality,ProductIds,ProductNames,ProductQualities,ProductTotalWeight,ProductWeights,ProductName1,ProductName2) AS
(
SELECT Id
,Quality
, ',' + CAST(Id AS VARCHAR(MAX))
,',' + CAST(ProductName AS VARCHAR(MAX))
,',' + CAST(Quality AS VARCHAR(MAX))
,ProductWeight
, ',' + CAST(ProductWeight AS VARCHAR(MAX))
,ProductName
,CAST(ProductName AS VARCHAR(MAX))
FROM #tblProducts
UNION ALL
SELECT p.Id
, p.Quality
,c.ProductIds + ',' + CAST(p.Id AS VARCHAR(MAX))
,c.ProductNames + ',' + CAST(p.ProductName AS VARCHAR(MAX))
,c.ProductQualities + ',' + CAST(p.Quality AS VARCHAR(MAX))
,c.ProductTotalWeight + p.ProductWeight
,c.ProductWeights + ',' + CAST(p.ProductWeight AS VARCHAR(MAX))
,p.ProductName
,c.ProductName2
FROM #tblProducts AS p JOIN Cte c ON p.Id < c.Id
WHERE p.ProductName = c.ProductName2
)
SELECT
ProductIds = STUFF(ProductIds,1,1,'')
,ProductNames = STUFF(ProductNames,1,1,'')
,ProductQualities = STUFF(ProductQualities,1,1,'')
,ProductTotalWeight
,ProductWeights = STUFF(ProductWeights,1,1,'')
FROM CTE
WHERE ProductTotalWeight BETWEEN 20 AND 25
Result
ProductIds ProductNames ProductQualities ProductTotalWeight ProductWeights
3 Demir-3 ST-1 20 20
11,7,2 Demir-2,Demir-2,Demir-2 ST-2 ,ST-2,ST-2 24 5,12,7
11,7,4 Demir-2,Demir-2,Demir-2 ST-2 ,ST-2,ST-3 25 5,12,8
11,4,2 Demir-2,Demir-2,Demir-2 ST-2 ,ST-3,ST-2 20 5,8,7
10,3 Demir-3,Demir-3 ST-3,ST-1 25 5,20
9,1 Demir-1,Demir-1 ST-3,ST-1 20 10,10
9,8 Demir-1,Demir-1 ST-3,ST-1 25 10,15
8,1 Demir-1,Demir-1 ST-1,ST-1 25 15,10
8,5 Demir-1,Demir-1 ST-1,ST-3 21 15,6
7,4 Demir-2,Demir-2 ST-2,ST-3 20 12,8
Unfortunately, SQL is not the correct coding engine for coming up with all combinations from a table with a given calculation, as SQL code I hacked together shows.
Normally I would recommend this is coded in a procedural language given it's nature.
Note also that the solution here, with larger input data sets can quickly exhaust SQL's recursion limit. The number of possible products can also grow to sufficently large numbers that it will run very slowly.
drop table products
drop table #MinW
drop table #Products
go
create table products
(
id int,
name varchar(20),
quality varchar(20),
[weight] int
)
go
insert into products
select
1, 'Demir-1', 'ST-1', 10
union
select 2, 'Demir-2', 'ST-2', 7
union
select 3, 'Demir-3', 'ST-1', 20
union
select 4, 'Demir-2', 'ST-3', 8
union
select 5, 'Demir-1', 'ST-3', 6
union
select 6, 'Demir-4', 'ST-2', 10
union
select 7, 'Demir-2', 'ST-2', 12
union
select 8, 'Demir-1', 'ST-1', 15
union
select 9, 'Demir-1', 'ST-3', 10
union
select 10, 'Demir-3', 'ST-3', 5
union
select 11, 'Demir-2', 'ST-2', 5
go
select
p.id, p.name, p.quality, p.weight
into #MinW
from products p
inner join
(
select name,min(weight) as weight
from products group by name having min(weight) < 25
) as minW
on p.name = minW.name and p.weight = minW.weight
go
with weightProducts (id, name, [weight],history)
as
(
select id, name,[weight],convert(varchar(1024),'"'+convert(varchar(10),id)+'",') from #minW
union all
select products.id, products.name,products.[weight] + weightProducts.[weight],convert(varchar(1024),weightProducts.history+'"'+convert(varchar(10),products.id)+'",') from products, weightProducts where products.name = weightproducts.name and charindex('"'+convert(varchar(10),products.id)+'",',weightProducts.history)<=0 and (products.weight + weightProducts.weight) < 25
)
-- Statement using the CTE
select *
into #Products
from weightProducts
where weight between 20 and 25
go
select
p1.history,
p2.id,
p2.quality,
p2.weight,
p1.weight as totalWeight
from #Products p1
inner join
products p2 on p1.name = p2.name and charindex('"'+convert(varchar(10),p2.id)+'",',p1.history)>0
order by
p1.history,
p2.id
SELECT * FROM tablename
WHERE name IN (
SELECT name FROM tablename
GROUP BY name
HAVING SUM(weight) BETWEEN 20 AND 25)
Try this:
SELECT * FROM products
WHERE name IN (
SELECT name FROM products
GROUP BY name
HAVING SUM(weight) BETWEEN 20 AND 25)
How about this query:
select id, name, quality, weight from PRODUCTS
group by id, name, quality, weight
having sum(weight) between 20 and 25

Row_Number simulation in Sql server 2000

I have a sample input table as
Declare #input TABLE(Name VARCHAR(8))
INSERT INTO #input(Name) values('Aryan')
INSERT INTO #input(Name) values('Aryan')
INSERT INTO #input(Name) values('Joseph')
INSERT INTO #input(Name) values('Vicky')
INSERT INTO #input(Name) values('Jaesmin')
INSERT INTO #input(Name) values('Aryan')
INSERT INTO #input(Name) values('Jaesmin')
INSERT INTO #input(Name) values('Vicky')
INSERT INTO #input(Name) values('Padukon')
INSERT INTO #input(Name) values('Aryan')
INSERT INTO #input(Name) values('Jaesmin')
INSERT INTO #input(Name) values('Vick')
INSERT INTO #input(Name) values('Padukon')
INSERT INTO #input(Name) values('Joseph')
INSERT INTO #input(Name) values('Marya')
INSERT INTO #input(Name) values('Vicky')
Also I have a tally table as under
declare #t table(n int)
insert into #t select 1 union all select 2 union all
select 3 union all select 4 union all select 5 union all
select 6 union all select 7 union all select 8 union all
select 9 union all select 10 union all select 11 union all
select 12 union all select 13 union all select 14 union all
select 15 union all select 16 union all select 17 union all
select 18 union all select 19 union all select 20
In Sql Server 2005 if I do as
Select rn, name from (
select ROW_NUMBER()over (order by Name) as rn , * from #input) x
where rn % 2 <> 0
I get the output as
rn name
1 Aryan
3 Aryan
5 Jaesmin
7 Jaesmin
9 Joseph
11 Padukon
13 Vick
15 Vicky
Bu I am restricted to Sql server 2000. How can I get the same output?
I have tried with
SELECT name, (SELECT COUNT(*) FROM #input AS i2 WHERE i2.Name <= i1.Name) As rn
FROM #input AS i1
but the output is wrong
name rn
Aryan 4
Aryan 4
Joseph 9
Vicky 16
Jaesmin 7
Aryan 4
Jaesmin 7
Vicky 16
Padukon 12
Aryan 4
Jaesmin 7
Vick 13
Padukon 12
Joseph 9
Marya 10
Vicky 16
Declare your table variable as
Declare #input TABLE(_id int identity(1, 1), Name VARCHAR(8))
And then reqrite your query as
Select _id, name
from #input
where _id % 2 <> 0
Use this query:
SELECT t1.name, t.n
FROM
(
SELECT a.name, a.c, (SELECT COUNT(*) FROM #input AS i2 WHERE i2.Name <= a.Name) [rn]
FROM
(
SELECT i.name, count(*) c
FROM #input i
GROUP BY i.name
)a
)t1
JOIN #t t ON t.n <= t1.rn
WHERE t.n > t1.rn - t1.c
It produces desired output:
name n
-------- -----------
Aryan 1
Aryan 2
Aryan 3
Aryan 4
Jaesmin 5
Jaesmin 6
Jaesmin 7
Joseph 8
Joseph 9
Marya 10
Padukon 11
Padukon 12
Vick 13
Vicky 14
Vicky 15
Vicky 16