Sql Multiple row merge into single row with specific pattern - sql-server

Sql select query which gives merge multiple row into one single row
using sqlserver 2005 and above
I have two tables ie (tb_master,tb_tasks)
create table tb_tasks(
id int IDENTITY(1,1) NOT NULL,
id_tbmaster int NOT NULL,
Tasks nvarchar(max) NOT NULL
)
create table tb_master(
id int IDENTITY(1,1) NOT NULL,
grade nchar(10) NOT NULL,
name nvarchar(50) NOT NULL,
task_date datetime NOT NULL,
)
select * from tb_master
id grade name task_date
1 A John 2012-02-13 10:40:00.000
2 B Tom 2012-02-13 10:40:00.000
select tb_tasks
id id_tbmaster Tasks
1 1 cooking food.
2 1 Programing 2 hours
3 1 Attending meeting
4 2 Driving car
5 2 hangout with friends
Have tried this query
select tasks + ' , ' as 'data()' from tb_tasks for xml path('')
Gives Output
XML
cooking food , Programing 2 hours , Attending meeting , Driving car , hangout with friends ,
I need output like
id Name grade task_date tasksDetails
1 John A 2012-02-13 10:40:00.000 1)cooking food, 2)Programing 2 hours, 3)Attending meeting
2 Tom B 2012-02-13 10:40:00.000 1) Driving car, 2)hangout with friends
Query i tried
select a.name,a.task_date,b.tasks =replace(select (CONVERT(VARCHAR,(ROW_NUMBER() OVER(ORDER BY id DESC)))) + ') ' + tasks + ' , ' as 'data()'
from tb_tasks for xml path(''))
from tb_master a inner join tb_tasks b
on a.id=b.id_tbmaster
Thanks in advance

This query will create requested list of activities per tb_master. Two pointers: one should match ordering in row_number() over() and in query to get consistent results, and number 3 in last row of OUTER APPLY is number of characters in separator (in this case, ' , '). If you change separator you need to adjust this number.
select a.name,a.task_date, b.taskList
from tb_master a
OUTER APPLY
(
select stuff ((select ' , '
+ CONVERT(VARCHAR(10), ROW_NUMBER()
OVER(ORDER BY id DESC))
+ ') '
+ tasks
from tb_tasks b
where a.id = b.id_tbmaster
order by id desc
for xml path (''))
, 1, 3, '') taskList
) b
Live demo is at Sql Fiddle.

Related

Combine Multiple Rows into one row on a single table

I have a table that contains multiple Insurance policies for each client that contains
Order (Primary, Secondary etc. up to 9 possible Insurance policies)
I need one row per client containing all the client's insurance policies in order of importance.
for example:
Client, Order, Company, Number, Start, End, Co-Pay, Active?
1 1 BCBS 12345 1/1/2019 $10 Active
1 2 Medicare X21Y52 2/1/2018 Active
1 6 Self-Pay
2 6 Medicare X21Y52 2/1/2018 Active
2 8 Self-Pay
I need a single row for each Client with the data for each Active Policy
1,1,BCBS,12345,1/1/2019,,$10,Active,2,Medicare,X21Y52,2/1/2018,,,Active,6,Self-Pay,,,,,
2,6,Medicare,x22y22,7/1/2002,,,Active,8,Self-Pay,,,,,,
How can I code to get the desired result.
There is a maximum of 9 policy references
Only want Active policies
As you didn't post your schema, i just make some assumption on the data type. Note that you need to convert to string before concatenation
select t.Client, stuff(csv, 1, 1, '')
from (
select distinct Client
from a_table t
) t
cross apply
(
select ',' + coalesce(x.Company, '') +
',' + coalesce(x.Number, '') +
',' + coalesce(convert(varchar(10), [Start], 121), '') +
',' + . . .
from a_table x
where x.Client = t.Client
and x.Active = 'Active'
order by [Order]
for xml path ('')
) c (csv)

How to Add values of column 2 at the end of column 1 if value of column 1 been duplicate

