Introduction
It is really great to use GROUP BY
. But what about when we have to create a query that would:
- Select specific row/rows
- Combine multiple rows into a single row/column
of a group.
Check out the example below to walk through the code samples and final solutions to select a single row or to roll-up multiple rows into a single row in SQL Server.
Data
Think about a survey scenario, where a user has to answer a number of multiple choice questions. Plus the user can answer the same question as many times as he/she wants.
CREATE TABLE #tblSurvey(
ParticipantName VARCHAR(50),
QuestionName VARCHAR(50),
SelectedOption VARCHAR(50),
ResponseDateTime DATETIME
);
INSERT
INTO #tblSurvey
VALUES
('Dan', 'Q1', 'F1', '20180521 10:34:01 AM'),
('Dan', 'Q1', 'F2', '20180521 10:34:03 AM'),
('Dan', 'Q1', 'F2', '20180521 10:34:05 AM'),
('Dan', 'Q2', 'F1', '20180521 10:34:01 AM'),
('Dan', 'Q2', 'F1', '20180521 10:34:03 AM'),
('Dan', 'Q2', 'F2', '20180521 10:34:05 AM'),
('Dan', 'Q3', 'F2', '20180521 10:34:01 AM'),
('Dan', 'Q3', 'F2', '20180521 10:34:03 AM'),
('Dan', 'Q3', 'F1', '20180521 10:34:05 AM');
SELECT * FROM #tblSurvey;
Regular Query With Where, Group, Having And Order
Here is a simple example to use WHERE
, GROUP BY
, HAVING
and ORDER BY
in a single query:
SELECT ParticipantName, QuestionName, COUNT(SelectedOption) AS OptionCount
FROM #tblSurvey
WHERE QuestionName IN ('Q1', 'Q2', 'Q3')
GROUP BY ParticipantName, QuestionName
HAVING COUNT(SelectedOption) > 1
ORDER BY QuestionName DESC;
Group Wise Last/First Row
GROUP BY ParticipantName, QuestionName
wise first/last Inserted row comparing ResponseDateTime
.
WITH LastResponse
AS
(
SELECT ROW_NUMBER() OVER(
PARTITION BY ParticipantName, QuestionName
ORDER BY ResponseDateTime DESC
) AS OrderId, *
FROM #tblSurvey
)
SELECT *
FROM LastResponse
WHERE OrderId = 1;
We can add few more variations to this query, like:
Nth Row
WHERE OrderId = 3;
Specific Nth Rows
WHERE OrderId IN (3, 5);
N number of Rows
WHERE OrderId < 5;
Group Wise Highest/Lowest Count Row
GROUP BY ParticipantName, QuestionName, SelectedOption
wise most/least SelectedOption
.
WITH OptionWiseCount
AS
(
SELECT
*,
COUNT(SelectedOption) OVER(PARTITION BY ParticipantName, QuestionName, SelectedOption) _
AS OptionCount
FROM #tblSurvey
),
MostResponse
AS
(
SELECT ROW_NUMBER() OVER(
PARTITION BY ParticipantName, QuestionName
ORDER BY OptionCount DESC
) AS OrderId, *
FROM OptionWiseCount
)
SELECT *
FROM MostResponse
WHERE OrderId = 1;
Group Rows to a Single Column
GROUP BY ParticipantName, QuestionName
wise SelectedOption
's to a single column:
DECLARE @separator CHAR = ',';
SELECT
ParticipantName,
QuestionName,
COUNT(SelectedOption) AS TotalSelectedOption,
STUFF((SELECT @separator + CONVERT(NVARCHAR(MAX), SelectedOption)
FROM #tblSurvey AS uc
WHERE uc.ParticipantName = g.ParticipantName
AND uc.QuestionName = g.QuestionName
ORDER BY SelectedOption
FOR XML PATH(''), TYPE).value('.','NVARCHAR(MAX)')
, 1, 1, '') AS SelectedOptions
FROM #tblSurvey AS g
GROUP BY ParticipantName, QuestionName;
This is also possible with a custom aggregate function.
Show Group Id
LastResponse
AS
(
SELECT
ROW_NUMBER() OVER(
PARTITION BY ParticipantName, QuestionName
ORDER BY ResponseDateTime DESC
) AS OrderId,
DENSE_RANK () OVER(
ORDER BY ParticipantName, QuestionName
) AS GroupId,
*
FROM #tblSurvey
)
SELECT *
FROM LastResponse;
Good to Read
Please find the necessary SQL file as an attachment.