SQL - Group by an agregate function - sql

I have a question whether if it's possible to make a group by an aggregate function.
Scenario:
I have a table which has biomass(kg) and number of individuals for everyday and a description, therefore I can calculate the total av. weight and total number of individuals within two dates as:
select
description,
sum(biomass)/sum(number_individuals) as av.weight,
sum(number_individuals) as individuals
from
Table
group by description
Which works okay, now, the thing is that I want to group those individuals separating them by weight ranges, in order to get something like:
description range(kg) number av.weigh(g)
Foo 2-3 2400 2584.48
I have tried something like
SELECT
description,
case when sum(biomass)/sum(number_individuals) >= 2000.0
and sum(biomass)*1000/sum(number_individuals) < 3000 then '2-3'
else 'nothing'
end as desc_range
FROM Table
Group by
description,
sum(biomass)/sum(number_individuals)
But it doesn't seem to work, neither using the alias desc_range ofc.
I am using Informix 9.40 TC3
Any help will be appreciated.
Best regards

If you want to aggregate on an aggregation, you usually need a subquery. However, you mention individuals, so perhaps this is what you want:
select description,
(case when biomass between 2 and 3 then '2-3'
else 'nothing'
end) as biomass
sum(biomass)/sum(number_individuals) as av.weight, sum(number_individuals) as individuals
from Table
group by description,
(case when biomass between 2 and 3 then '2-3'
else 'nothing'
end);

Related

Query to find sorted list of same columns with different conditions

I am new to SQL, I have a question which I think you guys could help me out.
with properties as
(
select count(*) as records
From propdata P
where root_tstamp >= '2020-01-01' and and
(case when min_rooms is null and max_rooms is null then 0 else 1 end) = 1 and
(1 between min_rooms and max_rooms)
)
select apartments, count(*)/properties.records as all
From propdata P inner join properties
on properties.records is not null
where root_tstamp >= '2020-01-01' and
(1 between rooms and rooms) and
(case when min_rooms is null and max_rooms is null then 0 else 1 end) = 1
group by apartments, all
order by all desc;
When I run this query I get the result as Apartments and all by sorting in descending order for the condition 1 rooms which is mentioned in where condition ((1 between min_rooms and max_rooms)).
RESULT:
APARTMENTS ALL
willington 9.893
greens apartment. 8.92
garden glow apartment. 6.82
What I need is column wise descending sorted list of apartments for each condition in terms of rooms 1, 2 ,3 in the where condition.
EXPECTED RESULT
APARTMENTS - 1ROOM ALL. APARTMENT - 2 ROOM ALL
willington 9.893 FLOWARD APARTMENTS 8.1
greens apartment. 8.92. KNIGHT ANGELS 5.8
garden glow apartment. 6.82. HOVEY APARTMENTS. 2.3
Can anyone please help me out ! Thank you
I have mentioned the result which i get now and the result i wanted. is there any way to display the same column in terms of each condition sorted in descending to show seperate. Appreciate your help thank you.
SQL is not designed to work in that way. It looks as through you need this for presentation to a human. If so, do not try to do this in your data layer, do it in your application / presentation layer.
For a natural SQL structure, anything dynamically varying, such as the apartment's categorisation by number of rooms, should be in a column's values, not a new column.
SELECT
rooms,
apartments,
COUNT(*) * 100.0 / SUM(COUNT(*)) OVER () AS all
FROM
propdata
WHERE
root_tstamp >= '2020-01-01'
AND (1 between rooms and rooms)
AND (case when min_rooms is null and max_rooms is null then 0 else 1 end) = 1
GROUP BY
rooms,
apartments
ORDER BY
rooms,
all DESC;
Notes:
SUM(COUNT(*)) OVER () totals up the COUNT(*) values from the whole result set, avoiding the need for your common table expression.
I'm not sure exactly what the WHERE clause is meant to be doing, so I've just copied it from your question. If you explain it a bit more, with examples, that can most likely be improved / simplified too.

Generate columns from values returned by SELECT

