Merging multiple rows with same ID in SQL Server - sql-server

What is the most efficient method to combine multiple rows of values with the same ID in SQL Server?
Original data table dbo.ProductCategory:
+-----------+----------+------+
| ProductID | CATID | AA |
+-----------+----------+------+
| 1 | 123 | A |
| 1 | 412 | B |
| 2 | 567 | C |
| 2 | 521 | A |
| 3 | 2 | D |
| 3 | 5 | A |
| 4 | 6 | C |
| 4 | 8 | E |
| 4 | 123 | A |
+----+------+----------+------+
And I'm trying to achieve the following result
+-----------+----------+------+
| ProductID | CATID | AA |
+-----------+----------+------+
| 1 | 123,412 | A,B |
| 2 | 567,521 | C,A |
| 3 | 2,5 | D,A |
| 4 | 6,8,123 | C,E,A|
+----+------+----------+------+

In SQL Server 2017+, you can use STRING_AGG
select ProductId, STRING_AGG(CATID, ',') as CATID, STRING_AGG(AA, ',') AA
from PC
GROUP BY ProductID

Sample Data
DECLARE #Temp AS TABLE (ProductID INT, CATID INT, AA CHAR(2))
INSERT INTO #Temp
SELECT 1 , 123 , 'A' UNION ALL
SELECT 1 , 412 , 'B' UNION ALL
SELECT 2 , 567 , 'C' UNION ALL
SELECT 2 , 521 , 'A' UNION ALL
SELECT 3 , 2 , 'D' UNION ALL
SELECT 3 , 5 , 'A' UNION ALL
SELECT 4 , 6 , 'C' UNION ALL
SELECT 4 , 8 , 'E' UNION ALL
SELECT 4 , 123 , 'A'
Using STUFF() In sql server
SELECT ProductID,STUFF((SELECT CONCAT(', ', CATID)
FROM #Temp i
WHERE i.ProductID = o.ProductID
FOR XML PATH ('')),1,1,'') AS CATID
,STUFF((SELECT CONCAT(', ', AA)
FROM #Temp i
WHERE i.ProductID = o.ProductID
FOR XML PATH ('')),1,1,'') AS AA
FROM #Temp o
GROUP BY ProductID
Result
ProductID CATID AA
------------------------------------
1 123, 412 A , B
2 567, 521 C , A
3 2, 5 D , A
4 6, 8, 123 C , E , A

Related

Get all course, chapter in one column SQL Server

Course
+-----+----------+
| id | c_name |
+-----+----------+
| 1 | course1 |
| 7 | course2 |
+-----+----------+
Chapter
+-----+----------+------------+
| id | Ch_name | c_id |
+-----+----------+------------+
| 3 | Chapter1 | 1 |
| 9 | Chapter2 | 7 |
| 11 | Chapter3 | 1 |
| 17 | Chapter4 | 1 |
+-----+----------+------------+
I'm trying to select all data so that I can generate the following output:
+-----+-- |
|Course |
+-----+-- |
|Course1 |
|Chapter1 |
|Chapter3 |
|Chapter4 |
| |
|Course2 |
|Chapter2 |
I have tried in this way:
select
c.CourseID ,
'Course' as table_name,
c.CourseName as Course,
'' as Chapter
from [MstCourse]c
union
select
s.CourseID,
'Chapter' as table_name,
c.CourseName as Course,
s.ChapterName as Chapter
from [MstCourse] c
inner JOIN [ManageChapter] s ON c.CourseID= s.CourseID
order by Course, Chapter
But I am not getting the results in a single column.
You could achieve this with a group by ... with rollup clause.
Sample data
create table course
(
id int,
name nvarchar(10)
);
insert into course(id, name) values
(1, 'Course1'),
(7, 'Course2');
create table chapter
(
id int,
name nvarchar(10),
c_id int
);
insert into chapter(id, name, c_id) values
(3 , 'Chapter1', 1),
(9 , 'Chapter2', 7),
(11, 'Chapter3', 1),
(17, 'Chapter4', 1);
Solution
select coalesce(ch.Name, co.Name) as [Course]
from course co
join chapter ch
on ch.c_id = co.id
group by co.Name, ch.Name with rollup
having grouping(co.Name) <> 1
order by co.Name, ch.Name;
For some background on how this solution works, have a look at this fiddle.

