I have a table named results retrieved and displayed as follows
the columns sub1 to sub2 represents the subject names and the rows have values scored by the students
While the information is retrieved from the db, I also need to count in how many subjects did a student score less than 40 for example, Tom scored less than 40 in 2 subjects and the result would look like as follows
Please help how to write a query to display the last column
Normalizing your table would have made this much easier, but even without changing its structure, you can get this result with some case statements:
SELECT id, students,
sub1, sub2, sub3, sub4, sub5, sub6,
CASE WHEN sub1 < 40 THEN 1 ELSE 0 END +
CASE WHEN sub2 < 40 THEN 1 ELSE 0 END +
CASE WHEN sub3 < 40 THEN 1 ELSE 0 END +
CASE WHEN sub4 < 40 THEN 1 ELSE 0 END +
CASE WHEN sub5 < 40 THEN 1 ELSE 0 END +
CASE WHEN sub6 < 40 THEN 1 ELSE 0 END AS "Failed IN"
FROM my_table
MySQL has the convenient ability to treat boolean comparisons as integers. Here is a pretty simple way to express your logic:
select id, students, sub1, sub2, sub3, sub4, sub5, sub6,
((sub1 < 40) + (sub2 < 40) + (sub3 < 40) + (sub4 < 40) + (sub5 < 40) + (sub6 < 40)
) as FailedIn
from table t;
Related
Let's say I have a table with Columns A, B, C, D, E and F.
How would I query for entries where (A, B, C, D, E, F) = (1, 2, 3, 4, 5, 6) but only a subset of columns need to match? For example at least 3 out of the 6 columns have to match.
The only solution I can think of is to go through all combinations where (A, B, C) = (1, 2 ,3) or (A, B, D) = (1, 2, 4) or...
But in this example that would already be 20 where clauses, if my math is correct. Is there a better solution, that also works with more columns? Or is my only option to programmatically create a huge, non human-readable query string with hundreds of where clauses?
In MySql boolean expressions are evaluated as 1 for true or 0 for false, so you can add them in the WHERE clause:
WHERE (A = 1) + (B = 2) + (C = 3) + (D = 4) + (E = 5) + (F = 6) >= 3
Just in case any of the 6 columns is nullable, use the NULL-safe equal to operator <=> instead of =:
You can use a score system and then get the rows sorted by score. For example:
select *
from (
select t.*,
case when a = 1 then 1 else 0 end +
case when b = 2 then 1 else 0 end +
case when c = 3 then 1 else 0 end +
case when d = 4 then 1 else 0 end +
case when e = 5 then 1 else 0 end +
case when f = 6 then 1 else 0 end as score
from t
) x
where score >= 3
order by score desc
Of course, this query won't be efficient in terms of execution time, but should work well for small subsets of data.
There is a request for 15 minute intervals in the interval from 09.00 to 18.00 hours.
SELECT t.event_date,
case
when TIME_TO_SEC(t.event_date) > inter.begin AND TIME_TO_SEC(t.event_date) < inter.end
then floor((TIME_TO_SEC(t.event_date) - inter.begin) / inter.width)
when TIME_TO_SEC(t.event_date) <= inter.begin
then 0
when TIME_TO_SEC(t.event_date) >= inter.end
then floor((inter.end - inter.begin) / inter.width)
else null
end as full_interval_number
FROM table t,
(select TIME_TO_SEC('09:00:00') as begin,
TIME_TO_SEC('18:00:00') as end,
TIME_TO_SEC('00:15:00') as width
) inter
How to use WHERE to exclude temporary intrevalues №0, 3, 7, 15 or intrevalues from 09.00 to 10.00 hours?
Something like this:
where
TIME_TO_SEC(event_date) < TIME_TO_SEC('09:00:00')
and TIME_TO_SEC(event_date) > TIME_TO_SEC('10:00:00')
or:
where
full_interval_number not in (0, 3, 7, 15)
Is this what you want? The first part is the general limit for 9-18 and the second part is the specific requirement to exclude values between 9-10
where TIME_TO_SEC(event_date) between TIME_TO_SEC('09:00:00') and TIME_TO_SEC('18:00:00')
and not (TIME_TO_SEC(event_date) between TIME_TO_SEC('09:00:00') and TIME_TO_SEC('10:00:00'))
Full query
SELECT t.event_date,
case
when TIME_TO_SEC(t.event_date) > inter.begin AND TIME_TO_SEC(t.event_date) < inter.end
then floor((TIME_TO_SEC(t.event_date) - inter.begin) / inter.width)
when TIME_TO_SEC(t.event_date) <= inter.begin
then 0
when TIME_TO_SEC(t.event_date) >= inter.end
then floor((inter.end - inter.begin) / inter.width)
else null
end as full_interval_number
FROM table t,
(select TIME_TO_SEC('09:00:00') as begin,
TIME_TO_SEC('18:00:00') as end,
TIME_TO_SEC('00:15:00') as width
) inter
where TIME_TO_SEC(event_date) between TIME_TO_SEC('09:00:00') and TIME_TO_SEC('18:00:00')
and not (TIME_TO_SEC(event_date) between TIME_TO_SEC('09:00:00') and TIME_TO_SEC('10:00:00'))
I'm creating a rating system. I have two tables hinne (rating) and hinnang (rating multiplier). I need to multiply the rating and then average the rating to know what rating I got out of all ratings by aine(subject).
Example:
All points need to be calculated in 0-100 point system.
So if my first rate is 25 and the rating multiplier is 4 then first rate (25/25)
4*25=100
If the second rate is 30 and multiplier 2 then second rate (30/50)
2*30=60
Now I need to average them like 100+60/2=80.
That should work in my SQL statement, but I got in trouble.
CASE
WHEN aine.nimetus = 'Füüsika I'
THEN hinne.tulemus * hindamine.kaal
ELSE 0
END
,0)
))
So, this is my pivot case statement. hindamine.kaal should be different value for each hinne.tulemus 25*4,50*2 BUT it doesn't work. It just uses multiplier value 4. How can I make this work?
The result of SQL: 150
The expected result: 100
Therefore here is my full SQL:
SELECT
tudeng.m_number,hindamine.kaal, ROUND(AVG(NULLIF(
CASE
WHEN aine.nimetus = 'Füüsika I'
THEN hinne.tulemus * hindamine.kaal
ELSE 0
END
,0)
))
AS FüüsikaI ,ROUND(AVG(NULLIF(
CASE
WHEN aine.nimetus = 'Kõrgem matemaatika I'
THEN hinne.tulemus * hindamine.kaal
ELSE 0
END
,0)
))
AS KõrgemmatemaatikaI ,ROUND(AVG(NULLIF(
CASE
WHEN aine.nimetus = 'Raalprojekteerimine'
THEN hinne.tulemus * hindamine.kaal
ELSE 0
END
,0)
))
AS Raalprojekteerimine ,ROUND(AVG(NULLIF(
CASE
WHEN aine.nimetus = 'Tehniline graafika'
THEN hinne.tulemus * hindamine.kaal
ELSE 0
END
,0)
))
AS Tehnilinegraafika , ROUND(AVG(NULLIF(
CASE
WHEN aine.nimetus = 'Ettevõteluse alused'
THEN hinne.tulemus * hindamine.kaal
ELSE 0
END
,0)
))
AS Ettevõtelusealused
FROM
tudeng
INNER JOIN
aine_tudeng
ON
tudeng.tudeng_id = aine_tudeng.tudeng_id
INNER JOIN
aine
ON
aine.aine_id = aine_tudeng.aine_id
INNER JOIN
hinne
ON
hinne.aine_tudeng_id=aine_tudeng.aine_tudeng_id
INNER JOIN
hindamine
ON
hindamine.hindamine_id=aine_tudeng.aine_id
GROUP BY
tudeng.m_number
I suppose your error is here:
ON hindamine.hindamine_id = aine_tudeng.aine_id
A hindamine (assessment/rating?) is something different from an aine (subject?), so you are mistaken in joining on these IDs.
(I have used Google translator to help me with the meanings.)
I am trying to calculate an average number from three columns but only include the column in the calculation if column is not null and is bigger than 0;
for example the average usually is
(column1 + column2 + column3) / 3
but if column3 is null or 0 then it will be
(column1 + column2 + column3) / 2 or (column1 + column2 ) / 2
I have this sol far but it is not complete. the average is wrong when one of the columns is 0 (0 is default)
SELECT movie.title,
movie.imdbrating,
movie.metacritic,
tomato.rating,
((imdbrating + metacritic + tomato.rating)/3) as average
FROM movie, tomato
WHERE movie.imdbid = tomato.imdbid
How can I implement this?
I'm fixing the rest of the query to use table aliases and proper join syntax. But the case statements are what you really need:
SELECT m.title, m.imdbrating, m.metacritic,
t.rating,
((case when imdbrating > 0 then imdbrating else 0 end) +
(case when metacritic > 0 then metacritic else 0 end) +
(case when t.rating > 0 then t.rating else 0 end) +
) / nullif(coalesce((imdbrating > 0), 0) + coalesce((metacritic > 0), 0) + coalesce((t.rating > 0), 0)), 0)
FROM movie m JOIN
tomato t
ON m.imdbid = t.imdbid;
The denominator is using a convenient MySQL extension where booleans are treated as 0 or 1 in a numeric context. The nullif() returns NULL if no rating meets the conditions. And, the > 0 is is not true for NULL values.
Try this:
SELECT (IF Column3 IS NULL OR Column3=0, AVG(Column1+Column2), Avg(Column1+Column2+ Column3)) as Result FROM Table
EDIT:
If any of the tree columns can be nullm, try this:
SELECT AVG(IF(Column1 IS NULL,0,Column1)+IF(Column2 IS NULL,0,Column2)+IF(Column3 IS NULL,0,Column3)) as Result FROM Table
UPDATE
(ifnull(column1,0)+ifnull(column2,0)+ifnull(column3,0))/
nullif((abs(sign(ifnull(column1,0)))
+abs(sign(ifnull(column2,0)))
+abs(sign(ifnull(column3,0)))), 0)
Can handle Negative values.
Demo to test
I've been asked to create a financial report, which needs to give a total commission rate between two dates for several 'referrers'. That's the easy part.
The difficult part is that the commission rate varies depending not only on the referrer but also on the type of referral and also on the number of referrals of that type that have been made by a given referrer.
The tracking of the number of referrals needs to take into account ALL referrals, rather than those in the given date range - in other words, the commission rate is on a sliding scale for each referrer, changing as their total referrals increase. Luckily, there are only a maximum of 3 commission levels for each type of referral.
The referrals are all stored in the same table, 1 row per referral, with a field denoting the referrer and the type of referral. An example to illustrate:
ID Type Referrer Date
1 A X 01/12/08
2 A X 15/01/09
3 A X 23/02/09
4 B X 01/12/08
5 B X 15/01/09
6 A Y 01/12/08
7 A Y 15/01/09
8 B Y 15/01/09
9 B Y 23/02/09
The commission rates are not stored in the referral table - and indeed may change - instead they are stored in the referrer table, like so:
Referrer Comm_A1 Comm_A2 Comm_A3 Comm_B1 Comm_B2 Comm_B3
X 30 20 10 55 45 35
Y 45 35 25 60 40 30
Looking at the above referral table as an example, and assuming the commission rate level increased after referral number 1 and 2 (then remained the same), running a commission report for December 2008 to February 2009 would return the following:
[Edit] - to clarify the above, the commission rate has three levels for each type and each referrer, with the initial rate Comm_A1 for the first referral commission, then Comm_A2 for the second, and Comm_A3 for all subsequent referrals.
Referrer Type_A_Comm Type_A_Ref Type_B_Comm Type_B_Ref
X 60 3 100 2
Y 80 2 100 2
Running a commission report for just February 2009 would return:
Referrer Type_A_Comm Type_A_Ref Type_B_Comm Type_B_Ref
X 10 1 0 0
Y 0 0 40 1
Edit the above results have been adjusted from my original question, in terms of the column / row grouping.
I'm quite sure that any solution will involve a sub-query (perhaps for each referral type) and also some kind of aggregate / Sum If - but I'm struggling to come up with a working query.
[Edit] I'm not sure about writing an equation of my requirements, but I'll try to list the steps as I see them:
Determine the number of previous referrals for each type and each referrer - that is, irrespective of any date range.
Based on the number of previous referrals, select the appropriate commission level - 0 previous = level 1, 1 previous = level 2, 2 or more previous = level 3
(Note: a referrer with no previous referrals but, say, 3 new referrals, would expect a commission of 1 x level 1, 1 x level 2, 1 x level 3 = total commission)
Filter results according to a date range - so that commission payable for a period of activity may be determined.
Return data with column for referrer, and a column with the total commission for each referral type (and ideally, also a column with a count for each referral type).
Does that help to clarify my requirements?
Assuming that you have a table called type that lists your particular referral types, this should work (if not, you could substitute another subselect for getting the distinct types from referral for this purpose).
select
r.referrer,
t.type,
(case
when isnull(ref_prior.referrals, 0) < #max1 then
(case
when isnull(ref_prior.referrals, 0) + isnull(ref_period.referrals, 0) < #max1 then isnull(ref_period.referrals, 0)
else #max1 - isnull(ref_prior.referrals, 0)
end)
else 0
end) * (case t.type when 'A' then r.Comm_A1 when 'B' then r.Comm_B1 else null end) +
(case when isnull(ref_prior.referrals, 0) + isnull(ref_period.referrals, 0) > #max1 then
(case
when isnull(ref_prior.referrals, 0) < #max2 then
(case
when isnull(ref_prior.referrals, 0) + isnull(ref_period.referrals, 0) < #max2 then isnull(ref_period.referrals, 0)
else #max2 - isnull(ref_prior.referrals, 0)
end)
else 0
end) -
(case
when isnull(ref_prior.referrals, 0) < #max1 then
(case
when isnull(ref_prior.referrals, 0) + isnull(ref_period.referrals, 0) < #max1 then isnull(ref_period.referrals, 0)
else #max1 - isnull(ref_prior.referrals, 0)
end)
else 0
end)
else 0 end) * (case t.type when 'A' then r.Comm_A2 when 'B' then r.Comm_B2 else null end) +
(case when isnull(ref_prior.referrals, 0) + isnull(ref_period.referrals, 0) > #max2 then
(isnull(ref_period.referrals, 0)) -
(
(case when isnull(ref_prior.referrals, 0) + isnull(ref_period.referrals, 0) > #max1 then
(case
when isnull(ref_prior.referrals, 0) < #max2 then
(case
when isnull(ref_prior.referrals, 0) + isnull(ref_period.referrals, 0) < #max2 then isnull(ref_period.referrals, 0)
else #max2 - isnull(ref_prior.referrals, 0)
end)
else 0
end) -
(case
when isnull(ref_prior.referrals, 0) < #max1 then
(case
when isnull(ref_prior.referrals, 0) + isnull(ref_period.referrals, 0) < #max1 then isnull(ref_period.referrals, 0)
else #max1 - isnull(ref_prior.referrals, 0)
end)
else 0
end)
else 0 end) +
(case
when isnull(ref_prior.referrals, 0) < #max1 then
(case
when isnull(ref_prior.referrals, 0) + isnull(ref_period.referrals, 0) < #max1 then isnull(ref_period.referrals, 0)
else #max1 - isnull(ref_prior.referrals, 0)
end)
else 0
end)
)
else 0 end) * (case t.type when 'A' then r.Comm_A3 when 'B' then r.Comm_B3 else null end) as Total_Commission
from referrer r
join type t on 1 = 1 --intentional cartesian product
left join (select referrer, type, count(1) as referrals from referral where date < #start_date group by referrer, type) ref_prior on ref_prior.referrer = r.referrer and ref_prior.type = t.type
left join (select referrer, type, count(1) as referrals from referral where date between #start_date and #end_date group by referrer, type) ref_period on ref_period.referrer = r.referrer and ref_period.type = t.type
This assumes that you have a #start_date and #end_date variable, and you'll obviously have to supply the logic missing from the case statement to make the proper selection of rates based upon the type and number of referrals from ref_total.
Edit
After reviewing the question, I saw the comment about the sliding scale. This greatly increased the complexity of the query, but it's still doable. The revised query now also depends on the presence of two variables #max1 and #max2, representing the maximum number of sales that can fall into category '1' and category '2' (for testing purposes, I used 1 and 2 respectively, and these produced the expected results).
Adam's answer is far more thorough than I'm going to be but I think trying to write this as a single query might not be the right approach.
Have you thought about creating a stored procedure which creates and then populates a temporary table, step by step.
The temporary table would have the shape of the results set you're looking for. The initial insert creates your basic data set (essentially the number of rows you're looking to return with key identifiers and then anything else you're looking to return which can be easily assembled as part of the same query).
You then have a series of updates to the temporary table assembling each section of the more complex data.
Finally select it all back and drop the temporary table.
The advantages of this are that it allows you to break it down in your mind and assemble it a bit at a time which allows you to more easily find where you've gone wrong. It also means that the more complex bits can be assembled in a couple of stages.
In addition if some poor sod comes along and has to debug the whole thing afterwards it's going to be far easier for him to trace through what's happening where.
EDIT: this answer does not take the following requirement into account, but there seems to be a bunch of new explanations so I guess I'll leave it here as is...
The tracking of the number of referrals needs to take into account ALL referrals, rather than those in the given date range
Ok, assuming the report period is monthly, and using a CASE where actually an IF could distinguish the two valid rates (for count = 1 and count > 1) as well, what about:
select
ref.month,
ref.referrer,
ref.type,
( ref.count *
case ref.type
when 'A' then
case ref.count
-- not useful: when 0 then com.Comm_A1
when 1 then com.Comm_A2
else com.Comm_A3
end case
when 'B' then
case ref.count
-- not useful: when 0 then com.Comm_B1
when 1 then com.Comm_B2
else com.Comm_B3
end case
end case
) as total_commission
from
( select
date_format(date, '%Y-%m') as month,
referrer,
type,
count(*) as count
from referrals
group by month, referrer, type
) as ref,
join commissions com on com.referrer = ref.referrer
(I guess the names such as 'ref' and 'count' are not too well chosen above.)