sql query group by sql server - sql-server

I have a sql server database table with columns as shown below :
Table1
Id Name ErrorId
1 AB
2 CD
3 AB 3
4 AB 4
I want to get an output something like this :
Name IdCount ErrorIdCount ErrorIds
AB 3 2 4,3
CD 1 0 0
I wrote a query which looks like this currently :
select Name, Count(Id) as IdCount,
Count(Distinct case when ErrorId != ' ' then Id END) as ErrorIdCount
from Table1
group by Name;
It gives me something like this below :
Name IdCount ErrorIdCount.
AB 3 2
CD 1 0
I cannot figure out how I can include the ErrorIds too in my query ?
Can anyone point me out how I can solve this ?

Declare #a table (Id int, Name varchar(10),ErrorId int)
insert into #a Values (1,'AB',null),(2,'CD',null),(3,'AB',3),(4,'AB',4);
Select Name, Count(Id) as IdCount,
Count(Distinct case when ErrorId != ' ' then Id END) as ErrorIdCount
,[ErrorIds]=
STUFF((SELECT ', ' + Cast(ErrorId as Varchar(10))
FROM #a iup
WHERE iup.Name = a.Name
order by ErrorId
FOR XML PATH('')), 1, 1, '')
from #a a
Group by Name

Related

What is the best way to find records from a field that contains an int array? by the way, the query params also is an int array

Let's say I have a table 'T1' that contains 3 records like this:
A B
101,103,115,189 NAME1
101,115 NAME2
102,116 NAME3
and now I have to find all the rows from field A that contains (101,102,115) which should be NAME1, NAME2 and NAME3.
Since there are over 100000 rows, I need to find an effective way to do this.
Very much appreciate for any kind help.
I'm using SQL Server 2014.
Solution:
I create a third table to maintain the relationship between Job and Category table, the final query should be like this:
SELECT * FROM Job WHERE Job_Id IN (
SELECT DISTINCT(Job_Id) FROM Job_RS_Category WHERE Category_Id in (100015,100054,100060,100062,100063,100068,100070,100072,100073,100081,100096,100099))
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE Testdata
(
SomeID INT,
A VARCHAR(MAX),
B VARCHAR(MAX)
)
INSERT Testdata SELECT 1, '101,103,115,189', 'NAME1'
INSERT Testdata SELECT 2, '101,115' ,'NAME2'
INSERT Testdata SELECT 3, '102,116' , 'NAME3'
Query 1:
;WITH tmp(SomeID, B, DataItem, A) AS
(
SELECT
SomeID,
B,
LEFT(A, CHARINDEX(',', A + ',') - 1),
STUFF(A, 1, CHARINDEX(',', A + ','), '')
FROM Testdata
UNION all
SELECT
SomeID,
B,
LEFT(A, CHARINDEX(',', A + ',') - 1),
STUFF(A, 1, CHARINDEX(',', A + ','), '')
FROM tmp
WHERE
A > ''
)
select distinct B from tmp where DataItem IN ('101','102','115')
Results:
| B |
|-------|
| NAME1 |
| NAME2 |
| NAME3 |

SQL SERVER Pivot rows to column