SQL Dynamic Pivoting with Dynamic Data

I am using an SQL Server database and have these following tables
Table "Data"
------------------
| Id | data_name |
------------------
| 1 |Data 1 |
| 2 |Data 2 |
| 3 |Data 3 |
| 4 |Data 4 |
| 5 |Data 5 |
------------------
and Table "Value_data"
--------------------------------------------------------------------------------------------------------------
| Id | data_id | date | col_1_type | col_1_name | col_1_value | col_2_type | col_2_name | col_2_value |
--------------------------------------------------------------------------------------------------------------
| 1 | 1 | 2017-01-01 | A | Alpha | 12 | B | Beta | 23 |
| 2 | 1 | 2017-02-01 | A | Alpha | 32 | B | Beta | 42 |
---------------------------------------------------------------------------------------------------------------
And i want to make result like so
-----------------------------------------------------------------
|value_id | data_id | data_name | date | A-Alpha | B-Beta |
-----------------------------------------------------------------
|1 | 1 | Data 1 | 2017-01-01 | 12 | 23 |
|2 | 1 | Data 1 | 2017-02-01 | 32 | 42 |
-----------------------------------------------------------------
I've search multiple times for solutions, i've tried using Pivot for example for a static result,
DECLARE #Data TABLE ( Id INT, data_name VARCHAR(10) )
INSERT INTO #Data VALUES
( 1 ,'Data 1'),
( 2 ,'Data 2'),
( 3 ,'Data 3'),
( 4 ,'Data 4'),
( 5 ,'Data 5')
DECLARE #Value_data TABLE (Id INT, data_id INT, [date] DATE, col_1_type VARCHAR(10), col_1_name VARCHAR(10), col_1_value INT, col_2_type VARCHAR(10), col_2_name VARCHAR(10), col_2_value INT)
INSERT INTO #Value_data VALUES
( 1, 1, '2017-01-01','A','Alpha','12','B','Beta','23'),
( 2, 1, '2017-02-01','A','Alpha','32','B','Beta','42')
;WITH CTE AS (
select vd.Id value_id
, vd.data_id
, d.data_name
, vd.[date]
, vd.col_1_type + '-' +vd.col_1_name Col1
, vd.col_1_value
, vd.col_2_type + '-' +vd.col_2_name Col2
, vd.col_2_value
from #Value_data vd
inner join #Data d on vd.data_id = d.Id
)
SELECT * FROM CTE
PIVOT( MAX(col_1_value) FOR Col1 IN ([A-Alpha])) PVT_A
PIVOT( MAX(col_2_value) FOR Col2 IN ([B-Beta])) PVT_B
but it wont work well with the data that i'm using with the joining tables because my database will be dynamic, anyone had a solution with the same case?

How to pull the average sales per visit of the top 10% of customers from 3 segments in T-SQL?

