Group concat in sql server for multiple column groups performance - sql

I am currently working in SQL server 2005 and table contain million of rows.
The table have following rows and columns
ID PO_ID Event_ID Item_ID
1 22 970 123456
1 22 970 123457
1 23 970 1234589
1 22 971 12345790
1 22 971 12345792
I want to concat column item_ID for multiple column group "ID, PO_ID, Event_ID"
The output Will be like this
ID PO_ID Event_ID Item_ID
1 22 970 123456,123457
1 23 970 1234589
1 22 971 12345790,12345792
I have the following SQL query
select ID, PO_ID, Event_ID,
substring(
( SELECT ','+ Item_ID)
FROM table as a
WHERE a.ID=table.ID
AND a.PO_ID=table.PO_ID
and a.event_ID=table.event_ID
FOR XML PATH ('')
)
from table
group by ID,PO_ID,Event_ID;
But this query is really slow in terms of performance
Is there any optimized way to do this in SQL server 2005?
Any help will be appreciated.
Note : I don't have permissions to create UDF or indexes.

I would recommend writing this as:
select ID, PO_ID, Event_ID,
stuff( (select ',' + a.Item_ID)
from table a
where a.ID = t.ID and
a.PO_ID = t.PO_ID and
a.event_ID = t.event_ID
FOR XML PATH ('')
), 1, 1, ''
) as items
from (select distinct ID, PO_ID, Event_ID
from table t
) t;
Then for performance, you want an index on (ID, PO_ID, Event_ID, Item_Id).

It's possible you create an index on the table this could help you

Related

SQL Query Optimization to retrieve non-null entries

Need help in optimizing SQL query, I have figured a way to solve the problem by using UNIONALL, but my worry is that performance will be impacted as the record set is huge in production env.
I have a table of records in below format, I need help in retrieving the non-null entries if available otherwise pick the null entries.
In the below case; Query should exclude RowIds 1,7 and retrieve everything else, i.e because there are non-null entries for that combination.
RowID
UniqueID
TrackId
1
325
NULL
2
325
8zUAC
3
325
99XER
4
427
NULL
5
632
2kYCV
6
533
NULL
7
774
NULL
8
774
94UAC
--UNIONALL Command
SELECT A.* FROM
( SELECT * FROM [MY_PKG].[TEMP] WHERE TRACKID is not null) A
WHERE A.UNIQUEID in
( SELECT UNIQUEID FROM [MY_PKG].[TEMP] WHERE TRACKID is null
)
UNION ALL
SELECT B.* FROM
( SELECT * FROM [MY_PKG].[TEMP] WHERE TRACKID is null) B
WHERE B.UNIQUEID not in
( SELECT UNIQUEID FROM [MY_PKG].[TEMP] WHERE TRACKID is not null
)
Temp Table Creation Scrip
CREATE TABLE MY_PKG.TEMP
( UNIQUEID varchar(3),
TRACKID varchar(5)
);
INSERT INTO MY_PKG.TEMP
( UNIQUEID, TRACKID)
VALUES
('325',null),
('325','8zUAC'),
('325','99XER'),
('427',null),
('632','2kYCV'),
('533','2kYCV'),
('774',null),
('774','94UAC')
You can use the NOT EXISTS operator with a correlated subquery:
SELECT * FROM TEMP T
WHERE TRACKID IS NOT NULL
OR (TRACKID IS NULL
AND NOT EXISTS(
SELECT 1 FROM TEMP D
WHERE D.UNIQUEID = T.UNIQUEID AND
D.TRACKID IS NOT NULL)
)
See demo

combine multiple rows into a single row with repeating set of columns