I have the input as
123 1 Y Active
123 1 Y Idle
109 1 Y Active
109 1 Y Away
165 1 Y Active
145 1 Y Idle
I need the output as :
123 1 Y Active and Idle
109 1 Y Active and Away
165 1 Y Active Only
145 1 Y Idle Only
SELECT distinct COl1,Col2,Col3 ,
case when Col4 = 'Active' and Col4 = 'Idle' then 'Active and Idle'
when Col4 = 'Active' and Col4 = 'Away' then 'Active and Away'
when Col4 = 'Active' then 'Active Only'
when Col4 = 'Idle' then 'Idle Only'
end as category
from Person
I tried the above query but it is not working and it wont work because i am
trying to convert multiple rows to 1 column, which is not possible with
Case.
I have to try something like PIVOT, but not able to implement . Any help?
You need FOR XML PATH() approach :
SELECT DISTINCT p.col1, STUFF(pp.Col4, 1, 4, '') AS category
FRPM Person p CROSS APPLY
( SELECT ' AND ' + p1.Col4
FROM Person p1
WHERE P1.col1 = p.col1
FOR XML PATH('')
) pp(Col4);
By this way FOR XML PATH('') would return xml element for each col1 & outer query STUFF() will replace AND with '' at the start of position of string.
try the following:
declare #table table(
col1 int,col2 int,col3 nvarchar(10),col4 nvarchar(50)
)
insert into #table values(123, 1, 'Y' ,'Active')
insert into #table values(123, 1, 'Y' ,'Idle')
insert into #table values(109, 1, 'Y' ,'Active')
insert into #table values(109, 1, 'Y' ,'Away')
insert into #table values(165, 1, 'Y' ,'Active')
insert into #table values(145, 1, 'Y' ,'Idle')
select col1,col2,col3, stuff((SELECT ' AND ' + t2.col4 + CASE WHEN count(t1.col1) = 1 THEN ' ONLY' ELSE '' END
FROM #table t2
WHERE t2.col1 = t1.col1
ORDER BY t2.col1
FOR XML PATH(''), TYPE).value('.', 'varchar(max)')
,1,5,'') AS col4
from #table t1
group by col1,col2,col3
I'm using the stuff to concat values that does not include in the group.
And when count = 1 its mean that there is only one item in this group so you need to add the Only suffix.
That's not such an obvious question, even in SQL Server 2017 which provides STRING_AGG. A check is needed to append Only if there is only one row per group.
Given this table :
declare #table table (id int, flag1 int, flag2 char(1), status varchar(40))
insert into #table
values
(123,1,'Y','Active'),
(123,1,'Y','Idle'),
(109,1,'Y','Active'),
(109,1,'Y','Away'),
(165,1,'Y','Active'),
(145,1,'Y','Idle')
The following query will return the aggregated values. If there's only one value, it will append Only :
select id,flag1,flag2,
string_agg(status,' and ') + IIF(count(*)>1,'',' Only')
from #table
group by id,flag1,flag2
This produces :
109 1 Y Active and Away
123 1 Y Active and Idle
145 1 Y Idle Only
165 1 Y Active Only

Converting rows to columns