I'm newer to my job and i am not used to doing queries as complicated as the one i am being asked to do. Please help! I'm trying to pull the average sales per visit of the top 10% of customers from 3 customer segments. I'm stuck on pulling only the top 10% of customers. Here are two sample tables and what i have so far.
SELECT
cust_segment.segment,
AVG(cust_sales.sales) / cust_sales.visits AS "sales/vist"
FROM
cust_segment
INNER JOIN
cust_sales ON cust_segment.Customer = cust_sales.Customer
WHERE
cust_sales in (SELECT TOP 10 Percent cust_sales.sales, cust_segement.segment
FROM cust_segment
INNER JOIN cust_sales ON cust_segment.customer = cust_sales.customer)
GROUP BY
segment;
cust_segment
+-------------+---------+
| Customer | Segment |
+-------------+---------+
| 10000834678 | A |
| 10000467169 | A |
| 10000217202 | B |
| 10001562687 | C |
| 10000742574 | C |
| 10001577918 | A |
| 10000825179 | B |
| 10000019009 | B |
| 10001225606 | C |
| 10000473429 | A |
+-------------+---------+
cust_sales
+-------------+----------------+--------+
| Customer | Sales | Visits |
+-------------+----------------+--------+
| 10000834678 | $ 54.56 | 8 |
| 10000467169 | $ 27.61 | 7 |
| 10000217202 | $ 150.01 | 39 |
| 10001562687 | $ 39.59 | 8 |
| 10000742574 | $ 18.35 | 9 |
| 10001577918 | $ 23.72 | 4 |
| 10000825179 | $ 7.69 | 7 |
| 10000019009 | $ 94.41 | 47 |
| 10001225606 | $ 36.00 | 12 |
| 10000473429 | $ 5.76 | 6 |
+-------------+----------------+--------+
It should return:
+---------+-------------+
| Segment | Sales/Visit |
+---------+-------------+
| A | 6.82 |
| B | 3.846410256 |
| C | 4.94875 |
+---------+-------------+
This should do it:
SELECT
seg.Segment,
SUM( sales.Sales ) / SUM( sales.Visits ) AS [Sales/Visit]
FROM
cust_segment AS seg
LEFT OUTER JOIN cust_sales AS sales
ON seg.Customer = sales.Customer
AND seg.Customer IN (
SELECT TOP 10 PERCENT sa.Customer
FROM cust_sales AS sa
INNER JOIN cust_segment AS sg
ON sg.Segment = seg.Segment
AND sg.Customer = sa.Customer
ORDER BY sa.Sales DESC
)
GROUP BY
seg.Segment
;With cust_segment( Customer , Segment )
AS
(
SELECT 10000834678 , 'A' union all
SELECT 10000467169 , 'A' union all
SELECT 10000217202 , 'B' union all
SELECT 10001562687 , 'C' union all
SELECT 10000742574 , 'C' union all
SELECT 10001577918 , 'A' union all
SELECT 10000825179 , 'B' union all
SELECT 10000019009 , 'B' union all
SELECT 10001225606 , 'C' union all
SELECT 10000473429 , 'A'
)
,cust_sales(Customer,Sales,Visits)
AS
(
SELECT 10000834678 , 54.56 , 8 UNION ALL
SELECT 10000467169 , 27.61 , 7 UNION ALL
SELECT 10000217202 , 150.01 , 39 UNION ALL
SELECT 10001562687 , 39.59 , 8 UNION ALL
SELECT 10000742574 , 18.35 , 9 UNION ALL
SELECT 10001577918 , 23.72 , 4 UNION ALL
SELECT 10000825179 , 7.69 , 7 UNION ALL
SELECT 10000019009 , 94.41 , 47 UNION ALL
SELECT 10001225606 , 36.00 , 12 UNION ALL
SELECT 10000473429 , 5.76 , 6
)
SELECT Segment,seg AS [Sales/Visit] FROM
(
SELECT * ,ROW_NUMBER()OVER(PARTITION by seg ORDER BY Segment )Seq FROM
(
SELECT * ,MAX([Sales/Visit])OVER (PARTITION BY Segment ORDER BY Segment)seg FROM
(
SELECT C.Customer,c.Segment,SUM(Sales) /SUM(Visits) AS [Sales/Visit] FROM cust_sales s
INNER JOIN cust_segment c ON c.Customer=s.Customer
GROUP BY C.Customer,c.Segment
)dt
)dt2
)Final WHERE Final.Seq=1 ORDER BY 1
OutPut
Segment|Sales/Visit
------------------
A 6.820000
B 3.846410
C 4.948750

sql server, aggregate (count) on many columns in table

