Mutually exclusive counts in SQL - sql

The table that I need to query looks like this
ID - Account - Product
1 - 002 - Bike
2 - 003 - Bike
4 - 003 - Motor
5 - 004 - Car
I need to be able to retrieve the number of accounts that purchased the each of the products and combinations of the products, like this
Bike | Car | Motor | Bike&Car | Bike&Motor | Car&Motor | Bike&Car&Motor
Note that an account that purchased a combination of products will be counted as 1.
Please help me in retrieving this data.

You can do this using two levels of aggregation. One method puts the values on different rows:
select has_bike, has_motor, has_car, count(*)
from (select account,
max(case when product = 'bike' then 1 else 0 end) as has_bike,
max(case when product = 'motor' then 1 else 0 end) as has_motor,
max(case when product = 'car' then 1 else 0 end) as has_car
from t
group by account
) t
group by has_bike, has_motor, has_car;
Or in columns:
select sum(has_bike * (1 - has_motor) * (1 - has_car)) as has_only_bike,
sum((1 - has_bike) * has_motor * (1 - has_car)) as has_only_motor,
sum((1 - has_bike) * (1 - has_motor) * has_car) as has_only_car,
. . .
from (select account,
max(case when product = 'bike' then 1 else 0 end) as has_bike,
max(case when product = 'motor' then 1 else 0 end) as has_motor,
max(case when product = 'car' then 1 else 0 end) as has_car
from t
group by account
) t;

If you only have a limited set of Products, then you can use this:
-- Create sample data
CREATE TABLE #tbl(
ID INT,
Account VARCHAR(10),
Product VARCHAR(10)
);
INSERT INTO #tbl VALUES
(1, '002', 'Bike'),
(2, '003', 'Bike'),
(3, '003', 'Motor'),
(4, '004', 'Car');
WITH Cte AS(
SELECT t1.Account, a.Products
FROM #tbl t1
CROSS APPLY (
SELECT STUFF((
SELECT '&' + t2.Product
FROM #tbl t2
WHERE t2.Account = t1.Account
ORDER BY t2.Product
FOR XML PATH(''), type).value('.[1]','nvarchar(max)'),
1, 1, '') AS Products
) a
GROUP BY t1.Account, a.Products
)
SELECT
Bike = SUM(CASE WHEN Products = 'Bike' THEN 1 ELSE 0 END),
Car = SUM(CASE WHEN Products = 'Car' THEN 1 ELSE 0 END),
Motor = SUM(CASE WHEN Products = 'Motor' THEN 1 ELSE 0 END),
[Bike&Car] = SUM(CASE WHEN Products = 'Bike&Car' THEN 1 ELSE 0 END),
[Bike&Motor] = SUM(CASE WHEN Products = 'Bike&Motor' THEN 1 ELSE 0 END),
[Car&Motor] = SUM(CASE WHEN Products = 'Car&Motor' THEN 1 ELSE 0 END),
[Bike&Car&Motor] = SUM(CASE WHEN Products = 'Bike&Car&Motor' THEN 1 ELSE 0 END)
FROM Cte;
DROP TABLE #tbl; -- Remove sample data
The idea is to generate 1 row for each Account, together with a comma-delimited Products. If you execute the query inside the CTE, you will get:
Account Products
---------- ---------------
002 Bike
003 Bike&Motor
004 Car
With that, you can do a conditional aggregation. The above uses a static solution, if you do not know the number of Products, you may need to come up with a dynamic approach.
ONLINE DEMO

Related

Adding a dummy identifier to data that varies by position and value

