Sql rows count with grouping - sql-server

Hello I have two tables as below
tblContactType
typeId typeName active
1 Email 1
2 Phone 1
3 Address 1
4 Fax 1
tblContact
id IdName typeId groupId
100 test 1 1
101 test2 1 1
102 test3 1 2
103 test4 2 2
104 test5 2 3
105 test6 3 3
Want the results to be with column names as typeName count and grouped by group id. Results should be total number of types associated to a group,which are associated to a contact.
GroupId EmailCount PhoneCount AddressCount FaxCount
1 2 0 0 0
2 1 1 0 0
3 0 1 1 0

You can group by and pivot as below:
Select * from (
Select t.groupid, tct.typename, t.id from tblContact t
inner join tblContactType tct
on t.typeid = tct.typeid
) a
pivot (count(a.id) for typename in ([Email],[Phone],[Address],[Fax]) ) p
For dynamic list of columns you can use dynamic query as below:
declare #cols1 varchar(max)
declare #query nvarchar(max)
Select #cols1 = stuff((Select distinct ','+QuoteName(typename) from tblContactType for xml path('')),1,1,'')
Set #query = ' Select * from (
Select t.groupid, tct.typename, t.id from tblContact t
inner join tblContactType tct
on t.typeid = tct.typeid
) a
pivot (count(a.id) for typename in (' + #cols1 + ') ) p '
Select #query --Check the generated query is good and then execute below
--exec sp_executesql #query
Output as below:
+---------+---------+-------+-----+-------+
| groupid | Address | Email | Fax | Phone |
+---------+---------+-------+-----+-------+
| 1 | 0 | 2 | 0 | 0 |
| 2 | 0 | 1 | 0 | 1 |
| 3 | 1 | 0 | 0 | 1 |
+---------+---------+-------+-----+-------+

Here is another solution.
SELECT groupId,
SUM(CASE WHEN c.typeId = 1 THEN 1 ELSE 0 END) 'EmailCount',
SUM(CASE WHEN c.typeId = 2 THEN 1 ELSE 0 END) 'PhoneCount',
SUM(CASE WHEN c.typeId = 3 THEN 1 ELSE 0 END) 'AddressCount',
SUM(CASE WHEN c.typeId = 4 THEN 1 ELSE 0 END) 'FaxCount'
FROM tblContact c
JOIN tblContactType ct ON c.typeId = ct.typeId
GROUP BY groupId
Results
-------------------------------------------------------------
groupId | EmailCount | PhoneCount | AddressCount | FaxCount
-------------------------------------------------------------
1 | 2 | 0 | 0 | 0
2 | 1 | 1 | 0 | 0
3 | 0 | 1 | 1 | 0
-------------------------------------------------------------

Related

SQL Server Group By Combine Pivot Table Approaches

I have tables which designed like survey questions.
Answer table store like below. One answer related with three questions. QuestionId 1 and QuestionId 2 store Yes or No answers. If QuestionId 1 and QuestionId 2 both of them answer Yes, I want QuesiontId 3 to sum count with column value otherwise alias should be Others. Which approach is better , could u please help :)
AnswerId | QuestionId | QuestionOptionText
---------+------------+-------------------
1 | 1 | No
1 | 2 | No
1 | 3 | 0
--------------------------------------
2 | 1 | Yes
2 | 2 | No
2 | 3 | 0
--------------------------------------
3 | 1 | No
3 | 2 | Yes
3 | 3 | 1-10
--------------------------------------
4 | 1 | Yes
4 | 2 | Yes
4 | 3 | 1-10
--------------------------------------
5 | 1 | Yes
5 | 2 | Yes
5 | 3 | 11-20
Result should be like that
1-10 | 11-20 | Other
-----+-------+-------
1 | 1 | 3
I think you need a self join of the three types of question, and then the summing logic is easier. A different table structure would probably have simplified things. Normally you would have each question in a separate column.
select
sum(iif(ans1 = 'Yes' and ans2 = 'Yes' and ans3 = '1-10',1,0)) as '1-10',
sum(iif(ans1 = 'Yes' and ans2 = 'Yes' and ans3 = '11-20',1,0)) as '11-20',
sum(iif(ans1 <> 'Yes' or ans2 = 'Yes',1,0)) as 'other'
from (
select QuestionOptionText as ans1 from t where QuestionId = 1
) as a1
inner join (
select QuestionOptionText as ans2 from t where QuestionId = 2
) as a2
on a1.AnswerId = a2.AnswerId
inner join (
select QuestionOptionText as ans3 from t where QuestionId = 3
) as a3
on a1.AnswerId = a3.AnswerId
Is this what you want?
select sum(case when QuestionOptionText = '1-10' then 1 else 0 end),
sum(case when QuestionOptionText = '11-20' then 1 else 0 end),
sum(case when QuestionOptionText not in ('1-10', '11-20') then 1 else 0 end)
from t
where QuestionId = 3;
You could do this with two levels of aggregation. First compute the outcome of each series of answerId, the pivot the resultset:
select
sum(case when res = '1-10' then 1 else 0 end) res_1_10,
sum(case when res = '11-20' then 1 else 0 end) res_1_20,
sum(case when res = 'Other' then 1 else 0 end) res_other
from (
select
case when max(case when QuestionId in (1, 2) then QuestionOptionText end) = 'Yes'
then max(case when QuestionId = 3 then QuestionOptionText end)
else 'Other'
end res
from mytable
group by answerId
) t