I have a table with multiple insurance policies per client. One Policy per record.
I need to represent all the policies in one row per client.
I have read all the similar questions and they may be close but I don't seem to be able to translate the answers into my situation.
The table that I have looks like this
enter code here Client-ID Ins-Company Policy-Number Start-Date
1 BCBS BSBC1 2018-01-01
1 Aetna Aetna1 2017-01-01
1 Self-Pay N/A 2016-01-01
2 Self-Pay N/A 2015-01-01
3 BCBS BCBS3 2014-01-01
3 Self-Pay N/A 2013-01-01
Expected Result:
enter code here Client-ID Ins-Co1 Policy1 Start1 Ins-Co2 Policy2 Start2 Ins-Co3 Policy3 Start3
1 BCBS BCBS1 2018-01-01 Aetna Aetna1 2017-01-01 Self-Pay N/A 2016-01-01
2 Self-Pay N/A 2015-01-01
3 BCBS BCBS3 2014-01-01 Self-Pay N/A 2013-01-01
In need to create another table with these records
I created the result I needed with the following code.
First, I created a View of the source table that added a Row Number using the following Function:
ROW_NUMBER() OVER(PARTITION BY a.Client_ID ORDER BY a.Billing_Order ASC) AS Row_No
and built the following Code
enter code hereDECLARE #sql varchar(max)
DECLARE #colList varchar(max)
--create dynamic list of columns
SELECT #colList =
STUFF
(
(
SELECT + ',' +
quotename(colName + Row_No)
FROM Credible_Client_Insurance_Raw_Data_Query
CROSS APPLY
(
SELECT 1 As Ord, 'Payer_ID' ColName UNION ALL
SELECT 2 As Ord, 'Billing_Order' UNION ALL
SELECT 3 As Ord, 'Insurance_ID' UNION ALL
SELECT 4 As Ord, 'Group_No' UNION ALL
SELECT 5 As Ord, 'Copay_Fee' UNION ALL
SELECT 6 As Ord, 'Start_Date'
) v
GROUP BY colName, Ord, Row_No
ORDER BY Row_No, Ord
for xml path(''), type
).value('/','varchar(max)'),1,1,''
)
--unpivot columns into rows and then apply pivot
SET #sql
= '
SELECT Client_ID, ' + #colList + '
FROM
(
SELECT Client_ID, ColVal,
colName + Row_No ColName
FROM Credible_Client_Insurance_Raw_Data_Query
CROSS APPLY
(
SELECT Payer_ID As ColVal, ''Payer_ID'' ColName UNION ALL
SELECT Billing_Order, ''Billing_Order'' UNION ALL
SELECT Insurance_ID, ''Insurance_ID'' UNION ALL
SELECT Group_No, ''Group_No'' UNION ALL
SELECT CAST(Copay_Fee AS VARCHAR), ''Copay_Fee'' UNION ALL
SELECT CAST(Start_Date AS VARCHAR), ''Start_Date''
) v
) A
PIVOT
(
MAX(ColVal) FOR ColName IN (' + #colList + ')
) P1 '
EXEC(#sql)
Code was copied from another question
Pivot on multiple columns based on single column
Now I have a follow up question:
Now that I created a query that provides the result I need
I need to place the result into a table.
I cannot take this query and make it into a VIEW because it starts with a Declare Statement ( Illegal for Views )
So how do I transfer this query data into a Table?

SQL Server: ORDER BY parameters in IN statement

I have a SQL statement that is the following:
SELECT A.ID, A.Name
FROM Properties A
WHERE A.ID IN (110, 105, 104, 106)
When I run this SQL statement, the output is ordered according to the IN list by ID automatically and returns
104 West
105 East
106 North
110 South
I want to know if it is possible to order by the order the parameters are listed within the IN clause. so it would return
110 South
105 East
104 West
106 North
I think the easiest way in SQL Server is to use a JOIN with VALUES:
SELECT p.ID, p.Name
FROM Properties p JOIN
(VALUES (110, 1), (105, 2), (104, 3), (106, 4)) ids(id, ordering)
ON p.id = a.id
ORDER BY ids.ordering;
Sure...
just add an Order clause with a case in it
SELECT A.ID, A.Name
FROM Properties A
WHERE A.ID IN (110,105,104,106)
Order By case A.ID
when 110 then 0
when 105 then 1
when 104 then 2
when 106 then 3 end
With the help of a parsing function which returns the sequence as well
SELECT B.Key_PS
, A.ID
, A.Name
FROM Properties A
Join (Select * from [dbo].[udf-Str-Parse]('110,105,104,106',',')) B on A.ID=B.Key_Value
WHERE A.ID IN (110,105,104,106)
Order by Key_PS
The UDF if you need
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimeter varchar(10))
--Usage: Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
-- Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
-- Select * from [dbo].[udf-Str-Parse]('id26,id46|id658,id967','|')
-- Select * from [dbo].[udf-Str-Parse]('hello world. It. is. . raining.today','.')
Returns #ReturnTable Table (Key_PS int IDENTITY(1,1), Key_Value varchar(max))
As
Begin
Declare #XML xml;Set #XML = Cast('<x>' + Replace(#String,#Delimeter,'</x><x>')+'</x>' as XML)
Insert Into #ReturnTable Select Key_Value = ltrim(rtrim(String.value('.', 'varchar(max)'))) FROM #XML.nodes('x') as T(String)
Return
End
The Parser alone would return
Select * from [dbo].[udf-Str-Parse]('110,105,104,106',',')
Key_PS Key_Value
1 110
2 105
3 104
4 106
What you could potentially do is:
Create a TVF that would split string and keep original order.
This questions seems to have this function already written: MS SQL: Select from a split string and retain the original order (keep in mind that there might be other approaches, not only those, covered in this question, I just gave it as an example to understand what function should do)
So now if you'd run this query:
SELECT *
FROM dbo.Split('110,105,104,106', ',') AS T;
It would bring back this table as a result.
items rownum
------------
110 1
105 2
104 3
106 4
Following that, you could simply query your table, join with this TVF passing your IDs as a parameter:
SELECT P.ID, P.Name
FROM Properties AS P
INNER JOIN dbo.Split('110,105,104,106', ',') AS T
ON T.items = P.ID
ORDER BY T.rownum;
This should retain order of parameters.
If you need better performance, I'd advice to put records from TVF into hash table, index it and then join with actual table. See query below:
SELECT T.items AS ID, T.rownum AS SortOrder
INTO #Temporary
FROM dbo.Split('110,105,104,106', ',') AS T;
CREATE CLUSTERED INDEX idx_Temporary_ID
ON #Temporary(ID);
SELECT P.ID, P.Name
FROM Properties AS P
INNER JOIN #Temporary AS T
ON T.ID = P.ID
ORDER BY T.SortOrder;
This should work better on larger data sets and equally well on small ones.
Here is a solution that does not rely on hard codes values or dynamic sql (to eliminate hard coding values).
I would build a table (maybe temp or variable) with OrderByValue and OrderBySort and insert from the application.
OrderByValue OrderBySort
110 1
105 2
104 3
106 4
Then I would join on the value and sort by the sort. The join will be the same as the In clause.
SELECT A.ID, A.Name
FROM Properties A
JOIN TempTable B On A.ID = B.OrderByValue
Order By B.OrderBySort
Another solution for this problem is prepare a temporary table for IN clause like
declare #InTable table (ID int, SortOrder int not null identity(1,1));
We can fill this temp table with your data in order you want
insert into #InTable values (110), (105), (104), (106);
At last we need to modify your question to use this temp table like this
select A.ID, A.Name
from Properties A
inner join #InTable as Sort on A.ID = Sort.ID
order by Sort.SortOrder
On the output you can see this
ID Name
110 South
105 East
104 West
106 North
In this solution you don't need to provide order in special way. You just need to insert values in order you want.