I am working on a project in SQL Server with diagnosis codes and a patient can have up to 4 codes but not necessarily more than 1 and a patient cannot repeat a code more than once. However, codes can occur in any order. My goal is to be able to count how many times a Diagnosis code appears in total, as well as how often it appears in a set position.
My data currently resembles the following:
PtKey
Order #
Order Date
Diagnosis1
Diagnosis2
Diagnosis3
Diagnosis 4
345
1527
7/12/20
J44.9
R26.2
NULL
NULL
367
1679
7/12/20
R26.2
H27.2
G47.34
NULL
325
1700
7/12/20
G47.34
NULL
NULL
NULL
327
1710
7/12/20
I26.2
J44.9
G47.34
NULL
I would think the best approach would be to create a dummy column here that would match up the diagnosis by position. For example, Diagnosis 1 with A, and Diagnosis 2 with B, etc.
My current plan is to rollup the diagnosis using an unpivot:
UNPIVOT ( Diag for ColumnALL IN (Diagnosis1, Diagnosis2, Diagnosis3, Diagnosis4)) as unpvt
However, this still doesn’t provide a way to count the diagnoses by position on a sales order.
I want it to look like this:
Diagnosis
Total Count
Diag1 Count
Diag2 Count
Diag3 Count
Diag4 Count
J44.9
2
1
1
0
0
R26.2
1
1
0
0
0
H27.2
1
0
1
0
0
I26.2
1
1
0
0
0
G47.34
3
1
0
2
0
You can unpivot using apply and aggregate:
select v.diagnosis, count(*) as cnt,
sum(case when pos = 1 then 1 else 0 end) as pos_1,
sum(case when pos = 2 then 1 else 0 end) as pos_2,
sum(case when pos = 3 then 1 else 0 end) as pos_3,
sum(case when pos = 4 then 1 else 0 end) as pos_4
from data d cross apply
(values (diagnosis1, 1),
(diagnosis2, 2),
(diagnosis3, 3),
(diagnosis4, 4)
) v(diagnosis, pos)
where diagnosis is not null;
Another way is to use UNPIVOT to transform the columns into groupable entities:
SELECT Diagnosis, [Total Count] = COUNT(*),
[Diag1 Count] = SUM(CASE WHEN DiagGroup = N'Diagnosis1' THEN 1 ELSE 0 END),
[Diag2 Count] = SUM(CASE WHEN DiagGroup = N'Diagnosis2' THEN 1 ELSE 0 END),
[Diag3 Count] = SUM(CASE WHEN DiagGroup = N'Diagnosis3' THEN 1 ELSE 0 END),
[Diag4 Count] = SUM(CASE WHEN DiagGroup = N'Diagnosis4' THEN 1 ELSE 0 END)
FROM
(
SELECT * FROM #x UNPIVOT (Diagnosis FOR DiagGroup IN
([Diagnosis1],[Diagnosis2],[Diagnosis3],[Diagnosis4])) up
) AS x GROUP BY Diagnosis;
Example db<>fiddle
You can also manually unpivot via UNION before doing the conditional aggregation:
SELECT Diagnosis, COUNT(*) As Total Count
, SUM(CASE WHEN Position = 1 THEN 1 ELSE 0 END) As [Diag1 Count]
, SUM(CASE WHEN Position = 2 THEN 1 ELSE 0 END) As [Diag2 Count]
, SUM(CASE WHEN Position = 3 THEN 1 ELSE 0 END) As [Diag3 Count]
, SUM(CASE WHEN Position = 4 THEN 1 ELSE 0 END) As [Diag4 Count]
FROM
(
SELECT PtKey, Diagnosis1 As Diagnosis, 1 As Position
FROM [MyTable]
UNION ALL
SELECT PtKey, Diagnosis2 As Diagnosis, 2 As Position
FROM [MyTable]
WHERE Diagnosis2 IS NOT NULL
UNION ALL
SELECT PtKey, Diagnosis3 As Diagnosis, 3 As Position
FROM [MyTable]
WHERE Diagnosis3 IS NOT NULL
UNION ALL
SELECT PtKey, Diagnosis4 As Diagnosis, 4 As Position
FROM [MyTable]
WHERE Diagnosis4 IS NOT NULL
) d
GROUP BY Diagnosis
Borrowing Aaron's fiddle, to avoid needing to rebuild the schema from scratch, and we get this:
https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=d1f7f525e175f0f066dd1749c49cc46d

Union with Group By