Grouping SQL columns from one table

I am currently having difficulty getting the correct values from my table. Here is my table
NOTE: The column Status has 3 possible values (Cleaned, Unclean, Closed)
+-----------+-------------+--------+------------+
|ApplicantID|ApplicantName| Status | HireDate |
+-----------+-------------+--------+------------+
| 1 | John Smith |Cleaned |08/26/2015 |
| 2 | Alex Murphy |Closed |09/12/2015 |
| 3 | Oliver David|Cleaned |01/11/2015 |
| 4 | Max Payne |Unclean |03/18/2015 |
+-----------+-------------+--------+------------+
The output I'm expecting and it should also be sorted by year.
For example I call all these records for the year 2015 which I get using the variable #Year.
NOTE: The column Total is the SUM of Cleaned and Unclean
+---------+-----------+-----------+----------+---------+
| Month | Cleaned | Unclean | Closed | Total |
+---------+-----------+-----------+----------+---------+
| January| 1 | 0 | 0 | 1 |
| February| 0 | 0 | 0 | 0 |
| March | 0 | 1 | 0 | 1 |
| April | 0 | 0 | 0 | 0 |
| May | 0 | 0 | 0 | 0 |
| June | 0 | 0 | 0 | 0 |
| July | 0 | 0 | 0 | 0 |
| August | 1 | 0 | 0 | 1 |
|September| 0 | 0 | 1 | 0 |
| October| 0 | 0 | 0 | 0 |
| November| 0 | 0 | 0 | 0 |
| December| 0 | 0 | 0 | 0 |
+---------+-----------+-----------+----------+---------+
I can't seem to get the right code, for the sql this is my current code.
SELECT Month(HireDate) AS Month, COUNT(*)
FROM Hires
GROUP BY Month(HireDate)
I know my coding is wrong, because it is incomplete.
Generate a list of numbers from 1 to 12 first to hold all months. Then do a LEFT JOIN on Hires to make sure all missing months are accounted for. Then use conditional aggregation for the totals:
SQL Fiddle
;WITH CteMonths AS(
SELECT * FROM(VALUES
(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)
)t(N)
)
SELECT
Month = DATENAME(MONTH, DATEADD(MONTH, N-1,0)),
Cleaned = SUM(CASE WHEN h.Status = 'Cleaned' THEN 1 ELSE 0 END),
Closed = SUM(CASE WHEN h.Status = 'Closed' THEN 1 ELSE 0 END),
Unclean = SUM(CASE WHEN h.Status = 'Unclean' THEN 1 ELSE 0 END),
Total = SUM(CASE WHEN h.Status IN('Cleaned', 'Unclean') THEN 1 ELSE 0 END)
FROM CteMonths m
LEFT JOIN Hires h
ON m.N = MONTH(h.HireDate)
--AND YEAR(h.HireDate) = #year --uncomment this line to filter for year.
GROUP BY m.N
ORDER BY m.N
If you want to include the YEAR:
SQL Fiddle
;WITH CteMonths AS(
SELECT * FROM(VALUES
(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)
)t(N)
),
CteYears(yr) AS(
SELECT DISTINCT YEAR(HireDate) FROM Hires
),
CteAllDates(dt) AS(
SELECT
DATEADD(MONTH, m.N - 1, DATEADD(YEAR, y.yr - 1900, 0))
FROM CteMonths m
CROSS JOIN CteYears y
)
SELECT
Year = YEAR(d.dt),
Month = DATENAME(MONTH, d.dt),
Cleaned = SUM(CASE WHEN h.Status = 'Cleaned' THEN 1 ELSE 0 END),
Closed = SUM(CASE WHEN h.Status = 'Closed' THEN 1 ELSE 0 END),
Unclean = SUM(CASE WHEN h.Status = 'Unclean' THEN 1 ELSE 0 END),
Total = SUM(CASE WHEN h.Status IN('Cleaned', 'Unclean') THEN 1 ELSE 0 END)
FROM CteAllDates d
LEFT JOIN Hires h
ON MONTH(d.dt) = MONTH(h.HireDate)
AND YEAR(d.dt) = YEAR(h.HireDate)
GROUP BY YEAR(d.dt), MONTH(d.dt), DATENAME(MONTH, d.dt)
ORDER BY YEAR(d.dt), MONTH(d.dt)
If you want to filter for year, say #year = 2015, you can replace the previous ctes with:
;WITH CteMonths AS(
SELECT * FROM(VALUES
(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)
)t(N)
),
CteAllDates(dt) AS(
SELECT
DATEADD(MONTH, m.N - 1, DATEADD(YEAR, #year - 1900, 0))
FROM CteMonths m
)...
I suggest to create TEMP table with values from 1 to 12 (numbers of months) and JOIN your table with TEMP table. To achieve values as columns names you can use PIVOT or CASE. You can do It in following:
INSERT INTO #Months VALUES
(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)
SELECT DATENAME(MONTH, DATEADD(MONTH, m.Id-1, 0)) AS [Month]
, SUM(CASE WHEN [Status] = 'Cleaned' THEN 1 ELSE 0 END) AS [Cleaned]
, SUM(CASE WHEN [Status] = 'Closed' THEN 1 ELSE 0 END ) AS [Closed]
, SUM(CASE WHEN [Status] = 'Unclean' THEN 1 ELSE 0 END) AS [Unclean]
, SUM(CASE WHEN [Status] IN ('Unclean', 'Cleaned') THEN 1 ELSE 0 END) AS [Total]
FROM #Test t
RIGHT JOIN #Months m ON m.Id = MONTH(t.HireDate)
GROUP BY m.Id
OUTPUT
+---------+-----------+-----------+----------+---------+
| Month | Cleaned | Unclean | Closed | Total |
+---------+-----------+-----------+----------+---------+
| January | 1 | 0 | 0 | 1 |
| February| 0 | 0 | 0 | 0 |
| March | 0 | 1 | 0 | 1 |
| April | 0 | 0 | 0 | 0 |
| May | 0 | 0 | 0 | 0 |
| June | 0 | 0 | 0 | 0 |
| July | 0 | 0 | 0 | 0 |
| August | 1 | 0 | 0 | 1 |
|September| 0 | 0 | 1 | 0 |
| October | 0 | 0 | 0 | 0 |
| November| 0 | 0 | 0 | 0 |
| December| 0 | 0 | 0 | 0 |
+---------+-----------+-----------+----------+---------+
DEMO
You can test It at: SQL FIDDLE

Count all rows from a column, and get 2 different count columns on the result even if there are not records to count

What I want to do is to count all rows from a column State, and get 2 different count columns on the result (one for each possible value 1 or 0).
The thing is that I need to show on the results 0 even if there are not records to count.
For example I have this table subcategory with this sample data:
SubcategoryID | Name |
---------------------------
201 | Name1 |
202 | Name2 |
203 | Name3 |
204 | Name4 |
And I have this table product with this sample data:
ProductID |Subcategory| State |
-------------------------------
101 | 201 | 1 |
102 | 201 | 1 |
103 | 201 | 1 |
104 | 202 | 0 |
105 | 202 | 0 |
106 | 203 | 1 |
107 | 203 | 0 |
108 | 203 | 0 |
State: 1 = Active, 0 = Inactive
So I want to get this:
|Subcategory| Active|Inactive|
------------------------------
| 201 | 3 | 0 |
| 202 | 0 | 2 |
| 203 | 1 | 2 |
| 204 | 0 | 0 |*
Please note that there are not products related to SubcategoryId = 204 on product table.
I'm using this query to get only the products that are related to one subcategory. Do you know what I have to add to get the line "|204|0|0|" ?
select p.subcategory,
sum(case when state = 1 then 1 else 0 end) as active,
sum(case when state = 0 then 1 else 0 end) as inactive
from product p
group by p.subcategory
Use Left Join to get all the rows from subcategory table and then do the Count.
SELECT S.subcategoryID,
Sum(CASE WHEN state = 1 THEN 1 ELSE 0 END) AS active,
Sum(CASE WHEN state = 0 THEN 1 ELSE 0 END) AS inactive
FROM subcategory s
LEFT JOIN product p
ON s.SubcategoryID = p.Subcategory
GROUP BY s.subcategoryID
Or use Count, case statement else part can be avoided
SELECT S.subcategoryID,
count(CASE WHEN state = 1 THEN 1 END) AS active,
count(CASE WHEN state = 0 THEN 1 END) AS inactive
FROM subcategory s
LEFT JOIN product p
ON s.SubcategoryID = p.Subcategory
GROUP BY s.subcategoryID
SQLFIDDLE DEMO
Just another way of doing it.
--Getting the counts for 'active' and 'inactive' states
SELECT S.subcategoryID,
count(CASE WHEN state = 1 THEN 1 END) AS active,
count(CASE WHEN state = 0 THEN 1 END) AS inactive
FROM subcategory s
JOIN product P ON s.SubcategoryID = p.Subcategory
GROUP BY s.subcategoryID
--Now adding subcategories which are only in subcategory table using `EXCEPT`.
UNION ALL
(
SELECT subcategoryid, 0, 0 from subcategory
EXCEPT
SELECT subcategory, 0, 0 from product
)

Count and Group By for products sold

I have a table Transaction & a table product as below .
Each Transaction has multiple products.
ProductId Name
1 ABC
2 DEF
3 GHI
Each transaction can have multiple products sold.
TransactionId ProductSoldInDept1 ProductSoldinDept2 ProductSoldinDept3
1 1 null null
2 1 2 null
3 3 1 null
4 2 3 1
I am planning to generate a report and I would like to get a result something like this :
This shows the number of products sold per each department grouped by Id
Expected Result :
ProductID Department1ProdCount Department2ProdCount Department3ProdCount
1 2 1 1
2 1 1 0
3 1 1 0
I could get till here , this is a query to get the counts for one specific product
which is productid : 1
I would like to know how I could use a group by here :
select Count(CASE WHEN ProductSoldInDept1 = 1 THEN 1 END) ,
Count(CASE WHEN ProductSoldInDept2 = 1 THEN 1 END) ,
Count(CASE WHEN ProductSoldInDept3 = 1 THEN 1 END)
from Table1
SELECT
p.ProductID,
Dept1ProdCount = COUNT(CASE WHEN t.ProductSoldInDept1 = p.ProductID THEN 1 END),
Dept2ProdCount = COUNT(CASE WHEN t.ProductSoldInDept2 = p.ProductID THEN 1 END),
Dept3ProdCount = COUNT(CASE WHEN t.ProductSoldInDept3 = p.ProductID THEN 1 END)
FROM dbo.Product AS p
LEFT OUTER JOIN dbo.[Transaction] AS t
ON p.ProductID IN
(t.ProductSoldInDept1, t.ProductSoldinDept2, t.ProductSoldinDept3)
GROUP BY p.ProductID;
Result
| PRODUCTID | DEPT1PRODCOUNT | DEPT2PRODCOUNT | DEPT3PRODCOUNT |
----------------------------------------------------------------
| 1 | 2 | 1 | 1 |
| 2 | 1 | 1 | 0 |
| 3 | 1 | 1 | 0 |
See a demo
Try this
select ProductID,
v.Department1ProdCount, v.Department2ProdCount, v.Department3ProdCount
from product a
cross apply
(
select Count(CASE WHEN ProductSoldInDept1 = a.ProductID THEN 1 END) Department1ProdCount,
Count(CASE WHEN ProductSoldInDept2 = a.ProductID THEN 1 END) Department2ProdCount,
Count(CASE WHEN ProductSoldInDept3 = a.ProductID THEN 1 END) Department3ProdCount
from Table1
) v
This:
TransactionId ProductSoldInDept1 ProductSoldinDept2 ProductSoldinDept3
1 1 null null
2 1 2 null
3 3 1 null
4 2 3 1
might be better structured as this:
transid prodsold deptid
1 1 1
2 1 1
2 2 2
3 3 1
3 1 2
4 2 1
4 3 2
4 1 3
I think that would make your queries easier to write.

cross apply in sql with sum

I have the following query:
insert into [MyDB].[dbo].[Reports_ActivityStat] (ActivityID,TaskID,LS_HowOthersAnswered,LS_ImproveMyChances)
(
SELECT
ActivityID=tasks.ActivityID,
TaskID=tasks.ID,
CAST(
CASE
WHEN CHARINDEX('stats',item.value('(UsedLifeSavers)[1]', 'NVARCHAR(MAX)'))>0
THEN 1
ELSE 0
END AS bit) as LS_HowOthersAnswered,
CAST(
CASE
WHEN CHARINDEX('FiftyFifty',item.value('(UsedLifeSavers)[1]', 'NVARCHAR(MAX)'))>0
THEN 1
ELSE 0
END AS bit) as LS_ImproveMyChances
FROM [MyDB].[dbo].[Tasks] as tasks CROSS APPLY [Progress].nodes ('//Progress/Steps/ProgressStep') Progress(item)
)
which acts on the following Tasks table:
ID | ActivityID | Progress
1 | 1 | [example below..]
2 | 1 | [example below..]
Where Progress is an xml of the sort:
<Progress xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Steps>
<ProgressStep>
<FinishedOn>2012-10-30T13:07:52.6374861+02:00</FinishedOn>
<Score>0</Score>
<StartedOn>2012-10-30T13:07:45.8234861+02:00</StartedOn>
<Status>Finished</Status>
<StepIndex>0</StepIndex>
<StepType>Summary</StepType>
<UsedLifeSavers xmlns:a="http://schemas.datacontract.org/2004/07/MindLab.Logic.Study" />
</ProgressStep>
<ProgressStep>
<FinishedOn i:nil="true" />
<PartNumber>1</PartNumber>
<Score>0</Score>
<StartedOn>2012-10-30T13:07:52.6374861+02:00</StartedOn>
<Status>NotFinished</Status>
<StepIndex>1</StepIndex>
<StepType>Information</StepType>
<SubmittedAnswersCount>0</SubmittedAnswersCount>
<UsedLifeSavers xmlns:a="http://schemas.datacontract.org/2004/07/MindLab.Logic.Study">
<a:LifeSavers>Stats</a:LifeSavers>
<a:LifeSavers>FiftyFifty</a:LifeSavers>
</UsedLifeSavers>
</ProgressStep>
</Steps>
</Progress>
(usually there are many more than 2 steps..)
My query yields (not actual data, just sample):
ID | ActivityID | TaskID | LS_HowOthersAnswered | LS_ImproveMyChances
1 | 1 | 1 | 0 | 0
2 | 1 | 1 | 1 | 0
3 | 1 | 1 | 0 | 0
This is almost what I need but not quite.
I need for every unique TaskID the SUM of all LS_HowOthersAnswered and LS_ImproveMyChances.
I was trying to group by, but couldn't make it because it's just too different from regular inner join in that matter..
Found out:
insert into [MyDB].[dbo].[Reports_ActivityStat] (ActivityID,TaskID,LS_HowOthersAnswered,LS_ImproveMyChances)
(
SELECT
ActivityID=tasks.ActivityID,
TaskID=tasks.ID,
SUM (CAST(
CASE
WHEN CHARINDEX('stats',item.value('(UsedLifeSavers)[1]', 'NVARCHAR(MAX)'))>0
THEN 1
ELSE 0
END AS int)
),
SUM (CAST(
CASE
WHEN CHARINDEX('FiftyFifty',item.value('(UsedLifeSavers)[1]', 'NVARCHAR(MAX)'))>0
THEN 1
ELSE 0
END AS int)
)
FROM [MyDB].[dbo].[Tasks] as tasks CROSS APPLY [Progress].nodes ('//Progress/Steps/ProgressStep') Progress(item)
group by tasks.ID
)

Resources