SQL: Count() based on column value - sql

I have a table as follows:
CallID | CompanyID | OutcomeID
----------------------------------
1234 | 3344 | 36
1235 | 3344 | 36
1236 | 3344 | 36
1237 | 3344 | 37
1238 | 3344 | 39
1239 | 6677 | 37
1240 | 6677 | 37
I would like to create a SQL script that counts the number of Sales outcomes and the number of all the other attempts (anything <> 36), something like:
CompanyID | SalesCount | NonSalesCount
------------------------------------------
3344 | 3 | 1
6677 | 0 | 2
Is there a way to do a COUNT() that contains a condition like COUNT(CallID WHERE OutcomeID = 36)?

You can use a CASE expression with your aggregate to get a total based on the outcomeId value:
select companyId,
sum(case when outcomeid = 36 then 1 else 0 end) SalesCount,
sum(case when outcomeid <> 36 then 1 else 0 end) NonSalesCount
from yourtable
group by companyId;
See SQL Fiddle with Demo

Something like this:
SELECT companyId,
COUNT(CASE WHEN outcomeid = 36 THEN 1 END) SalesCount,
COUNT(CASE WHEN outcomeid <> 36 THEN 1 END) NonSalesCount
FROM
yourtable
GROUP BY
companyId
should work -- COUNT() counts only not null values.

Yes. Count doesn't count NULL values, so you can do this:
select
COUNT('x') as Everything,
COUNT(case when OutcomeID = 36 then 'x' else NULL end) as Sales,
COUNT(case when OutcomeID <> 36 then 'x' else NULL end) as Other
from
YourTable
Alternatively, you can use SUM, like bluefeet demonstrated.

SELECT
companyId, SalesCount, TotalCount-SalesCount AS NonSalesCount
FROM
(
select
companyId,
COUNT(case when outcomeid = 36 then 1 else NULL end) SalesCount,
COUNT(*) AS TotalCount
from yourtable
group by companyId
) X;
Using this mutually exclusive pattern with COUNT(*)
avoids a (very small) overhead of evaluating a second conditional COUNT
gives correct values if outcomeid can be NULL
Using #bluefeet's SQLFiddle with added NULLs

Knowing COUNT() and SUM() only count non-null values and the following rule:
true or null = true
false or null = null
For fiddling around, you can take Taryn's answer and circumvent CASE altogether in a super-dirty and error-prone way!
select companyId,
sum(outcomeid = 36 or null) SalesCount,
sum(outcomeid <> 36 or null) NonSalesCount
from yourtable
group by companyId;
Forget to add an or null and you'll be counting everything!

Related

SQL count where where column is greater than the other in group by?

Suppose I have a table money_table like:
team_id | money_spent | money_budget
--------------------------------------
123 | 3456.32 | 3466
964 | 236.32 | 200
123 | 9663 | 9400
964 | 3456.32 | 3466
The output table should be:
team_id | total_money_spent | total_money_budget | days_over_spent | days_under_spent
--------------------------------------
123 | 13119.32 | 12866 | 2 | 0
964 | 3692.64 | 3666 | 1 |. 1
The first 2 columns are easy with a group BY, I am wondering about the last 2 columns and how to tackle that. My initial query was:
SELECT
team_id,
SUM(money_spent) as total_money_spent,
SUM(money_budget) as total_money_budget
FROM money_table
GROUP BY team_id
ORDER BY team_id ASC
The works fine for the first 2 columns, but I am unable to think of how to get days_over_spent and days_under_spent.
Any suggestions?
Edit:
days_over_spent is the number of rows where money_spent > money_budget
days_under_spent is the number of rows where money_spent < money_budget
You could do the calculations for "over the budget" in a CTE
with tmp (t, s, b, o, u) as (
select
team,
spent,
budget,
case when spent > budget then 1 else 0 end,
case when spent < budget then 1 else 0 end
from budget
)
select
t as team,
sum(s) as total_spent,
sum(b) as total_budget,
sum(o) as days_over,
sum(u) as days_under
from tmp
group by t
Of course you can also just add the case into the query itself
select
team,
sum(spent),
sum(budget),
sum(case when spent > budget then 1 else 0 end),
sum(case when spent < budget then 1 else 0 end)
from budget
group by team
SELECT
team_id,
SUM(money_spent) as total_money_spent,
sum(money_budget) as total_money_budget,
sum(case when money_spent > money_budget then 1 else 0 end) as days_over_spent,
sum(case when money_spent < money_budget then 1 else 0 end) as days_under_spent
FROM money_table
GROUP BY team_id
ORDER BY team_id ASC

