Convert into column using delimited PIPE in MS SQL - sql-server-2005

Present value
All (one column)
--
43 | 00043 | 22/09/2016 | Ooredoo - AuU TX | Raj singh
I want it should be in separate column like given below
ID Key Date Company Owner
43 00043 22/09/2016 Ooredoo - AuU TX Raj singh

You can use the following to do so and declare a delimiter at the beginning. Here is a sample to do it:
DECLARE #Delimiter VARCHAR(40)
SET #Delimiter = '|'
;WITH CTE AS
(
SELECT
CAST('<M>' + REPLACE([ColName], #Delimiter , '</M><M>') + '</M>' AS XML)
AS [ColName XML]
FROM [Table]
)
SELECT
[ColName XML].value('/M[1]', 'VARCHAR(40)') As [ID],
[ColName XML].value('/M[2]', 'VARCHAR(40)') As [Key],
[ColName XML].value('/M[3]', 'VARCHAR(40)') As [Date],
[ColName XML].value('/M[4]', 'VARCHAR(40)') As [Company],
[ColName XML].value('/M[5]', 'VARCHAR(40)') As [Owner]
FROM CTE

Related

SQL Merge duplicate rows

I want to change my data from this:
ID
Date
2245873
03-JAN
2245873
03-JAN
2245873
04-JAN
8394313
03-JAN
8394313
04-JAN
8394313
05-JAN
3446512
31-DEC
3446512
20-JAN
617828
31-DEC
617828
03-JAN
617828
20-JAN
61342
02-JAN
to this:
ID
date1
date2
date3
2245873
03-JAN
04-JAN
8394313
03-JAN
04-JAN
05-JAN
3446512
31-DEC
20-JAN
617828
31-DEC
03-JAN
20-JAN
61342
02-JAN
Remove the duplicate values for each ID (see ID=2245873),
List the dates associated with each ID in a row,
I don't know how many dates each ID has so the number of columns I need is unknown, is this possible?
I also need to be able to merge this new table with another, so it needs to be a view or alter table?
If there are no more dates associated with an ID I want the cell to be null
Table name: dbo.rem
I have taken the dynamic pivot columns formation part from the below answer
dynamic pivot query
The below logic should work
declare #tbl table(id int, date varchar(50))
insert into #tbl values(2245873,'03-Jan')
,(2245873,'03-Jan'),(2245873,'04-Jan')
,(8394313,'03-Jan'),(8394313,'05-Jan'),(8394313,'07-Jan')
select distinct * into #temp
--,ROW_NUMBER()over(order by id) rownum
from #tbl
-- This part is to generate row numbers and form the dates
select id,date,
'date' + convert(varchar,rownum) as 'datetobepivoted' into #temp1
from(
select *,ROW_NUMBER()over(partition by id order by id) rownum from #temp
)t
declare #pivotcolstbl varchar(200) = (STUFF((SELECT distinct ',isnull(' + QUOTENAME(c.datetobepivoted) + ', '''') ' + c.datetobepivoted
FROM #temp1 c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,''))
declare #pivotcols varchar(200) = (STUFF((SELECT distinct ',' + QUOTENAME(c.datetobepivoted)
FROM #temp1 c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,''))
declare #query varchar(max)
set #query = '
SELECT id, ' + #pivotcolstbl + ' from
(select id,date,datetobepivoted from #temp1)t
pivot(
max(date) for datetobepivoted in (' + #pivotcols + '))t1
'
exec(#query)
drop table #temp
drop table #temp1
Note: max aggregate function works on varchar too !
Does this work?
Trying to do a dynamic PIVOT similar to the other post linked above, but adding a field using 'Day' as a string and DENSE_RANK to determine the output columns (Date1, Date2, Date3...)
This is used both to set the #cols variable, and within the SELECT statement in brackets - where the resultant field is named [xdate].
When pivoted, it is these values that appear as column names alongside your original ID, then populated with specific dates relevent to that ID... hopefully!
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT DISTINCT ',' + QUOTENAME('Date'+ CAST(DENSE_RANK() OVER (PARTITION BY [ID] ORDER BY [date]) AS VARCHAR(MAX)) )
from sourcetable yt
group by [ID], [date]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT [ID],' + #cols + ' from
(
select [ID], [Date], ''Date''+ CAST(DENSE_RANK() OVER (PARTITION BY [ID] ORDER BY [date]) AS VARCHAR(MAX)) as [xdate]
from sourcetable yt
) x
pivot
(
MAX([date] )
for [xdate] in (' + #cols + ')
) p '
EXECUTE(#query)
GO

Selecting multiple columns to one string (by date)

My pricelist table looks like this:
ItemCode VendorCode UnitCost StartingDate
333 362 2.31 2016-08-19 00:00:00.0
333 362 2.16 2018-02-22 00:00:00.0
444 362 12.96 2014-01-09 00:00:00.0
444 362 13.10 2015-01-09 00:00:00.0
444 430 13.05 2017-04-01 00:00:00.0
444 550 13.30 2018-02-01 00:00:00.0
I would like to have query result following:
333:(362,2.16,2018-02-22)
444:(362,13.10,2015-01-09),(430,13.05,2017-04-01),(550,13.30,2018-02-01)
So all different Vendors and their prices should be listed and only latest by date.
I got this far:
SELECT
Pricelist.ItemCode + ': '+ temp.data
FROM Pricelist
INNER JOIN (SELECT p1.ItemCode,
STUFF((SELECT '; ' + p2.VendorCode
FROM Pricelist p2
WHERE p2.ItemCode = p1.ItemCode
ORDER BY VendorCode
FOR XML PATH('')), 1, 1, '') AS data
FROM Pricelist p1) as temp
ON Pricelist.ItemCode = temp.ItemCode
GROUP BY Pricelist.ItemCode, temp.data
ORDER BY 1
But not even close to the result I need.
I would use row_number() function :
select concat(itemcode, ':',
stuff( ( select top (1) with ties ',(' +concat(VendorCode, ',', UnitCost ,',', cast(StartingDate as date)) +')'
from Pricelist
where itemcode = p.itemcode
order by row_number() over (partition by VendorCode order by StartingDate desc)
for xml path('')
), 1, 1, ''
))
from Pricelist p
group by itemcode;
Try with this query:
create table #t (ItemCode int, VendorCode int, UnitCost decimal (10,2), StartingDate datetime)
insert into #t values
(333,362, 2.31,'2016-08-19 00:00:00.0'),
(333,362, 2.16,'2018-02-22 00:00:00.0'),
(444,362,12.96,'2014-01-09 00:00:00.0'),
(444,362,13.10,'2015-01-09 00:00:00.0'),
(444,430,13.05,'2017-04-01 00:00:00.0'),
(444,550,13.30,'2018-02-01 00:00:00.0')
;with tr1 as (
select
convert(varchar(100),ItemCode) + ':' as ItemCode,
'(' + convert(varchar(100),VendorCode) + ',' + convert(varchar(100),UnitCost) + ',' + convert(varchar(19),StartingDate,121) + ')' as Vals,
row_number() over (partition by ItemCode,VendorCode order by StartingDate desc) rn
from #t
)
select distinct ItemCode,
stuff((
select ',' + Vals
from tr1 b
where b.ItemCode=tr1.ItemCode
and rn=1
for xml path ('')
),1,1,'')
from tr1
where rn=1
First grouping the itemcodes, and then linking that to a string with the vendor details might be quite performant.
Linking those itemcodes to an OUTER APPLY with a FOR XML works well.
For example :
declare #Pricelist table (ItemCode int, VendorCode int, UnitCost decimal (10,2), StartingDate datetime)
insert into #Pricelist values
(333,362,02.31,'2016-08-19T00:01:00'),
(333,362,02.16,'2018-02-22T00:02:00'),
(444,362,12.96,'2014-01-09T00:03:00'),
(444,362,13.10,'2015-01-09T00:04:00'),
(444,430,13.05,'2017-04-01T00:05:00'),
(444,550,13.30,'2018-02-01T00:06:00');
select concat(itemcode,':',stuff(x.details,1,1,'')) as ItemVendorDetails
from (select distinct itemcode from #Pricelist) i
outer apply
(
select top 1 with ties
concat(',(',VendorCode,',',UnitCost,',',convert(date,StartingDate),')')
from #Pricelist p
where p.ItemCode = i.ItemCode
order by row_number() over (partition by ItemCode, VendorCode order by StartingDate desc)
for xml path('')
) x(details);
Result:
ItemVendorDetails
------------------------------------------------------------------------
333:(362,2.16,2018-02-22)
444:(362,13.10,2015-01-09),(430,13.05,2017-04-01),(550,13.30,2018-02-01)
The following query works with the sample data provided:
;WITH VendorPerItemCTE AS (
SELECT ItemCode,
VendorCode,
UnitCost,
StartingDate,
ROW_NUMBER() OVER (PARTITION BY ItemCode, VendorCode
ORDER BY StartingDate DESC) AS seq
FROM PriceList
)
SELECT CAST(ItemCode AS VARCHAR(12)) + ':' + STUFF(ItemData , 1, 1, '')
FROM (
SELECT DISTINCT p.ItemCode,
(SELECT ', (' + CAST(VendorCode AS VARCHAR(10)) + ', ' +
CAST(UnitCost AS VARCHAR(10)) + ', ' +
CONVERT(VARCHAR(12), StartingDate, 102 ) +
')'
FROM VendorPerItemCTE AS c
WHERE p.ItemCode = c.ItemCode AND c.seq = 1
FOR XML PATH('')) AS ItemData
FROM PriceList AS p) AS t
Demo here

I have a table with ID and Name. I am not able to make query for 4th and 5th row

Table A
ID Name
1 Sachin
2 Rahul
3 Saurav
I want to display Names according to ID on UI.
IDs are 1,2,3,1/2,1/2/3
I have displayed Name for 1,2,3 but I am not able to fetch for id as 1/2 and 1/2/3 as sachin/rahul and sachin/rahul/saurav.
Fun with strings... The following will essentially do a global search and replace on the string of IDs.
Now, we can use a parse/split function if you need a more robust approach
Declare #YourTable table (ID int,Name varchar(25))
Insert Into #YourTable values
(1,'Sachin'),
(2,'Rahul'),
(3,'Saurav')
Declare #Fetch varchar(max) = '1,2,3,1/2,1/2/3'
Select #Fetch = Replace('|'+Replace(Replace(#Fetch,',','|,|'),'/','|/|')+'|',MapFrom,MapTo)
From (
Select MapFrom='|'+cast(ID as varchar(25))+'|'
,MapTo =Name
From #YourTable
) A
Select Replace(#Fetch,'|','')
Returns
Sachin,Rahul,Saurav,Sachin/Rahul,Sachin/Rahul/Saurav
EDIT- Just in case you need a TABLE Version
Declare #Names table (ID int,Name varchar(25))
Insert Into #Names values (1,'Sachin'),(2,'Rahul'),(3,'Saurav')
Declare #IDs table (ID int,IDList varchar(150))
Insert Into #IDs values (1,'1,2,3,1/2,1/2/3'),(2,'2,3,1/2/3')
;with cte as (
Select A.*
,Name = IIF(Charindex('/',B.RetVal)>0 and C.RetVal>1,'/','')+N.Name
,RN = Row_Number() over (Partition By A.ID Order By B.RetSeq,C.RetSeq)
From #IDs A
Cross Apply [dbo].[udf-Str-Parse](A.IDList,',') B
Cross Apply [dbo].[udf-Str-Parse](B.RetVal,'/') C
Join #Names N on N.ID=C.RetVal
)
Select Distinct
ID
,IDList
,NewString = Replace((Select Stuff((Select ',' +Name From cte Where ID=A.ID Order By RN For XML Path ('')),1,1,'') ),',/','/')
From cte A
Returns
ID IDList NewString
1 1,2,3,1/2,1/2/3 Sachin,Rahul,Saurav,Sachin/Rahul,Sachin/Rahul/Saurav
2 2,3,1/2/3 Rahul,Saurav,Sachin/Rahul/Saurav
The UDF if interested
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ Replace(#String,#Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
--Performance On a 5,000 random sample -8K 77.8ms, -1M 79ms (+1.16), -- 91.66ms (+13.8)
Something like this?
declare #ids varchar(100) = '1/2/3'
,#names varchar(100) = ''
select #names += case when #names = '' then '' else '/' end + name
from mytable
where '/' + #ids +'/' like '%/' + cast(id as varchar(10)) + '/%'
order by id
select #names
Sachin/Rahul/Saurav

How to read value inside a cursor?

There are two tables.
One table contains:
Name value
A 1
B 2
C 3
D 4
another table contains
City value
aa 1
bb 2,3
cc 3
dd 1,2,4
I want an output which contains:
City value Name
aa 1 A
bb 2,3 B,C
cc 3 C
dd 1,2,4 A,B,D
How can i do this using cursor?
Thanks. Your question really made me appreciate normal forms.
Anyhow, I am going to go out on a limb and assume you asked for a cursor-based solution because you assumed the non-normalized data could not be handled.
Once you have the function to materialize the rows into a value list, you can solve this with a simple query.
Given:
CREATE TABLE dbo.NV (Name CHAR(1), Value INT)
CREATE TABLE dbo.CV (City varchar(88), ValueList VARCHAR(88))
loaded with the data you indicated.
And this SQL script:
GO
CREATE FUNCTION dbo.f_NVList(#VList VARCHAR(MAX)) RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #VAL VARCHAR(928)='',
#FIDescr VARCHAR(55)
SELECT #VAL = COALESCE(#VAL + LTRIM(map.name),'') + ','
FROM dbo.nv Map
WHERE CHARINDEX(','+LTRIM(STR(map.value)) + ',', ','+#VList + ',' ) > 0
SET #VAL = SUBSTRING(#VAL,1,len(#VAL)-1)
RETURN(#VAL)
END
GO -- end of function
-- this generates the output, using the function to materialize the name-values
SELECT cv.* , dbo.f_NVList(cv.ValueList ) as NameList FROM dbo.CV cv;
producing your output:
PLEASE DON'T - but If you really need the cursor for some reason, instead of
SELECT cv.* , dbo.f_NVList(cv.ValueList ) as NameList FROM dbo.CV cv;
use this
OPEN BadIdea;
FETCH NEXT FROM BadIdea INTO #C, #VList
WHILE ##FETCH_STATUS = 0
BEGIN
SET #NameList = dbo.f_NVList(#Vlist)
INSERT INTO #OUT VALUES( #C, #VLIST , #NameList )
FETCH NEXT FROM BadIdea INTO #C, #VList
END
CLOSE BadIdea
DEALLOCATE BadIdea
select * from #OUT ;
Please give a try on this:
;with nv as (
select *
from (values ('A', '1'), ('B', '2'), ('C', '3'), ('D', '4')) a (Name, value))
, cv as (
select *
from (values ('aa', '1'), ('bb', '2,3'), ('cc', '3'), ('dd', '1,2,4')) a(City, value)
)
, cv2 as (
select cv.City
, case when charindex(',',cv.value)>0 then LEFT(cv.value, charindex(',',cv.value)-1) else cv.value end value
, case when charindex(',',cv.value)>0 then right(cv.value, LEN(cv.value)-len(LEFT(cv.value, charindex(',',cv.value)-1)+',')) end leftover
from cv
union all
select cv.City
, case when charindex(',',cv.leftover)>0 then LEFT(cv.leftover, charindex(',',cv.leftover)-1) else cv.leftover end value
, case when charindex(',',cv.leftover)>0 then right(cv.leftover, LEN(cv.leftover)-len(LEFT(cv.leftover, charindex(',',cv.leftover)-1)+',')) end leftover
from cv2 cv
where cv.leftover is not null
)
select *
, stuff((
select ','+nv.Name
from cv2
join nv on nv.value=cv2.value
where cv2.City=cv.City
for xml path('')
), 1, 1, '') Name
from cv
With cv2 I split the values to City, with a recursive CTE. After that I calculate the new Name for each City.
I don't know how fast is on a big table, but I think it is better then cursor.
using CROSS APPLY we will initially delimit all the values and then we can acheieve using XML path () and CTE's
DECLARE #Name table (name varchar(5),value int)
INSERT INTO #Name (name,value)values ('A',1),('B',2),('C',3),('D',4)
DECLARE #City table (city varchar(10),value varchar(10))
INSERT INTO #City (city,value)values ('aa','1'),('bb','2,3'),('cc','3'),('dd','1,2,4')
Code :
;with CTE AS (
SELECT A.city,
Split.a.value('.', 'VARCHAR(100)') AS Data
FROM
(
SELECT city,
CAST ('<M>' + REPLACE(value, ',', '</M><M>') + '</M>' AS XML) AS Data
FROM #City
) AS A CROSS APPLY Data.nodes ('/M') AS Split(a)
),CTE2 AS (
Select c.city,t.value,STUFF((SELECT ', ' + CAST(name AS VARCHAR(10)) [text()]
FROM #Name
WHERE value = c.Data
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') List_Output
from CTE C
INNER JOIN #Name t
ON c.Data = t.value
)
select DISTINCT c.city,STUFF((SELECT ', ' + CAST(value AS VARCHAR(10)) [text()]
FROM CTE2
WHERE city = C.city
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') As Value ,STUFF((SELECT ', ' + CAST(List_Output AS VARCHAR(10)) [text()]
FROM CTE2
WHERE city = C.city
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ')As Name from CTE2 C
First, you need a function to split your comma-delimited values. Here is the DelimitedSplit8K written by Jeff Moden and improved by the community. This is regarded as one of the fastest SQL-based string splitter.
You should also read on FOR XML PATH(''), a method to concatenate strings. Check this article by Aaron Bertrand for more information.
SELECT
*
FROM Table2 t2
CROSS APPLY(
SELECT STUFF((
SELECT ',' + Name
FROM Table1
WHERE Value IN(
SELECT CAST(s.Item AS INT) FROM dbo.DelimitedSplit8K(t2.Value, ',') s
)
FOR XML PATH(''), type).value('.', 'VARCHAR(MAX)'
), 1, 1, '')
)x(Name)
SQL Fiddle
Notes:
Make sure to get the latest version of the DelimitedSplit8K.
For other splitter functions, check out this article by Aaron Bertrand.

Check the column contains string or not in sql

I have comma-separated string like 675,899,343,294,988.
My table has values like,
ID Values
1 56,78,485
2 90,343,398
3 756,46774,45,4
4 536,394,988
Here i want result like : ID 2,values 343 and ID 4,values 988
Here you can split values to each row for each Id.
;WITH CTE AS
(
-- Convert CSV to rows
SELECT ID,LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'VALUES'
FROM
(
-- To change ',' to any other delimeter, just change ',' before '</M><M>' to your desired one
SELECT ID,CAST ('<M>' + REPLACE([Values], ',', '</M><M>') + '</M>' AS XML) AS Data
FROM YourTable
) AS A
CROSS APPLY Data.nodes ('/M') AS Split(a)
)
SELECT *
FROM CTE
WHERE (ID=2 AND [VALUES]='343') OR (ID=4 AND [VALUES]='988')
EDIT :
If you want to get matching Id, you can do the below
SAMPLE TABLE
SELECT * INTO #TEMP
FROM
(
SELECT 1 ID, '56,78,485' [Values]
UNION ALL
SELECT 2, '90,343,398'
UNION ALL
SELECT 3, '756,46774,45,4'
UNION ALL
SELECT 4, '536,394,988'
)TAB
QUERY
DECLARE #STR VARCHAR(100)='675,899,343,294,988'
;WITH CTE1 AS
(
SELECT LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'String'
FROM
(
-- To change ',' to any other delimeter, just change ',' before '</M><M>' to your desired one
SELECT CAST ('<M>' + REPLACE(#STR, ',', '</M><M>') + '</M>' AS XML) AS Data
) AS A
CROSS APPLY Data.nodes ('/M') AS Split(a)
)
,CTE2 AS
(
-- Convert CSV to rows
SELECT ID,LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'Values'
FROM
(
-- To change ',' to any other delimeter, just change ',' before '</M><M>' to your desired one
SELECT ID,CAST ('<M>' + REPLACE([Values], ',', '</M><M>') + '</M>' AS XML) AS Data
FROM #TEMP
) AS A
CROSS APPLY Data.nodes ('/M') AS Split(a)
)
SELECT C2.*
FROM CTE1 c1
JOIN CTE2 C2 ON C1.String=C2.[VALUES]
You can use
SELECT `id` FROM `table` WHERE `Values` IN(string)