SQL Pivot Column that has multiple values for same column

Trying to pivot table results that may have multiple rows with the same value
I have data that looks like this so far.
Nbr Person Test
33 Barry. Prim
33 Brian Sup
33 Burke RT 1st
33 Ray Add
33 Jake Add
33 Smith Add
I'm trying to pivot it so that it looks like this:
Nbr Prim Sup 1st Add Add2 Add3
33 Barry Brian Burke Ray Jake Smith
This is what I have so far with a normal pivot but it doesn't work to grab all the ones with the same value in the Test Column
CREATE TABLE #testTbl(nbr int,name varchar(20),test VARCHAR(10))
INSERT INTO #testTbl
SELECT '33','Barry','Prim'
UNION
SELECT '33','Brian','Sup'
UNION
SELECT '33','Burke','1st'
UNION
SELECT '33','Ray','Add'
UNION
SELECT '33','jake','Add'
UNION
SELECT '33','Smith','Add'
select * from (
Select *
from #testTbl
) as x
pivot(
max(name) for test in ([prim],[sup],[1st],[add])
)
as pivot1
Any help is greatly appreciated. If its not possible to have the columns output as Add Add2 and Add3 thats fine. Whatever works.
You can do so by modifying the test value using window functions:
select *
from (Select tt.name,
(test + (case when count(*) over (partition by test) = 1
then ''
else cast(row_number() over (partition by test order by (select null)) as varchar(255))
end)) as test
from testTbl tt
) as x
pivot(
max(name) for test in ([prim], [sup], [1st], [Add1], [Add2], [Add3])
) as pivot1
A SQL Fiddle is here.

Linking together different columns in a SQL table?

I'm not very experienced with advance SQL queries, I'm familiar with basic statements and basic joins, currently trying to figure out how to write a query that seems to be out of my depth and I haven't been able to find a solution from google so far and I'm hoping somebody might be able to point me in the right direction.
The table I'm working with has an ID column, and a 'parent id' column.
I'm looking for all descendants of ID '1' - rows with a parent ID of '1', rows with a parent ID equal to any row's ID with a parent ID of '1' etc. Currently I've been doing this manually but there are hundreds of descendants so far and I feel like there's a way to put this into one query.
Any help would be appreciated, if this is unclear I can also try to clarify.
EDIT - I got it working with the following query:
with cteMappings as (
select map_id, parent_map_id, map_name
from admin_map
where map_id = '1'
union all
select a.map_id, a.parent_map_id, a.map_name
from admin_map a
inner join cteMappings m
on a.parent_map_id = m.map_id
)
select map_id, parent_map_id, map_name
from cteMappings
Sounds like it can be achieved by Common Table Expression:
DECLARE #temp TABLE (id INT IDENTITY(1, 1), parent_id INT);
INSERT #temp
SELECT NULL
UNION ALL
SELECT 1
UNION ALL
SELECT 2
UNION ALL
SELECT NULL
SELECT * FROM #temp
; WITH HierarchyTemp (id, parent_id, depth) AS (
SELECT id, parent_id, 0
FROM #temp
WHERE id = 1
UNION ALL
SELECT t.id, t.parent_id, ht.depth + 1
FROM #temp t
INNER JOIN HierarchyTemp ht ON ht.id = t.parent_id
)
SELECT *
FROM HierarchyTemp
So the above example is creating a table variable with 4 rows:
id parent_id
1 NULL
2 1
3 2
4 NULL
Result of descendent of id '1' (also including itself, but can be excluded with an additional WHERE clause):
id parent_id depth
1 NULL 0
2 1 1
3 2 2