I have a table as shown below
[ID] [ Company ] [ShippingCity] [externalID]
1 Microsoft Bellevue MM2547
2 Microsoft Bellevue MM2548
3 Microsoft Seattle MM2549
4 Microsoft Null MM2550
I want to be able to have the name in one select statement and apply a logic that gives me name of company base on it.
I am using this logic :
SELECT CASE WHEN SHIPPINGCITY IS NULL THEN CONCAT(COMPANY,'-',EXTERNALID)
ELSE CONCAT(COMPANY,'-',SHIPPINGCITY) ELSE **THIS PART IS WHERE i AM LOST**
IF SHIPPING CITY IS SAME ADD THE EXTERNAL ID INTO SECOND RECORD
I am aware that I can use only external ID but problem is client I am working with wants the first record without externalID at the end so they are looking for a result as shown below:
[ID] [ Company ] [ShippingCity] [externalID]
1 Microsoft-Bellevue Bellevue MM2547
2 Microsoft-Bellevue-MM2548 Bellevue MM2548
3 Microsoft-Seattle Seattle MM2549
4 Microsoft-MM2550 Null MM2550
is there any logic that I can use to get it in one single SELECT statement?
Thanks a lot in advance
This should do the trick:-
select
id,
case
when ShippingCity is null then Company + '-' + ExternalID
when row_number() over(partition by Company, ShippingCity order by ExternalID) = 1 then Company + '-' + ShippingCity
else Company + '-' + ShippingCity + '-' + ExternalID
end as [Company],
ShippingCity,
ExternalID
from [Your Table]
order by id
You could use the following logic, which makes use of the row_number to partition it based on company and shippingcity. Hope it makes sense to you.
DECLARE #t TABLE (ID INT, Company VARCHAR(255), ShippingCity VARCHAR(255), ExternalId VARCHAR(255))
INSERT INTO #t VALUES
(1,'Microsoft','Bellevue','MM2547'),
(2,'Microsoft','Bellevue','MM2548'),
(3,'Microsoft','Seattle','MM2549'),
(4,'Microsoft',Null,'MM2550')
SELECT sub.ID,
CASE WHEN sub.ShippingCity IS NULL THEN sub.Company + '-' + sub.ExternalId
WHEN sub.r = 1 THEN sub.Company + '-' + sub.ShippingCity
WHEN sub.r > 1 THEN sub.Company + '-' + sub.ShippingCity + '-' + sub.ExternalId END AS Company
, sub.ShippingCity, sub.ExternalId
FROM
(
SELECT Id, Company, ShippingCity, ExternalId,
ROW_NUMBER() OVER(PARTITION BY Company, ShippingCity ORDER BY Company, ShippingCity, ExternalId) AS r
FROM #t
) AS sub
ORDER BY ID
Result
ID Company ShippingCity ExternalId
----------------------------------------------------------
1 Microsoft-Bellevue Bellevue MM2547
2 Microsoft-Bellevue-MM2548 Bellevue MM2548
3 Microsoft-Seattle Seattle MM2549
4 Microsoft-MM2550 NULL MM2550

TSQL, change value on a comma delimited column