I have a table which I have simplified to that below. It actually extends up to Type60
Number | Type1 | Type2 | Type3 | Type4
------------------------------------------------------
1 | 1 | 1 | 1 | 0
2 | 1 | 1 | 1 | 0
3 | 1 | 1 | 1 | 0
4 | 0 | 2 | 0 | 1
5 | 0 | 2 | 1 | 0
6 | 0 | 2 | 0 | 1
7 | 1 | 1 | 1 | 0
8 | 1 | 1 | 0 | 1
9 | 1 | 2 | 1 | 0
10 | 0 | 2 | 0 | 1
The output that I want, is like below (extended to Name60)
Name | Value | CountOfValue
-----------------------------------
Name1 | 0 | 4
Name1 | 1 | 6
Name2 | 1 | 5
Name2 | 2 | 5
Name3 | 0 | 6
Name3 | 1 | 4
Name4 | 0 | 4
Name4 | 1 | 6
To get there, I have been doing:
select 'Name1' as Name, Type1, Count(Number)
from table1
group by Type1
union
select 'Name2' as Name, Type2, Count(Number)
from table1
group by Type2
union
select 'Name3' as Name, Type3, Count(Number)
from table1
group by Type3
union
select 'Name4' as Name, Type4, Count(Number)
from table1
group by Type4
upto Name60 etc
this take a fair while to run,
Is there a more efficent (faster) way to do the same thing does anyone know?
Thanks, Gary
You could try UNPIVOT.
SELECT Name, [values] AS Value, Count(*) AS CountOfValue
FROM
(
SELECT REPLACE([Type], 'Type', 'Name') AS Name, [values]
FROM #testPivot
UNPIVOT ([values] FOR [type] IN
(Type1, Type2, Type3, Type4) -- ETC to Type60
) AS unpvt
) AS test
GROUP BY Name, [values]
ORDER BY Name, Value
You'd still have to put in all the Type1 to Type 60 in the UNPIVOT. Check out PIVOT and UNPIVOT.

SQL Server 2005 T-SQL Problem: Need help in omitting records