SQL using more two columns with case

I can't find a good explanation for my problem.
I have a table:
user | 70Y | hospital
-------+-------+----------
1 | 18 | 1
2 | 70 | 1
3 | 90 | 0
I need to find is a how many people have more than 70Y, and if it has how many of those people are in the hospital.
I'm using this to find is his age more than 70:
SUM(CASE WHEN 70y > 70 THEN 1 ELSE 0 END) AS 'old_person'
but how do I find is he is in the hospital?
What I'm expecting from a table is:
| old_person | old_person_in_hospital|
+------------+-----------------------+
| 18 | 1 |
And if I would want to and more columns let's say check for 40Y old what is the easiest way to do so?
What I expect from table :
| old_person | 40y_person |
+-------------+---------------------+
| 18 | 16 |
in hospital | 1 | 2 |
You need a case for each column:
select
SUM(Case when [70y] > 70 then 1 else 0 end) old_person,
SUM(Case when [70y] > 70 and hospital = 1 then 1 else 0 end) old_person_in_hospital
from tablename
use another case for number in hospital count
select SUM(Case when 70y > 70 then 1 else 0 end) as old_person,
sum (Case when 70y > 70 and hospital=1 then 1 else 0 end ) hospital
from tbale
How about moving the condition to the where clause?
select count(*) as old_person,
sum(hospital) as old_person_in_hospital
from tablename
where [70y] > 70;
If you want to add more age groups, then you could use conditional aggregation. However, I might suggest that you use aggregation instead and put the results in different rows. For instance:
select (age / 10) as decade,
count(*) as num_people,
sum(hospital) as num_in_hospital
from tablename
group by (age / 10);

SQL Server: select count of rows with not empty fields and total count of rows

Table has 4 int columns (Price0, Price1, Price2, Price3).
Example of table:
ID | Price0 | Price1 | Price2 | Price3 |
---+--------+--------+--------+--------+
1 | 10 | 20 | NULL | NULL |
2 | 70 | NULL | NULL | NULL |
3 | 30 | 40 | 50 | NULL |
How to query this table to get
total count of rows
and count of rows where count of filled Price columns >= N (for example N = 2)
Result must be:
Total | Filled
------+-------
3 | 2
This query show how many Price fileds is filled in each row
select
(select count(*) as filledFieldsCount
from (values (T.Price0), (T.Price1), (T.Price2), (T.Price3)) as v(col)
where v.col is not null
)
from Table1 T
Wouldn't with only 4 columns a simple nested case when be straightforward
select count(*),
sum(case when (
CASE WHEN Price1 is null THEN 0 ELSE 1 END +
CASE WHEN Price2 is null THEN 0 ELSE 1 END +
CASE WHEN Price3 is null THEN 0 ELSE 1 END +
CASE WHEN Price4 is null THEN 0 ELSE 1 END) >= 2 then 1 else 0 end)
FROM Table1
You can do this with conditional aggregation:
select count(*),
sum(case when tt.filledFieldsCount >= 2 then 1 else 0 end)
from Table1 T outer apply
(select count(*) as filledFieldsCount
from (values (T.Price0), (T.Price1), (T.Price2), (T.Price3)) as v(col)
where v.col is not null
) tt;
I moved the subquery to the from clause using apply. This is an example of a lateral join. In this case, it does the same thing as the subquery.