I have a column called empl_type_multi which is just a comma delimited column, each value is a link to another table called custom captions.
For instance, i might have the following as a value in empl_type_multi:
123, RHN, 458
Then in the custom_captions table these would be individual values:
123 = Dog
RHN = Cat
458 = Rabbit
All of these fields are NTEXT.
What i am trying to do is convert the empl_type_multi column and chance it to the respective names in the custom_captions table, so in the example above:
123, RHN, 458
Would become
Dog, Cat, Rabbit
Any help on this would be much appreciated.
----- EDIT ------------------------------------------------------------------
Ok so ive managed to convert the values to the corresponding caption and put it all into a temporary table, the following is the output from a CTE query on the table:
ID1 ID2 fName lName Caption_name Row_Number
10007 22841 fname1 lname1 DENTAL ASSISTANT 1
10007 22841 fname1 lname1 2
10007 22841 fname1 lname1 3
10008 23079 fname2 lname2 OPS WARD 1
10008 23079 fname2 lname2 DENTAL 2
10008 23079 fname2 lname2 3
How can i update this so that anything under caption name is added to the caption name of Row_Number 1 separated by a comma?
If i can do that all i need to do is delete all records where Row_Number != 1.
------ EDIT --------------------------------------------------
The solution to the first edit was:
WITH CTE AS
(
SELECT
p.ID1
, p.ID2
, p.fname
, p.lname
, p.caption_name--
, ROW_NUMBER() OVER (PARTITION BY p.id1ORDER BY caption_name DESC) AS RN
FROM tmp_cs p
)
UPDATE tblPerson SET empType = empType + ', ' + c.Data
FROM CTE c WHERE [DB1].dbo.tblPerson.personID = c.personID AND RN = 2
And then i just incremented RN = 2 until i got 0 rows affected.
This was after i ran:
DELETE FROM CTE WHERE RN != 1 AND Caption_name = ''
select ID1, ID2, fname, lname, left(captions, len(captions) - 1) as captions
from (
select distinct ID1, ID2, cast(fname as nvarchar) as fname, cast(lname as nvarchar) as lname, (
select cast(t1.caption_name as nvarchar) + ','
from #temp as t1
where t1.ID1 = t2.ID1
and t1.ID2 = t2.ID2
and cast(caption_name as nvarchar) != ''
order by t1.[row_number]
for xml path ('')) captions
from #temp as t2
) yay_concatenated_rows
This will give you what you want. You'll see casting from ntext to varchar. This is necessary for comparison because many logical ops can't be performed on ntext. It can be implicitly cast back the other way so no worries there. Note that when casting I did not specify length; this will default to 30, so adjust as varchar(length) as needed to avoid truncation. I also assumed that both ID1 and ID2 form a composite key (it appears this is so). Adjust the join as you need for the relationship.
you have just shared your part of problem,not exact problem.
try this,
DECLARE #T TABLE(ID1 VARCHAR(50),ID2 VARCHAR(50),fName VARCHAR(50),LName VARCHAR(50),Caption_name VARCHAR(50),Row_Number INT)
INSERT INTO #T VALUES
(10007,22841,'fname1','lname1','DENTAL ASSISTANT', 1)
,(10007,22841,'fname1','lname1', NULL, 2)
,(10007,22841,'fname1','lname1', NULL, 3)
,(10008,23079,'fname2','lname2','OPS WARD', 1)
,(10008,23079,'fname2','lname2','DENTAL', 2)
,(10008,23079,'fname2','lname2', NULL, 3)
SELECT *
,STUFF((SELECT ','+Caption_name
FROM #T T1 WHERE T.ID1=T1.ID1 FOR XML PATH('')
),1,1,'')
FROM #T T
You can construct the caption_name string easily by looping through while loop
declare #i int = 2,#Caption_name varchar(100)= (select series from
#temp where Row_Number= 1)
while #i <= (select count(*) from #temp)
begin
select #Caption_name = #Caption_name + Caption_name from #temp where Row_Number = #i)
set #i = #i+1
end
update #temp set Caption_name = #Caption_name where Row_Number = 1
and use case statement to remove null values
(select case when isnull(Caption_name ,'') = '' then
'' else ',' + Caption_name end

How can I add a display order within a parent/child query?

Simple table example:
CatID | ParentID | Name | DisplayOrder
============================================
1 NULL A 1
2 1 A1 1
3 1 A2 2
4 NULL B 2
etc.
I want to order by parents, and children within that parent. But I also want to obey the DisplayOrder. So first by parent by display order, and then children within each parent, by their display order. One level deep only. I want to avoid a complex query if possible, such as most CTE examples I've seen.
Well, this works
SELECT * FROM (
SELECT
CatID
,ParentID
,DisplayOrder AS LevelOne
,NULL AS LevelTwo
FROM Table WHERE ParentID IS NULL
UNION ALL
SELECT
c.CatID
,c.ParentID
,d.DisplayOrder AS LevelOne
,c.DisplayOrder AS LevelTwo
FROM Table c WHERE ParentID IS NOT NULL
INNER JOIN Table d ON c.ParentID = d.CatID
) AS Cats
ORDER BY Cats.LevelOne, Cats.LevelTwo
Okay so the above query works fine, but only on one condition... The DisplayOrder of the parents must be different. So if two parents have 1 as the DisplayOrder ( for whatever reason ), then result set ends up a complete mess. As long as I have the parent's display order in sequence, 1, 2, 3, 4, 5, etc. Then it works. I understand why, but trying to find the best solution to solve it and account for the possibility of duplicate display orders.
This will allow you to sort properly without jumping through hoops, feel free to change the data types, just remember if the variables in DisplayOrder + ' - ' + CatID are int it will evaluate as 1+0+1=2 not '1 - 1'
CREATE TABLE #table (
CatID INT PRIMARY KEY
,ParentID INT NULL
,NAME VARCHAR(50)
,DisplayOrder INT
);
INSERT #table
VALUES (1,NULL,'A',1)
,(2,1,'A1',1)
,(3,1,'A2',2)
,(4,NULL,'B',2);
SELECT *
FROM (
SELECT CatID
,ParentID
,Convert(VARCHAR(50), DisplayOrder)
+ ' - '
+ Convert(VARCHAR(50), CatID) AS LevelOne
,NULL AS LevelTwo
FROM #table
WHERE ParentID IS NULL
UNION ALL
SELECT c.CatID
,c.ParentID
,Convert(VARCHAR(50), d.DisplayOrder)
+ ' - '
+ Convert(VARCHAR(50), d.CatID) AS LevelOne
,c.DisplayOrder AS LevelTwo
FROM #table c
INNER JOIN #table d ON c.ParentID = d.CatID
WHERE c.ParentID IS NOT NULL
) AS Cats
ORDER BY Cats.LevelOne
,Cats.LevelTwo;
DROP TABLE #table
This results in:
CatID ParentID LevelOne LevelTwo
1 NULL 1 - 1 NULL
2 1 1 - 1 1
3 1 1 - 1 2
4 NULL 2 - 4 NULL

How to phrase a T-SQL horizontal join?