Good day!
I need help in writing a query.. I have records in a table below.. The condition would be no records should be displayed if the succeeding records' new_state was repeated from the previous records(new_state) and if it is changed in the same date..
here record_id 1 has gone through the ff states: 0->1->2->1->3->4->3 in the same day.. state 1 was changed to state 2 then back to state 1 again (id 2 & 3 would not be displayed).. same with state 3 (id 5 & 6 would not be displayed)..
id | record_id| date_changed | old_state | new_state |
1 | 1 | 2009-01-01 | 0 | 1 |
2 | 1 | 2009-01-01 | 1 | 2 | not displayed
3 | 1 | 2009-01-01 | 2 | 1 | not displayed
4 | 1 | 2009-01-01 | 1 | 3 |
5 | 1 | 2009-01-01 | 3 | 4 | not displayed
6 | 1 | 2009-01-01 | 4 | 3 | not displayed
so the result would display only 2 records for record_id=1..
id | record_id| date_changed | old_state | new_state |
1 | 1 | 2009-01-01 | 0 | 1 |
4 | 1 | 2009-01-01 | 1 | 3 |
Here's the code for table creation and data:
IF OBJECT_ID('TempDB..#table','U') IS NOT NULL
DROP TABLE #table
CREATE TABLE #table
(
id INT identity primary key,
record_id INT,
date_changed DATETIME,
old_state INT,
new_state INT
)
INSERT INTO #table(record_id,date_changed,old_state,new_state)
SELECT 1,'2009-01-01',0,1 UNION ALL --displayed
SELECT 1,'2009-01-01',1,2 UNION ALL --not displayed
SELECT 1,'2009-01-01',2,1 UNION ALL --not displayed
SELECT 1,'2009-01-01',1,3 UNION ALL --displayed
SELECT 1,'2009-01-01',3,4 UNION ALL --not displayed
SELECT 1,'2009-01-01',4,3 --not displayed
INSERT INTO #table(record_id,date_changed,old_state,new_state)
SELECT 3,'2009-01-01',0,1 UNION ALL --displayed
SELECT 3,'2009-01-01',1,2 UNION ALL --not displayed
SELECT 3,'2009-01-01',2,3 UNION ALL --not displayed
SELECT 3,'2009-01-01',3,4 UNION ALL --not displayed
SELECT 3,'2009-01-01',4,1 --not displayed
SELECT * FROM #table
I would appreciate any help..
Thanks
For clarity regarding record_id=3.. Given this table:
id | record_id| date_changed | old_state | new_state |
7 | 3 | 2009-01-01 | 0 | 1 |
8 | 3 | 2009-01-01 | 1 | 2 | not displayed
9 | 3 | 2009-01-01 | 2 | 3 | not displayed
10 | 3 | 2009-01-01 | 3 | 4 | not displayed
11 | 3 | 2009-01-01 | 4 | 1 | not displayed
when running the query for record_id=3, the table result will be:
id | record_id| date_changed | old_state | new_state |
7 | 3 | 2009-01-01 | 0 | 1 |
Thanks!
UPDATE (12/2/2009):
Special scenario
id | record_id| date_changed | old_state | new_state |
1 | 4 | 2009-01-01 | 0 | 1 | displayed
2 | 4 | 2009-01-01 | 1 | 2 | displayed
3 | 4 | 2009-01-01 | 2 | 3 | not displayed
4 | 4 | 2009-01-01 | 3 | 2 | not displayed
5 | 4 | 2009-01-01 | 2 | 3 | displayed
6 | 4 | 2009-01-01 | 3 | 4 | not displayed
7 | 4 | 2009-01-01 | 4 | 3 | not displayed
where new_state 3 appears on id 3,5 and 7.. id 3 would not be displayed since it is between id 2 and id 4 which have the same new_state(3).. Then id 5 should be displayed since there is no existing new_state 3 yet..
code snippet:
IF OBJECT_ID('TempDB..#tablex','U') IS NOT NULL
DROP TABLE #tablex
CREATE TABLE #tablex
(
id INT identity primary key,
record_id INT,
date_changed DATETIME,
old_state INT,
new_state INT
)
INSERT INTO #tablex(record_id,date_changed,old_state,new_state)
SELECT 4,'2009-01-01',0,1 UNION ALL --displayed
SELECT 4,'2009-01-01',1,2 UNION ALL --displayed
SELECT 4,'2009-01-01',2,3 UNION ALL --not displayed
SELECT 4,'2009-01-01',3,2 UNION ALL --not displayed
SELECT 4,'2009-01-01',2,3 UNION ALL --displayed
SELECT 4,'2009-01-01',3,4 UNION ALL --not displayed
SELECT 4,'2009-01-01',4,3 --not displayed
I think the sequence in building the result is important..
Thanks!
SELECT A.*
/*
A.ID, A.old_state, a.new_state,
B.ID as [Next], b.old_state, b.new_state,
C.ID as [Prev], c.old_state, c.new_state
*/
FROM #table A LEFT JOIN
#table B ON A.ID = (B.ID - 1)
LEFT JOIN #table C ON (A.ID - 1) = C.ID
-- WHERE A.old_State <> B.new_State AND A.new_State <> C.old_State
WHERE A.record_id = 1
AND A.old_State <> COALESCE(B.new_State, -1)
AND A.new_State <> COALESCE(C.old_State, -1)
EDIT: I guess, what OP needs is that the remaining record should be selected except those where current record's old state is not the same as next record's new state (kind of an undo operation in records) and current record's new state should not be same as previous record's old state.
Following steps to get to the result
select all items that should not appear in the result.
left join these with the original table and select only those records that don't match a should not appear record.
.
;WITH cte_table (master_id, master_state, id, record_id, old_state, new_state, level) AS
(
SELECT id, old_state, id, record_id, old_state, new_state, 1
FROM #table
UNION ALL
SELECT master_id, master_state, #table.id, #table.record_id, #table.old_state, #table.new_state, level + 1
FROM cte_table
INNER JOIN #table ON cte_table.new_state = #table.old_state
AND cte_table.record_id = #table.record_id
AND cte_table.id < #table.id
AND cte_table.master_state < #table.old_state
)
SELECT master_id, t1.*, level
INTO #result
FROM #table t1
INNER JOIN (
SELECT master_id, min_child_id = MIN(id), level
FROM cte_table
GROUP BY master_id, level
) t2 ON t2.min_child_id = t1.id
SELECT t1.*
FROM #table t1
LEFT OUTER JOIN (
SELECT r1.id
FROM #result r1
INNER JOIN (
SELECT r1.master_id
FROM #result r1
INNER JOIN #result r2 ON r2.new_state = r1.old_state
AND r2.master_id = r1.master_id
WHERE r1.level = 1
) r2 ON r2.master_id = r1.master_id
) r1 ON r1.id = t1.id
WHERE r1.id IS NULL
AND t1.old_state < t1.new_state
ORDER BY 1, 2, 3

Resources