Count and Group By for products sold - sql-server

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.

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

QUERY IS NOT RETURNING PREDEFINED VALUE FOR WHEN THERE IS NOTHING

I want to query zero returns for null or nonexistent values, in the examples below, see what was the purpose and what was the output I received
SELECT
count(case when coalesce("CUSTOMER",'') = '' then 1 else 0 end) AS "OFFLINE GENERAL"
FROM
tbl_status
WHERE
"AVAILABLE" = '*OFFLINE'
AND "CUSTOMER" IN (
'CLIENT1',
'CLIENT2',
'CLIENT3',
'CLIENT4',
'CLIENT5'
)
GROUP BY
"CUSTOMER"
RETURNED
------
1 | 1
------
2 | 1
------
GOAL
1 | 1
-----
2 | 1
-----
3 | 0
-----
4 | 0
-----
5 | 0
...
Try a CTE with an outer join:
WITH cust AS (
SELECT *
FROM (VALUES
('CLIENT1'),
('CLIENT2'),
('CLIENT3'),
('CLIENT4'),
('CLIENT5')) AS cust(c)
)
SELECT cust.c,
count(*)
FILTER (WHERE coalesce("CUSTOMER",'') = '')
AS "OFFLINE GENERAL"
FROM tbl_status
RIGHT JOIN cust
ON cust.c = tbl_status."CUSTOMER"
WHERE
"AVAILABLE" = '*OFFLINE'
GROUP BY cust.c;

Group by DATETIME ranges on another table

I have a table that has 2 columns startdatetime and stopdatetime.
STARTDATETIME | STOPDATETIME | ID
2019-05-05T05:00:00Z 2019-05-05T06:00:00Z 1
2019-05-05T07:00:00Z 2019-05-05T08:00:00Z 1
2019-05-05T05:00:00Z 2019-05-05T06:00:00Z 2
2019-05-05T07:00:00Z 2019-05-05T08:00:00Z 2
2019-05-05T08:00:00Z 2019-05-05T10:00:00Z 2
A second table has a column with eventdatetime.
EVENTDATETIME | ID | Event
2019-05-05T05:30:00Z 1 1
2019-05-05T05:45:00Z 1 1
2019-05-05T07:30:00Z 1 1
2019-05-05T07:30:00Z 2 1
2019-05-05T07:40:00Z 2 1
2019-05-05T07:50:00Z 2 1
I am trying to do a select in the first table and join the second table where the eventdatetime falls in between the row and get the sum of events. What i don't know how to accomplish is to make a dynamic group by where it will take into consideration the other parameter.
Expected OUTPUT:
STARTDATETIME | STOPDATETIME | ID | SUM(EVENT)
2019-05-05T05:00:00Z 2019-05-05T06:00:00Z 1 2
2019-05-05T07:00:00Z 2019-05-05T08:00:00Z 1 1
2019-05-05T05:00:00Z 2019-05-05T06:00:00Z 2 0
2019-05-05T07:00:00Z 2019-05-05T08:00:00Z 2 3
2019-05-05T08:00:00Z 2019-05-05T10:00:00Z 2 0
SELECT
r.ID
,r.STARTDATETIME
,r.STOPDATETIME
,Sum(e.EVENT) as [SUM(Event)]
FROM dbo.GroupDatetimeRanges as r
Left Join dbo.GroupDatetimeEvents as e
on r.ID = e.ID and e.EVENTDATETIME between r.STARTDATETIME and r.STOPDATETIME
Group By
r.ID
,r.STARTDATETIME
,r.STOPDATETIME
Results
ID STARTDATETIME STOPDATETIME SUM(Event)
1 2019-05-05T05:00:00Z 2019-05-05T06:00:00Z 2
1 2019-05-05T07:00:00Z 2019-05-05T08:00:00Z 1
2 2019-05-05T05:00:00Z 2019-05-05T06:00:00Z NULL
2 2019-05-05T07:00:00Z 2019-05-05T08:00:00Z 3
2 2019-05-05T08:00:00Z 2019-05-05T10:00:00Z NULL
Try this code:
select
f.StartDateTime
, f.StopDateTime
, f.ID
, Qty = sum(
case
when s.EventDatetime between f.StartDateTime and f.StopDateTime
then s.Event
else 0
end)
from FirstTable f
left join SecondTable s on (s.ID = f.ID)
group by f.StartDateTime, f.StopDateTime, f.ID

Sql rows count with grouping

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
-------------------------------------------------------------

SQL Server query to retrieve all from View

I have a SQL View, similiar to the one below:
map_id | type_id | path
1 1 0
2 2 0
3 3 0
4 1 A>B
5 1 A>B>C
6 2 T>Z
7 2 T>Z>X
8 3 U
9 3 X>Y
10 1 D
And another table, tblRoles
role_group_id | type_id | map_id
1 1 1
2 1 4
I want to build a query that will include all map_id where role_group_id has the map_id = 1 and for the rest it should only get the corresponding map_id
So, the query result should look like:
role_group_id | type_id | map_id | path
1 1 4 A>B
1 1 5 A>B>C
1 1 10 D
1 1 1 0
2 1 4 A>B
Could someone point me to the correct way?
Thank you!
Haven't tested this yet, I hope this should work.
SELECT r.role_group_id
,r.type_id
,sv.map_id
,sv.path
FROM tblRoles r
LEFT JOIN sampleView sv ON r.type_id = sv.type_id
WHERE r.map_id = 1
UNION
SELECT r.role_group_id
,r.type_id
,r.map_id
,sv.path
FROM tblRoles r
INNER JOIN sampleView sv ON r.map_id = sv.map_id
WHERE r.map_id <> 1
You can use UNION
#rosuandreimihai: select all when map_id=1 and if map_id is different, select only the
corresponding row
DECLARE #CurrentMapId INT = 1
SELECT * FROM FirstTable
WHERE
TYPE_ID IN
(
SELECT r.type_id FROM tblRoles r
WHERE
map_id = #CurrentMapId
)
UNION
SELECT * FROM FirstTable
WHERE
map_id IN
(
SELECT r.map_id FROM tblRoles r
WHERE
#CurrentMapId = 1 OR
map_id = #CurrentMapId
)

Resources