multiple record in a single row

I have post graduation degree records of students in my database. student may have only one post graduation degree, some students may have more than one post graduation degree.
rollno | pgdegree | score
--------------------------
0001 | 41 | 56
0002 | 42 | 78
0002 | 49 | 75
0003 | 48 | 77
Here roll no. 0002 is more than one time and roll no. 0001,0003 are only one time.
i want my desired output as :
rollno | pgdegree1 | score1 | pgdegree2 | score2
------------------------------------------------
0001 | 41 | 56 | |
0002 | 42 | 78 | 49 | 75
0003 | 48 | 77 | |
Note : in my database any student can have one or two post gradation only. Not more than two PG degree.
Here is another solution using ROW_NUMBER() and conditional aggregation to save some unnecessary SELECTs :
SELECT s.rollno,
MAX(CASE WHEN s.rnk = 1 THEN s.pgdegree END) AS pgdegree1,
MAX(CASE WHEN s.rnk = 1 THEN s.score END) AS score1,
MAX(CASE WHEN s.rnk = 2 THEN s.pgdegree END) AS pgdegree2,
MAX(CASE WHEN s.rnk = 2 THEN s.score END) AS score2
FROM
(
SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY t.rollno ORDER BY t.pgdegree, t.score) AS rnk
FROM YourTable t
) s
GROUP BY s.rollno
Do a self LEFT JOIN to add second pgdegree if available for a rollno. Do NOT EXISTS to only return rows with lowest pgdegree as t1.pgdegree.
select t1.rollno, t1.pgdegree, t1.score, t2.pgdegree, t2.score
from tablename t1
left join tablename t2
on t1.rollno = t2.rollno and t1.pgdegree < t2.pgdegree
where not exists (select * from tablename t3
where t1.rollno = t3.rollno
and t1.pgdegree > t3.pgdegree)
You can use a clever pivot query:
SELECT t.rollno,
SUM(CASE WHEN pgdegree = (SELECT MIN(pgdegree) FROM yourTable WHERE rollno = t.rollno)
THEN pgdegree ELSE 0 END) AS pgdegree1,
SUM(CASE WHEN pgdegree = (SELECT MIN(pgdegree) FROM yourTable WHERE rollno = t.rollno)
THEN score ELSE 0 END) AS score1,
SUM(CASE WHEN pgdegree = (SELECT MAX(pgdegree) FROM yourTable WHERE rollno = t.rollno)
THEN pgdegree ELSE 0 END) AS pgdegree2,
SUM(CASE WHEN pgdegree = (SELECT MAX(pgdegree) FROM yourTable WHERE rollno = t.rollno)
THEN score ELSE 0 END) AS score2
FROM yourTable t
GROUP BY t.rollno
Explanation:
The first two CASE statements have subqueries which will return pgdegree if that value happens to be the minimum value for that given rollno. This pgdegree and score will appear as the first two columns. Similarly, the last two CASE statements use the maximum value to generate the second two columns.
select
rollno,
(array_agg(pgdegree))[1] as pgdegree1,
(array_agg(score))[1] as score1,
(array_agg(pgdegree))[2] as pgdegree2,
(array_agg(score))[2] as score2
from
your_table
group by
rollno;

SQL check if group contains NULL

Is there any function to check if a column in a group contains a NULL, alternatively how would I solve this? Example below of data structure.
id | value
----------
1 | NULL
1 | 56
2 | 98
2 | 14
Result:
id | value
----------
1 | 1
2 | 0
try
select id,
count(*) - count(value) as null_value_count
from your_table
group by id
SQLFiddle demo
Another possibility which doesn't use the fact that count(value) ignores NULL values:
select id,
sum(case when value is null then 1 else 0 end) as null_count
from your_table
group by id;