I have a query with the columns 'Name', 'Amount', and 'ReasonId'. I want to sum the amount and put the reasons on one row to keep every name to a single line. There are about 50 distinct ReasonId's so I do not want to name the column the name of the ReasonId's. Instead, I would like to name the columns 'Reason1', 'Reason2', 'Reason3', and 'Reason4'. One single name can have up to 4 different reasons.
I have this:
Name Amount ReasonId
-------------------------
Bob $5 7
Bob $8 6
John $2 8
John $5 9
John $3 9
John $8 4
I want to produce the following:
Name Amount Reason1 Reason2 Reason3 Reason4
-----------------------------------------------------
Bob $13 7 6 NULL NULL
John $18 8 9 4 NULL
One way to do this is to use the dense_rank window function to number the rows, and then use conditional aggregation to put the reason in the correct columns.
I can't see anything that would give the specific order of the reason columns though, maybe there is some column missing that provides the order?
with cte as (
select
name,
reasonid,
amount,
dense_rank() over (partition by name order by reasonid) rn
from your_table
)
select
name,
sum(amount) amount,
max(case when rn = 1 then reasonid end) reason1,
max(case when rn = 2 then reasonid end) reason2,
max(case when rn = 3 then reasonid end) reason3,
max(case when rn = 4 then reasonid end) reason4
from cte
group by name
If you have some column that gives the order you want then change the order by clause used in the dense_rank function.
Sample SQL Fiddle (using PG as MSSQL seems to be offline).
The output from the query above would be:
| name | amount | reason1 | reason2 | reason3 | reason4 |
|------|--------|---------|---------|---------|---------|
| Bob | 13 | 6 | 7 | (null) | (null) |
| John | 18 | 4 | 8 | 9 | (null) |
You could also use a pivot to achieve this; if you know the columns you can enter them in the script, but if not, you can use dynamic sql (there are reasons why you might want to avoid the dynamic solution).
The advantage of this route is that you can enter the column list in a table and then changes to that table will result in changes to your output with change to the script involved. The disadvantages are all those associated with dynamic SQL.
In the interests of variation, here is a dynamic SQL solution using temp tables to hold your data, since a different possibility has been provided:
-- set up your data
CREATE TABLE #MyTab (Name VARCHAR(4), Amount INT, ReasonId INT)
CREATE TABLE #AllPossibleReasons (Id INT,Label VARCHAR(10))
INSERT #AllPossibleReasons
VALUES
(1,'Reason1')
,(2,'Reason2')
,(3,'Reason3')
,(4,'Reason4')
,(5,'Reason5')
,(6,'Reason6')
,(7,'Reason7')
,(8,'Reason8')
,(9,'Reason9')
INSERT #MyTab
VALUES
('Bob',7,7)
,('Bob',8,6)
,('John',2,8)
,('John',5,9)
,('John',3,9)
,('John',8,4)
-----------------------------------------------------------------------------
-- The actual query
DECLARE #ReasonList VARCHAR(MAX) = ''
DECLARE #SQL VARCHAR(MAX)
SELECT #ReasonList = #ReasonList + ',' + QUOTENAME(Label)
FROM #AllPossibleReasons
SET #ReasonList = SUBSTRING(#ReasonList,2,LEN(#ReasonList))
SET #SQL =
'SELECT Name,Value,' + #ReasonList + ' FROM
(SELECT
M.Name,SUM(Amount) AS This, Label, SUM(Total.Value) AS Value
FROM
#MyTab AS M
INNER JOIN #AllPossibleReasons AS Reason ON M.ReasonId = Reason.Id
INNER JOIN(SELECT T.Name, SUM(Amount)Value
FROM #MyTab T GROUP BY T.Name) AS Total ON M.Name = Total.Name
GROUP BY M.Name, Reason.Label) AS Up
PIVOT (SUM(THis) FOR Label IN (' + #ReasonList + ')) AS Pvt'
EXEC (#SQL)
DROP TABLE #AllPossibleReasons
DROP TABLE #MyTab
Working from the information in ListAGG in SQLSERVER, I came up with this somewhat ugly example:
with tbl1 as (
-- Set up initial data set
select 'Bob' name, 5 amount, 7 ReasonId
union all select 'Bob' , 3, 4
union all select 'Bob', 2, 1
union all select 'Brian', 8, 2
union all select 'Bob', 6, 4
union all select 'Brian', 1, 3
union all select 'Tim', 2, 2)
, TBL2 AS ( -- Add a blank to separate the concatenation
SELECT NAME
, AMOUNT
, CAST(ReasonId as varchar) + ' ' ReasonId from tbl1
)
select ta.name
, Total
, ReasonIds from (
(select distinct name, stuff((select distinct '' + t2.ReasonId from tbl2 t2
where t1.name = t2.name
for xml path(''), type).value('.','NVARCHAR(MAX)'),1,0,' ') ReasonIds from tbl2 t1) ta
inner join ( select name, sum(amount) Total from tbl1 group by name) tb on ta.name = tb.name) ;
This converts TBL1 to the following:
name Total ReasonIds
Bob 16 1 4 7
Brian 9 2 3
Tim 2 2

How to transform SQL Server row data to columns?

I queried data like this
member_no cover_version product_id product_name product_type
--------- ------------- ---------- -------------------- ------------
11421 7 4 Excellent More E
11421 7 15 Comprehensive Data D
But I want to shape this data like this:
member_no cover_version product_e_id product_e_name product_d_id product_d_name
--------- ------------- ------------ -------------------- ------------ --------------------
11421 7 4 Excellent More 15 Comprehensive Data
I am using SQL Server 2008. What should be the best approach to shape the data as I want?
Assuming you've only got product types D and E as stated, a simple self-join will get you what you're after.
If you want something more generic, please expand your question.
DROP TABLE IF EXISTS #Demo
SELECT
*
INTO
#Demo
FROM
(VALUES
(11421, 7, 4, 'Excellent More', 'E')
,(11421, 7, 15, 'Comprehensive Data', 'D')) A
(member_no, cover_version, product_id, product_name, product_type)
SELECT
D.member_no
,D.cover_version
,E.product_id product_e_id
,E.product_name product_e_name
,D.product_id product_d_id
,D.product_name product_d_name
FROM
#Demo D
JOIN #Demo E ON D.member_no = E.member_no
AND D.product_type = 'D'
AND E.product_type = 'E';
member_no cover_version product_e_id product_e_name product_d_id product_d_name
----------- ------------- ------------ ------------------ ------------ ------------------
11421 7 4 Excellent More 15 Comprehensive Data
If you want to do this dynamically, based on unknown product types, you can use this.
DECLARE #SQL NVARCHAR(MAX),
#Columns NVARCHAR(MAX),
#CaseExpressions NVARCHAR(MAX) = 'MAX(CASE WHEN product_type = ''<<productType>>'' THEN product_id END) AS [product_<<productType>>_id],
MAX(CASE WHEN product_type = ''<<productType>>'' THEN product_name END) AS [product_<<productType>>_name]'
-- build concatenated string with 2 columns for each product_type in your table.
-- group by product_type to get distinct results.
-- replaces <<productType>> with the actual product_type value
SELECT #Columns = COALESCE(#Columns + ',', '') + REPLACE(#CaseExpressions, '<<productType>>', product_type)
FROM myTable
GROUP BY product_type
-- build select query
SET #SQL = 'SELECT member_no, cover_version,' + #Columns + ' FROM myTable GROUP BY member_no, cover_version'
-- to see what the dynamic sql looks like
PRINT #SQL
-- to execute the dynamic sql and see result
EXEC (#SQL)

Group by count once

My data is like below:
ClassId ClassName StudentId Subject SubjectId
-----------------------------------------------------
1 ESL 12 English 20
1 ESL 13 Science 30
1 ESL 12 Social 40
1 ESL 12 Maths 50
Required output: parameters are Subject column values
ClassId ClassName TotalStudents SubjectIds
-----------------------------------------------
1 ESL 2 20, 40, 50, 30
When one student takes multiple subjects then count student only once, so in the above data 12 is one student id takes multiple subjects so counted only once. TotalStudents value is 2 (1 from student id 12 and 1 from student id 13)
I am not looking for how to display subjectIds column value in comma separated string.
Thanks in advance
COUNT DISTINCT then use STUFF for combined the subject
declare #temp table
(ClassId int,ClassName nvarchar(max),StudentId int,Subject nvarchar(max), SubjectId int)
insert into #temp values (1,'ESL',12,'English' , 20 )
insert into #temp values (1,'ESL',13,'Science' , 30 )
insert into #temp values (1,'ESL',12,'Social ' , 40 )
insert into #temp values (1,'ESL',12,'Maths ' , 50 )
select ClassId,ClassName,COUNT(DISTINCT StudentId) CNT,
STUFF( (SELECT ',' + CAST(t1.SubjectId AS NVARCHAR)
FROM #temp t1
WHERE StudentId = t1.StudentId
FOR XML PATH('')),
1, 1, '') SubjectIdS
from #temp
GROUP BY ClassId,ClassName
OUTPUT
DISTINCT can be applied inside aggregate functions.
SELECT COUNT(DISTINCT column_name) FROM table_name;
If you don't need to display the SubjectIds, then you need to use a GROUP BY clause to group the resultset by ClassId and ClassName.
SELECT ClassId, ClassName, COUNT(distinct StudentId) as TotalStudents
FROM MyTable
GROUP BY ClassId, ClassName
See this example at SqlFiddle

Resources