SQL Group By Multiple values on different columns - sql

I have a table like this:
Section TestID Score
Section1 1 50
Section2 1 32
Section3 1 22
Section1 2 22
Section2 2 17
Section3 2 42
I'm looking to produce a table with each section and it's scores against all testIDs (up to a maximum of 3 scores). Is it possible to use a group by condition to produce a table similar to this:
Section Score1 Score2
Section1 50 22
Section2 32 17
Section3 22 42

With ROW_NUMBER() window function and conditional aggregation:
select t.section,
max(case when t.rn = 1 then t.score end) score1,
max(case when t.rn = 2 then t.score end) score2
from (
select *, row_number() over (partition by section order by testid) rn
from tablename
) t
group by t.section
See the demo.
Results:
> section | score1 | score2
> :------- | -----: | -----:
> Section1 | 50 | 22
> Section2 | 32 | 17
> Section3 | 22 | 42

You can use conditional aggregation:
select section,
max(case when testid = 1 then score end) as score_1,
max(case when testid = 2 then score end) as score_2
from t
group by section;

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

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;

How to create sql selection based on condition?

I have the following database which shows characteristics of attributes as follows:
attributeId | attributeCode | groupCode
------------+---------------+-----------
1 | 10 | 50
1 | 10 | 50
1 | 12 | 50
My desired result from a select would be:
attributeId | groupcount | code10 | code12
------------+------------+--------+--------
1 | 1 | 2 | 1
Which means: attributeId = 1 has only one groupCode (50), where attributeCode=10 occurs 2 times and attributeCode=12 occurs 1 time.
Of course the following is not valid, but you get the idea of what I'm trying to achieve:
select attributeId,
count(distinct(groupCode)) as groupcount,
attributeCode = 10 as code10,
attributeCode = 12 as code12
from table
group by attributeId;
Try this:
SELECT attributeId, COUNT(DISTINCT groupCode) AS groupcount,
COUNT(CASE WHEN attributeCode = 10 THEN 1 END) AS code10,
COUNT(CASE WHEN attributeCode = 12 THEN 1 END) AS code12
FROM mytable
GROUP BY attributeId
Demo here

SQL: Count() based on column value

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!

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;