I've got a query that returns data like so:
student
course
grade
a-student
ENG-W05
100
a-student
MAT-W05
85
a-student
ENG-W06
100
b-student
MAT-W05
90
b-student
SCI-W05
75
The data is grouped by student and course. Ideally, I'd like to have the above data transformed into the below:
student
ENG-W05
MAT-W05
ENG-W06
SCI-W05
a-student
100
85
100
NULL
b-student
NULL
90
NULL
75
So, after the transformation, each student only has one record, with all of their grades (and any missing courses graded as null).
Does anyone have any ideas? Obviously, this is fairly simple to do if I take the data out and transform it in a language (like Python), but I'd love to get the data in the desired format with an SQL query.
Also, would it be possible to have the columns order alphabetically (ascending)? So, the final output would be:
student
ENG-W05
ENG-W06
MAT-W05
SCI-W05
a-student
100
100
85
NULL
b-student
NULL
NULL
90
75
EDIT: To clarify, the values in course aren't known. The ones I provided are just examples. So ideally, if more course values found there way into that first query result (the first table), they would still be mapped to columns in the final result (without needing to change the query). In reality, I actually have >1k distinct values for the course column, and so I can't manually write out each one.
demos:db<>fiddle
You can use conditional aggregation for that:
SELECT
student,
SUM(grade) FILTER (WHERE course = 'ENG-W05') as eng_w05,
SUM(grade) FILTER (WHERE course = 'MAT-W05') as mat_w05,
SUM(grade) FILTER (WHERE course = 'ENG-W06') as eng_w06,
SUM(grade) FILTER (WHERE course = 'SCI-W05') as sci_w05
FROM mytable
GROUP BY student
The FILTER clause allows to aggregate only some specific records. So this one aggregates all records for a specific course.
Finding the correct aggregate function could be difficult. Here SUM() does the job, as there's only one value per group. MAX() or MIN() would do it as well. It depends on your real requirement. If there's really only one value per group, it doesn't matter, you just need to do any aggregation.
Instead of FILTER clause, which is Postgres specific, you could use the more SQL standard fitting CASE clause:
SELECT
student,
SUM(
CASE
WHEN course = 'ENG-W05' THEN grade
END
) AS eng_w05,
...
You can use the conditional aggregation as follows:
select student,
max(case when course = 'ENG-W05' then grade end) as "ENG-W05",
max(case when course = 'MAT-W05' then grade end) as "MAT-W05",
max(case when course = 'ENG-W06' then grade end) as "ENG-W06",
max(case when course = 'SCI-W05' then grade end) as "SCI-W05"
from (your_query) t
group by student

sql sum up connection time in call detail records table

lets say Ive got Customers Detail Records table which has columns:
UserAId
UserBId
Duration
Impulses
For eample:
UserAId UserBId Duration Impulses
1 2 30 5
1 2 20 3
2 1 10 2
2 3 5 1
Ok, now I would like to write a query which would aggregate total Duration, Impulses and count of calls betweend users without direction so that the result would look like:
UserAId UserBId TotalDuration TotalImpulses TotalCallsCount
1 2 60 10 3
2 3 5 1 1
Is it possible ? If so then how to do this > thanks for help
Of course, if you execute a query like this:
SELECT
UserAId,
UserBId,
SUM(Duration) AS TotalDuration,
SUM(Impulses) AS TotalImpulses,
COUNT(*) AS TotalCallsCount
FROM CustomerDetail
GROUP BY UserAId, UserBId
... you will not get what you want. That is because this query does not aggregate and combine the rows that have UserAId=1 and UserBId=2 with those that have UserAId=2 and UserBId=1.
To do what you want you need a little trick. What you call UserAId and UserBId in the result set are not actually always what you read on the input table. This query will do what you ask:
SELECT
CASE WHEN UserAId<UserBId THEN UserAId ELSE UserBId END AS User_AId,
CASE WHEN UserAId<UserBId THEN UserBId ELSE UserAId END AS User_BId,
SUM(Duration) AS TotalDuration,
SUM(Impulses) AS TotalImpulses,
COUNT(*) AS TotalCallsCount
FROM CustomerDetail
GROUP BY
CASE WHEN UserAId<UserBId THEN UserAId ELSE UserBId END,
CASE WHEN UserAId<UserBId THEN UserBId ELSE UserAId END
... it works even if UserAId=UserBId (you did not state if those two values can or cannot be the same). You will always get as User_AId the lesser of the 2 Ids, and as User_BId the greater of the 2 Ids... even if that combination does not exist as UserAId, UserBId nowhere in the table (obviously only if it does exist as UserBId, UserAId).
I have tested this on SQLFiddle here.
I am no SQL-Server expert. Some engines do allow the GROUP BY clause to reference calculated columns defined in the SELECT expression list without having to redefine them explicitly. This is non standard SQL, but it does make the SQL much more readable. Not sure if SQL-Server supports some sort of syntax for this.