I have a database which is including two tables, Labs and LabUsers
How do I join a column from LabUsers into a cell in Labs.
To be specific, I would like to have the user names of a lab to be concatenated and separated with a bullet ( • is Alt+0149), and to have these usernames sorted alphabetically left to right.
Here is an example.
The Labs table looks like this:
LabID LabName LabStudents
----- ---------- -----------
1 North NULL
2 North East NULL
3 South West NULL
and the LabUsers looks like this:
LabUserID LabUserName LabID
--------- ----------- -----
1 Diana 1
2 Paul 2
3 Paula 2
4 Romeo 1
5 Julia 1
6 Rose 2
7 Diana 2
I would like to get this outcome in the Labs table:
LabID LabName LabUsers
----- ---------- ---------------------
1 North Diana•Julia•Romeo
2 North East Diana•Paul•Paula•Rose
3 South West NULL
Here is the script to create the tables:
USE [tempdb];
GO
CREATE TABLE [dbo].[LabUsers]
(
[LabUserID] [int] PRIMARY KEY CLUSTERED,
[LabUserName] [nvarchar](50) NOT NULL,
[LabID] [int] NOT NULL
);
GO
INSERT [dbo].[LabUsers] SELECT 1, N'Diana', 1;
INSERT [dbo].[LabUsers] SELECT 2, N'Paul', 2;
INSERT [dbo].[LabUsers] SELECT 3, N'Paula', 2;
INSERT [dbo].[LabUsers] SELECT 4, N'Romeo', 1;
INSERT [dbo].[LabUsers] SELECT 5, N'Julia', 1;
INSERT [dbo].[LabUsers] SELECT 6, N'Rose', 2;
INSERT [dbo].[LabUsers] SELECT 7, N'Diana', 2;
CREATE TABLE [dbo].[Labs]
(
[LabID] [int] PRIMARY KEY CLUSTERED,
[LabName] [nvarchar](50) NOT NULL,
[LabUsers] [nvarchar](max) NULL
);
GO
INSERT [dbo].[Labs] SELECT 1, N'North', NULL;
INSERT [dbo].[Labs] SELECT 2, N'North East', NULL;
INSERT [dbo].[Labs] SELECT 3, N'South West', NULL;
SELECT l.LabID, l.LabName, LabUsers = STUFF((SELECT N'•' + lu.LabUserName
FROM dbo.LabUsers AS lu
WHERE lu.LabID = l.LabID
ORDER BY lu.LabUserName
FOR XML PATH(''),
TYPE).value(N'./text()[1]', N'nvarchar(max)'), 1, 1, N'')
FROM dbo.Labs AS l;
I see absolutely no reason to store this in the table, since you can always generate the code at runtime when you run a query. If you store it in the table, then you have to update it every single time you change any row in the table.
However, if I can't convince you not to do this (it really is bad to store redundant data like this), you can try this way:
;WITH x AS
(
SELECT l.LabID, l.LabName, x = STUFF((SELECT N'•' + lu.LabUserName
FROM dbo.LabUsers AS lu
WHERE lu.LabID = l.LabID
ORDER BY lu.LabUserName
FOR XML PATH(''),
TYPE).value(N'./text()[1]', N'nvarchar(max)'), 1, 1, N'')
FROM dbo.Labs AS l
)
UPDATE l
SET LabUsers = x.x
FROM dbo.Labs AS l
INNER JOIN x ON l.LabID = x.LabID;
As for the performance tests, I'd compare the above version with this variation:
SELECT l.LabID, l.LabName, LabUsers = STUFF((SELECT N'•' + lu.LabUserName
FROM dbo.LabUsers AS lu
WHERE lu.LabID = l.LabID
ORDER BY lu.LabUserName
FOR XML PATH('')), 1, 1, '')
FROM dbo.Labs AS l;
On my system I see the initial version at the top of this answer to be far more expensive. Also note that stuffing (no pun intended) these approaches into a user-defined function will bring it closer to the concatenation method #RThomas proposed.
Give this a try
SELECT LabName ,
STUFF(( SELECT ',' + LabUsers.LabUserName
FROM dbo.LabUsers
WHERE LabUsers.LabID = Labs.LabID
ORDER BY LabName
FOR
XML PATH('')
), 1, 1, '') AS Labusers
FROM dbo.Labs
ORDER BY LabName
The FOR XML PATH('') concatenates your strings together into one XML result and the STUFF puts a "nothing" character at the first character, e.g. wipes out the unneeded first comma.
Another way to do it is set up a UDF that returns all the lab users as a single string like this:
CREATE FUNCTION LabUserString
(
#pLabId Int
)
RETURNS NVarChar(Max)
AS
BEGIN
Declare #pResult NVarChar(Max)
SELECT #pResult = COALESCE(#pResult + N'•', '') + [LabUserName]
FROM LabUsers WHERE LabId = #pLabId
Return #pResult
END
And then a query like this:
Select LabID, LabName, dbo.LabUserString(LabID) AS LabUsers FROM Labs

Resources