Had a look at other questions, tried different things but still returning more than one row.
Problem with Union on 2 tables, with group by clause. There should only be one row returned, grouped by the serviceID.
SELECT
serviceID,
serviceName,
FullCount,
WaitingCount,
InProgressCount
from (
select
a.serviceID,
serviceName,
count(applicantID) FullCount,
ISNULL(SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END),0) AS WaitingCount,
ISNULL(SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END),0) AS InProgressCount
from Products s
left join Assigns a on a.serviceID = s.productID
WHERE s.clientID = #ClientID
group by serviceID, serviceName
UNION
select
s.serviceID,
p.serviceName,
count(s.ApplicantID) FullCount,
ISNULL(SUM(CASE WHEN s.status = 0 THEN 1 ELSE 0 END),0) AS WaitingCount,
ISNULL(SUM(CASE WHEN s.status = 1 THEN 1 ELSE 0 END),0) AS InProgressCount
from Legacies s
Left Join Products p on s.serviceID = p.productID
WHERE s.client = #CompanyName
group by serviceID, serviceName
) t
GROUP BY serviceID, serviceName
I'm always getting 2 rows returned, one from each of the tables. I need to group them both together so it only returns 1 row, based on the servicedID.
The data I'm trying to return is from the following tables..
Products Table
productID serviceName
-------------------------
1 Gold Service
2 Silver Service
3 Bronze Service
Assigns Table
ApplicantID serviceID status
-------------------------------------
1 1 0
2 1 0
3 1 1
4 2 0
5 1 1
Legacies Table
ApplicantID serviceID status
-------------------------------------
1 1 0
2 1 0
3 1 0
4 2 0
5 1 1
The result I'm trying to get is one row per serviceID, to show how many applicants are on this service in both the Legacies and Assigns table, something like:-
serviceID serviceName FullCount WaitingCount InProgressCount
----------------------------------------------------------------
1 Gold Service 8 5 3
2 Silver Service 2 2 0
3 Bronze Service 0 0 0
FullCount is a total number of applicants on each service, WaitingCount is the number of applicants on the service with a status of '0' and InProgressCount is the number on this service with a status of '1'
Based on additional information, I think you can just union all the Legacies and Assigns tables.
still untested
select serviceID, servicename, count(*) fullcount
,sum(case when status = 0 THEN 1 ELSE 0 END) AS WaitingCount
,SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS InProgressCount
from (
select ApplicantID, serviceID, status
from Assigns
WHERE clientID = #ClientID
union all
select ApplicantID, serviceID, status
from Legacies
WHERE clientID = #ClientID
) combined
left join Products P on P.productID = combined.serviceID
group by serviceID, servicename
below is before edit
It's hard to tell because you do not post enough information (no sample data, no table structures, no expected output). But I think you can probably combine it all into 1 query:
untested which should be obvious with the lack of information.
SELECT isnull(a.serviceID, L.serviceID) serviceID, p.serviceName
,count(*) FullCount, SUM(CASE WHEN isnull(a.status, L.status) = 0 THEN 1 ELSE 0 END) WaitingCount
,sum(CASE WHEN isnull(a.status, L.status) = 1 THEN 1 ELSE 0 END) InProgressCount
from Legacies L
full outer join Assigns a on a.serviceID = L.serviceID
right outer join Products P on P.productID = isnull(a.serviceID, L.serviceID)
where (P.clientID = #ClientID
or L.client = #CompanyName
)
group by isnull(a.serviceID, L.serviceID), p.serviceName

How to sum up unique values using case statements having certain conditions