select sum(a), sum(b where c=1) from db; sql conditions in select statement

i guess i just lack the keywords to search, but this is burning on my mind:
how can i add a condition to the sum-function in the select-statement like
select sum(a), sum(b where c=1) from db;?
this means, i want to see the sum of column a and the sum of column b, but only of the records in column b of which column c has the value 1.
the output of heidi just says "bad syntac near WHERE". may there be any other way?
thanks in advance and best regards from Berlin, joachim
The exact syntax may differ depending on the database engine, however it will be along the lines of
SELECT
sum(a),
sum(CASE WHEN c = 1 THEN b ELSE 0 END)
FROM
db
select sum(case when c=1 then b else 0 end)
This technique is useful when you need a lot of aggregates on the same set of data - you can query the entire table without applying a where filter, and have a bunch of these which give you aggregated data for a specific filter.
It's also useful when you need a lot of counts based on filters - you can do sums of 1 or 0:
select sum(case when {somecondition} then 1 else 0 end)

Complex SQL query on one table

Have forgotten SQL queries as have not used it for a long time.
I have a following requirement.
Have a table called match where I keep my competitor details with respect to matches my team have played against them. So some important fields are like this
match_id
competior_id
match_winner_id
ismatchtied
goals_scored_my_team
goals_scored_comp
From this table I want to get the head to head information for all my competitors.
like this
Competitor Matches Wins Losses Draws
A 10 5 4 1
B 8 3 2 1
Draw information I can get from ismatchtied is set to 'Y' or 'N'.
I want to get all the info from one query. I can get all the info from executing queries separately and do complex logic processing in my server code. But my performance will take a hit.
Any help will be hugely appreciated.
cheers,
Saurav
You could use conditional aggregation, involving CASE expressions inside aggregate functions, like this:
SELECT
competitor_id,
COUNT(*) AS Matches,
COUNT(CASE WHEN goals_scored_my_team > goals_scored_comp THEN 1 END) AS Wins,
COUNT(CASE WHEN goals_scored_my_team < goals_scored_comp THEN 1 END) AS Losses,
COUNT(CASE WHEN goals_scored_my_team = goals_scored_comp THEN 1 END) AS Draws
FROM matches
GROUP BY
competitor_id
;
Every CASE above will evaluate to NULL when the condition isn't satisfied. And since COUNT(expr) omits NULLs, every COUNT(CASE ...) in the above query will effectively only count rows that match the corresponding WHEN condition.
So, the first COUNT counts only rows where my team scored more against the competitor, i.e. where my team won. In a similar way, the second and the third CASEs get the numbers of losses and draws.
SELECT m4.competior_id, COUNT(*) as TotalMathces,
(select count(*) from match m1 where goals_scored_my_team>goals_scored_comp AND m1.competior_id=m4.competior_id) as WINS,
(select count(*) as WIN from match m2 where goals_scored_comp>goals_scored_my_team AND m2.competior_id=m4.competior_id) as LOSES,
(select count(*) as WIN from match m3 where goals_scored_my_team=goals_scored_comp AND m3.competior_id=m4.competior_id) as DRAWS
FROM match m4 group by m4.competior_id;