I have a table that may have the same item but with different sizes, I would like to count those items with more than one size (e.g. marvel shirt with S, M sizes will count as "1"), but still be able to count how many S and M. I have 2 outcomes I would like to get. Please see below for more detail.
TABLE B
ITEM_NO ITEM
=========================
3130C MARVEL_SHIRT
1845C SPONGEBOB_BOXERS
A900C CK_COAT
A988C RIDER_JEANS
TABLE C
ITEM_NO SIZE
===============
3130C S
3130C M
1845C M
A900C L
A988C M -
I tried just counting it but it is incorrect since it counts how many distinct sizes are there
select (case substr(item_no, 5, 1)
when 'C' then 'clothes'
when 'T' then 'toys'
else 'misc'
end) inv,
count(item_no) total
,sum (case when C.size = 'S' then 1 else 0 end) AS small
,sum (case when C.size = 'M' then 1 else 0 end) AS med
,sum (case when C.size = 'L' then 1 else 0 end) AS large
,count (distinct C.size) AS multiple_sizes
from B left outer join C on B.item_no = C.item_no
group by substr(item_no, 5, 1);
Actual outcome (incorrect):
INV TOTAL Small Med Large Multiple_Sizes
==========================================================
CLOTHES 4 1 3 1 3
Desired/expected outcome:
INV TOTAL Small Med Large Multiple_Sizes
==========================================================
CLOTHES 4 1 3 1 1
Below is another possible desired outcome given this scenario: What if those with multiple sizes shouldn't be counted separately (i.e. Marvel shirt has multiple sizes, thus it won't count the S or M since it's already counted under Multiple_Sizes)?
INV TOTAL Small Med Large Multiple_Sizes
==========================================================
CLOTHES 4 0 2 1 1
You probably need to group twice (1) by item number (2) by item category:
SELECT SUBSTR(item_no, 5, 1) AS category
, COUNT(*) AS count_products
, SUM(count_small) AS small
, SUM(count_med) AS med
, SUM(count_large) AS large
, SUM(CASE WHEN count_small + count_med + count_large > 1 THEN 1 END) AS has_multiple
FROM (
SELECT b.ITEM_NO
, COUNT(CASE WHEN c.SIZE = 'S' THEN 1 END) AS count_small
, COUNT(CASE WHEN c.SIZE = 'M' THEN 1 END) AS count_med
, COUNT(CASE WHEN c.SIZE = 'L' THEN 1 END) AS count_large
FROM b
LEFT JOIN c ON b.item_no = c.item_no
GROUP BY b.ITEM_NO
) x
GROUP BY SUBSTR(item_no, 5, 1)
| category | count_products | small | med | large | has_multiple |
| C | 4 | 1 | 3 | 1 | 1 |
And the variation:
SELECT SUBSTR(item_no, 5, 1) AS category
, COUNT(*) AS count_products
, SUM(CASE WHEN count_small + count_med + count_large = 1 THEN count_small END) AS small
, SUM(CASE WHEN count_small + count_med + count_large = 1 THEN count_med END) AS med
, SUM(CASE WHEN count_small + count_med + count_large = 1 THEN count_large END) AS large
, SUM(CASE WHEN count_small + count_med + count_large > 1 THEN 1 END) AS has_multiple
FROM (
SELECT b.ITEM_NO
, COUNT(CASE WHEN c.SIZE = 'S' THEN 1 END) AS count_small
, COUNT(CASE WHEN c.SIZE = 'M' THEN 1 END) AS count_med
, COUNT(CASE WHEN c.SIZE = 'L' THEN 1 END) AS count_large
FROM b
LEFT JOIN c ON b.item_no = c.item_no
GROUP BY b.ITEM_NO
) x
GROUP BY SUBSTR(item_no, 5, 1)
| category | count_products | small | med | large | has_multiple |
| C | 4 | 0 | 2 | 1 | 1 |
--creando tabla
create table #temp (itemId int, size nvarchar(1))
--insertando valores
insert into #temp values (1,'S')
insert into #temp values (1,'M')
insert into #temp values (2,'M')
insert into #temp values (3,'L')
insert into #temp values (4,'M')
-- table of Different Item Codes
select
itemId
into #masDeUnItem
from
(select itemId,size from #temp group by itemId,size) t1
group by itemId
having count(1) > 1
-- Variable of Counting different Items
declare #itemsDistintos int
-- Providing Value to Variable
select #itemsDistintos = count(1) from
(
select * from #masDeUnItem
) t1
--Outcome 1
select count(distinct(itemId)) TOTAL
,
sum(case when size = 'S' then 1 else 0 end) SMALL
, sum(case when size = 'M' then 1 else 0 end) MEDIUM
, sum(case when size = 'L' then 1 else 0 end) LARGE
, #itemsDistintos as Multiple_Sizes
from #temp
--Outcome 2
select count(distinct(a.itemId)) TOTAL
,
sum(case when size = 'S' and b.itemId is null then 1 else 0 end) SMALL
, sum(case when size = 'M' and b.itemId is null then 1 else 0 end) MEDIUM
, sum(case when size = 'L' and b.itemId is null then 1 else 0 end) LARGE
, #itemsDistintos as Multiple_Sizes
from #temp a
left join #masDeUnItem b
on a.itemId = b.itemId

SQL: Add a column and classify into categories

I have a table which has transaction_id as the primary key and also contains customer_id which is a foreign key.
Now there is a column type which has two values: 'Card' and 'cash'.
Now some of the customers have used both the methods for payment. I want to add a new column and classify the customers as "Only card" "Only cash" and "Both".
Transaction id Customer id Type
1 100 Card
2 101 Cash
3 102 Card
4 103 Cash
5 101 Card
So in this table I want a new column 'Type of payment' which classifies customer 101 as Both since he has used both the methods of payment.
You can use window functions:
select t.*,
(case when min(type) over (partition by customerid) = max(type) over (partition by customerid)
then 'Only ' + min(type) over (partition by customerid)
else 'both'
end)
from transactions t;
You can do better and remove a bit of redondancy (the values cash only and card only will be repeated in the table, in this case we prefer repeating an ID). So you can create a Table for example payement_methods that will have 2 columns for example id and method, you will populate it with the three options you just mentioned (cash only, card only, both), and you'll have in your transaction table a column payment_method_id for example (instead of the type column you were using).
example
|id | method |
|1 | Cash only |
|2 | Card Only |
|3 | Both |
transaction table
|id | other columns ...|payement method |
|1 | other columns ...|1 |
|2 | other columns ...|3 |
//...
sorry for my english, good luck.
Rather than adding a column to the table, if what you want to do is analyze the payment methods, then doing something like this might be better:
SELECT DISTINCT Table1.[Customer ID], T1.*
FROM Table1
CROSS APPLY (SELECT SUM(CASE WHEN [Type] = 'Cash' THEN 1 ELSE 0 END) AS Cash,
SUM(CASE WHEN [Type] = 'Card' THEN 1 ELSE 0 END) AS Card
FROM Table1 T WHERE T.[Customer ID] = Table1.[Customer ID]) T1
Gives you results like this:
CUSTOMER ID CASH CARD
100 0 1
101 1 1
102 0 1
103 1 0
Create table tran1(Transactionid int , Customerid int , Type varchar(100))
insert into tran1(Transactionid , Customerid , Type ) values
(1 , 100 , 'Card ' ),
(2 , 101 , 'Cash' ),
(3 , 102 , 'Card ' ),
(4 , 103 , 'Cash ' ),
(5 , 101 , 'Card ' )
alter table tran1 add NewType varchar(100)
Update tran1 set NewType ='Only card' where Customerid IN (
select d.custid from (
select Customerid as custid,SUM(case when [Type]='Card' then 1 else 0 end) card
,SUM(case when [Type]='Cash' then 1 else 0 end) cash
from tran1
group by Customerid)d
where d.card=1
)
Update tran1 set NewType ='Only Cash' where Customerid IN (
select d.custid from (
select Customerid as custid,SUM(case when [Type]='Card' then 1 else 0 end) card
,SUM(case when [Type]='Cash' then 1 else 0 end) cash
from tran1
group by Customerid)d
where d.cash=1
)
Update tran1 set NewType ='Both' where Customerid IN (
select d.custid from (
select Customerid as custid,SUM(case when [Type]='Card' then 1 else 0 end) card
,SUM(case when [Type]='Cash' then 1 else 0 end) cash
from tran1
group by Customerid)d
where d.card=1 and cash=1
)

Count in sql with group by

Table name: mytable
Id username pizza-id pizza-size Quantity order-time
--------------------------------------------------------------
1 xyz 2 9 2 09:00 10/08/2014
2 abc 1 11 3 17:45 13/07/2014
This is mytable which has 6 columns. Id is int, username is varchar, order-time is datetime and rest are of integer datatype.
How to count the number of orders with the following pizza quantities: 1, 2, 3, 4, 5, 6,7 and above 7?
Using a T-SQL query.
It would be very helpful If any one could help to me find the solution.
Try this !
SELECT COUNT(ID),CASE WHEN QUANTITY<7 THEN QUANTITY ELSE 'ABOVE7' END AS QUANTITIES
FROM mytable
GROUP BY CASE WHEN QUANTITY<7 THEN QUANTITY ELSE 'ABOVE7' END
Try
Select CASE WHEN Quantity > 7 THEN 'OVER7' ELSE Cast(quantity as varchar) END Quantity,
COUNT(ID) NoofOrders
from mytable
GROUP BY CASE WHEN Quantity > 7 THEN 'OVER7' ELSE Cast(quantity as varchar) END
or
Select
SUM(Case when Quantity = 1 then 1 else 0 end) Orders1,
SUM(Case when Quantity = 2 then 1 else 0 end) Orders2,
SUM(Case when Quantity = 3 then 1 else 0 end) Orders3,
SUM(Case when Quantity = 4 then 1 else 0 end) Orders4,
SUM(Case when Quantity = 5 then 1 else 0 end) Orders5,
SUM(Case when Quantity = 6 then 1 else 0 end) Orders6,
SUM(Case when Quantity = 7 then 1 else 0 end) Orders7,
SUM(Case when Quantity > 7 then 1 else 0 end) OrdersAbove7
from mytable
If the requirement is like count the number of orders with different pizza quantities and represent count of orders as : 1, 2, 3, 4, 5, 6,7 and consider all above order counts in new category : 'above 7' then you can use window function as:
select case when totalorders < = 7 then cast(totalorders as varchar(10))
else 'Above 7' end as totalorders
, Quantity
from
(
select distinct count(*) over (partition by Quantity order by Quantity asc)
as totalorders,
Quantity
from mytable
) T
order by Quantity
DEMO
Edit: if the requirement is like count the number of orders with pizza quantities: 1, 2, 3, 4, 5, 6,7 and consider all other pizza quantities in new category : 'above 7' then you can write as:
select distinct
count(*) over (
partition by Quantity order by Quantity asc
) as totalorders,
Quantity
from (
select
case when Quantity < = 7 then cast(Quantity as varchar(20))
else 'Above 7' end as Quantity, id
from mytable ) T
order by Quantity
DEMO