Why do I get NULL on this query? - mysql

I want to get the max values from kullnr where the values of ras are 1, 2 and 3. But when I run this query, I get NULL on all three. Here's the code:
SELECT MAX( k1.kullnr ) , MAX( k2.kullnr ) , MAX( k3.kullnr )
FROM kull AS k1
JOIN kull AS k2 ON k1.kullnr = k2.kullnr
JOIN kull AS k3 ON k1.kullnr = k3.kullnr
WHERE k1.ras =0
AND k2.ras =1
AND k3.ras =2
If I run queries separated from each other, they work fine. For example:
SELECT MAX(kullnr) FROM kull WHERE ras=0
But due to extreme memory limitations, I can't run the queries separated. How can I make the single-query version work?

Unless all your maximum kullnr fields have the same value, you will get null because you have joined on the field you are trying to maximize. I think this is more what you want:
SELECT k1.kullnr, k2.kullnr, k3.kullnr
FROM
(SELECT MAX(kullnr) AS kullnr FROM kull WHERE ras = 0) k1,
(SELECT MAX(kullnr) AS kullnr FROM kull WHERE ras = 1) k2,
(SELECT MAX(kullnr) AS kullnr FROM kull WHERE ras = 2) k3
I really don't think I would do this in this way, though, if I could help it. If you do not need all the maximums in the same row, another query might be:
SELECT ras, MAX(kullnr)
FROM kull
GROUP BY ras
HAVING ras = 0 OR ras = 1 OR ras = 2

Looking at your separate query
SELECT MAX(kullnr) FROM kull WHERE ras=0
You need
SELECT MAX(CASE WHEN ras = 0 THEN kullnr END ) ,
MAX(CASE WHEN ras = 1 THEN kullnr END ) ,
MAX(CASE WHEN ras = 2 THEN kullnr END )
FROM kull
WHERE ras IN (0,1,2)
You are joining on kullnr so for each row all self joined tables will have the same value for this field but then you are selecting the max value from each of the three tables for this field. If the JOIN returns any rows at all then the max would be the same across all 3 columns.

The query from Martin will get you the results you want.
As to WHY you got a null, the JOIN conditions are probably preventing any set of three rows from matching. They actually restrict the query to rows where the same kullnr is present in ras=0, ras=1 and ras=2

Related

Nested queries and Join

As a beginner with SQL, I’m ok to do simple tasks but I’m struggling right now with multiple nested queries.
My problem is that I have 3 tables like this:
a Case table:
id nd date username
--------------------------------------------
1 596 2016-02-09 16:50:03 UserA
2 967 2015-10-09 21:12:23 UserB
3 967 2015-10-09 22:35:40 UserA
4 967 2015-10-09 23:50:31 UserB
5 580 2017-02-09 10:19:43 UserA
a Value table:
case_id labelValue_id Value Type
-------------------------------------------------
1 3633 2731858342 X
1 124 ["864","862"] X
1 8981 -2.103 X
1 27 443 X
... ... ... ...
2 7890 232478 X
2 765 0.2334 X
... ... ... ...
and a Label table:
id label
----------------------
3633 Value of W
124 Value of X
8981 Value of Y
27 Value of Z
Obviously, I want to join these tables. So I can do something like this:
SELECT *
from Case, Value, Label
where Case.id= Value.case_id
and Label.id = Value.labelValue_id
but I get pretty much everything whereas I would like to be more specific.
What I want is to do some filtering on the Case table and then use the resulting id's to join the two other tables. I'd like to:
Filter the Case.nd's such that if there is serveral instances of the same nd, take the oldest one,
Limit the number of nd's in the query. For example, I want to be able to join the tables for just 2, 3, 4 etc... different nd.
Use this query to make a join on the Value and Label table.
For example, the output of the queries 1 and 2 would be:
id nd date username
--------------------------------------------
1 596 2016-02-09 16:50:03 UserA
2 967 2015-10-09 21:12:23 UserB
if I ask for 2 different nd. The nd 967 appears several times but we take the oldest one.
In fact, I think I found out how to do all these things but I can't/don't know how to merge them.
To select the oldest nd, I can do someting like:
select min((date)), nd,id
from Case
group by nd
Then, to limit the number of nd in the output, I found this (based on this and that) :
select *,
#num := if(#type <> t.nd, #num + 1, 1) as row_number,
#type := t.nd as dummy
from(
select min((date)), nd,id
from Case
group by nd
) as t
group by t.nd
having row_number <= 2 -- number of output
It works but I feel it's getting slow.
Finally, when I try to make a join with this subquery and with the two other tables, the processing keeps going on for ever.
During my research, I could find answers for every part of the problem but I can't merge them. Also, for the "counting" problem, where I want to limit the number of nd, I feel it's kind of far-fetch.
I realize this is a long question but I think I miss something and I wanted to give details as much as possible.
to filter the case table to eliminate all but oldest nds,
select * from [case] c
where date = (Select min(date) from case
where nd = c.nd)
then just join this to the other tables:
select * from [case] c
join value v on v.Case_id = c.Id
join label l on l.Id = v.labelValue_id
where date = (Select min(date) from [case]
where nd = c.nd)
to limit it to a certain number of records, there is a mysql specific command, I think it called Limit
select * from [case] c
join value v on v.Case_id = c.Id
join label l on l.Id = v.labelValue_id
where date = (Select min(date) from [case]
where nd = c.nd)
Limit 4 -- <=== will limit return result set to 4 rows
if you only want records for the top N values of nd, then the Limit goes on a subquery restricting what values of nd to retrieve:
select * from [case] c
join value v on v.Case_id = c.Id
join label l on l.Id = v.labelValue_id
where date = (Select min(date) from [case]
where nd = c.nd)
and nd In (select distinct nd from [case]
order by nd desc Limit N)
So finally, here is what worked well for me:
select *
from (
select *
from Case
join (
select nd as T_ND, date as T_date
from Case
where nd in (select distinct nd from Case)
group by T_ND Limit 5 -- <========= Limit of nd's
) as t
on Case.nd = t.T_ND
where date = (select min(date)
from Case
where nd = t.T_ND)
) as subquery
join Value
on Value.context_id = subquery.id
join Label
on Label.id = Value.labelValue_id
Thank you #charlesbretana for leading me on the right track :).

JOIN vs UNION vs IN() - big tables and many WHERE conditions

I use MySQL 5.5 and I have 3 tables created for testing:
attributes (entity_id, cid, aid, value) - indexes: ALL
items (entity_id, price, currency) - indexes: entity_id
rates (currency_from, currency_to, rate) - indexes: NONE
I need to count the results for specified conditions (search by attributes) and select X rows ordered by some column.
The query should support searching in item attributes (attributes table).
I have a query like this at first:
SELECT i.entity_id, i.price * COALESCE(r.rate, 1) AS final_price
FROM items i
JOIN attributes a ON a.entity_id = i.entity_id
LEFT JOIN rates r ON i.currency = r.currency_from AND r.currency_to = 'EUR'
WHERE a.cid = 4 AND ( (a.aid >= 10 AND a.value > 2000) OR (a.aid <= 10 AND a.value > 5) )
HAVING final_price BETWEEN 0 AND 9000
ORDER BY final_price DESC
LIMIT 20
but it's quite slow on big tables. The where conditions can be bigger (even to 30 params) and use CAST(a.value as SIGNED) to use BETWEEN sometimes (for range values).
For example:
SELECT
i.entity_id,
i.price * COALESCE(r.rate, 1) AS final_price
FROM
attributes a
JOIN items i
ON a.entity_id = i.entity_id
LEFT JOIN rates r
ON i.currency = r.currency_from
AND r.currency_to = 'EUR'
WHERE
a.cid = 4 AND (
(a.aid = 10 AND CAST(a.value AS SIGNED) BETWEEN 2000 AND 2014)
OR (a.aid = 121 AND CAST(a.value AS SIGNED) BETWEEN 40 AND 60)
OR (a.aid = 45 AND CAST(a.value AS SIGNED) BETWEEN 770 AND 1500)
OR (a.aid = 95 AND CAST(a.value AS SIGNED) BETWEEN 12770 AND 15500)
OR (a.aid = 98 AND a.value = 'some value')
OR (a.aid = 199 AND a.value = 'some another value')
OR (a.aid = 102 AND a.value = 1)
OR (a.aid = 112 AND a.value = 42) )
GROUP BY
i.entity_id
HAVING
COUNT(i.entity_id) = 7
AND final_price BETWEEN 0 AND 9000
ORDER BY
final_price DESC
LIMIT 20
I group by COUNT() equal to 7 (number of attributes to search), because I need to find items with all these attributes.
EXPLAIN for the base query (the first one):
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE a ALL entity_id,value NULL NULL NULL 379999 Using where; Using temporary; Using filesort
1 SIMPLE i eq_ref PRIMARY PRIMARY 4 testowa.a.entity_id 1 Using where
1 SIMPLE r ALL NULL NULL NULL NULL 2
I read many topics about comparing UNION vs JOIN vs IN() and the best results gives the second option, but it's too slow all the time.
Is there any way to get better performance here? Why is it so slow?
Should I think about moving some logic (split this query to 3 small) to backend (php/ror) code?
I would restructure your query slightly and have the attributes table first
and then joined to the items. Also, I would have a covering index on the
items table via (entity_id, price) and an index on your attributes table
ON (cid, aid, value, entity_id), and your rates table index
ON (currency_from, currency_to, rate). This way, all are covering indexes
and the engine won't need to go to the raw data pages to get the data, it can
pull it from the indexes it is already using for the joining / criteria.
SELECT
i.entity_id,
i.price * COALESCE(r.rate, 1) AS final_price
FROM
attributes a
JOIN items i
ON a.entity_id = i.entity_id
LEFT JOIN rates r
ON i.currency = r.currency_from
AND r.currency_to = 'EUR'
WHERE
a.cid = 4 AND ( (a.aid >= 10 AND a.value > 2000) OR (a.aid <= 10 AND a.value > 5) )
HAVING
final_price BETWEEN 0 AND 9000
ORDER BY
final_price DESC
LIMIT 20
So, although this would help the query you have provided, could you show some other where you would have many more criteria conditions... you mentioned it could be as many (or more) than 30. Looking at more might alter the query slightly.
As for your updated query with multiple criteria, I would then add an IN() clause for all the "aid" values after the "a.cid = 4". This way, before it has to hit all the "OR" conditions, if it fails on the "aid" not being one you consider, it never has to hit those... such as
a.cid = 4
AND a.id in ( 10, 121, 45, 95, 98, 199, 102 )
AND ( rest of the complex aid, casting and between criteria )

SQL query problems transpose MySQL

There are 2 simple tables
People:
person_id
Name
Reading assestment
date
person_id
quality
speed
Trying to create sql query
SELECT
AVG(r.quality),
AVG(r.speed),
FROM reading_assestment r,people p
where r.person_id =p.person_id
and person_id="3"
Current output:
Quality Speed
77 65
Outcome I am looking for:
Assestment Value
Quality 77
Speed 65
It is something to do with transpose , pivots.
The most general way is to start with your query and then unpivot using separate logic:
select (case when n = 1 then 'Quality' else 'Speed' end) as Assessment,
(case when n = 1 then avg_quality else avg_speed end) as Value
from (select AVG(r.quality) as avg_quality, AVG(r.speed) as avg_speed
from reading_assestment r join
people p
on r.person_id =p.person_id
where person_id = 3
) t cross join
(select 1 as n union all select 2) n
SELECT
AggResults.person_id AS person_id,
Assesment.Name AS AssessmentName,
CASE WHEN Assessment.Name = 'Quality' THEN AggResults.AvgQuality
WHEN Assessment.Name = 'Speed' THEN AggResults.AvgSpeed
ELSE NULL
END AS AssessmentValue
FROM
(
SELECT
people.person_id AS person_id,
AVG(quality) AS AvgQuality,
AVG(speed) AS AvgSpeed
FROM
reading_assessment
INNER JOIN
people
ON reading_assessment.person_id = people.person_id
GROUP BY
people.person_id
)
AS AggResults
CROSS JOIN
(
SELECT 'Quality' AS Name
UNION ALL
SELECT 'Speed' AS Name
)
AS Assessment
WHERE
AggResults.person_id = 3
I moved the = 3 to the outer query, just for ease of use. Most optimisers will treat both options similarly.

Join Table and Select Highest Date Value

Here is the query that I run
SELECT cl.cl_id, cc_rego, cc_model, cl_dateIn, cl_dateOut
FROM courtesycar cc LEFT JOIN courtesyloan cl
ON cc.cc_id = cl.cc_id
Results:
1 NXI955 Prado 2013-10-24 11:48:38 NULL
2 RJI603 Avalon 2013-10-24 11:48:42 2013-10-24 11:54:18
3 RJI603 Avalon 2013-10-24 12:01:40 NULL
The results that I wanted are to group by the cc_rego values and print the most recent cl_dateIn value. (Only Display Rows 1,3)
I've tried to use MAX on the date and group by clause, but it combines rows, 2 & 3 together showing both the highest value of dateIn and dateOut.
I resolved the problem.
Instead of using left join, I added a condition in the where clause which embeds to MAX of the dateIn
SELECT cll.cl_id, cc.cc_id, cc_rego, cc_model, cll.cl_dateIn, cll.cl_dateOut
FROM courtesycar cc, courtesyloan cll
WHERE cl_dateIn = (
SELECT MAX( cl.cl_dateIn )
FROM courtesyloan cl
WHERE cl.cc_id = cc.cc_id )
AND cc.cc_id = cll.cc_id

Subqueries to SELECT multiple COUNTs from two tables in one query

I'm struggling with what appears to be the most complicated SQL query I've ever written!
Database structure:
frog_shared.staff
`ID`, `Firstname`, `Surname`
frog_observations.observations
`ID`, `Teacher_ID`, `Type`, `Main_Positive_Aspect`, `Main_Development_Aspect`, `Grade_for_Behaviour`, `Grade_for_Attainment`, `Grade_for_Teaching`
frog_observations.aspects
`ID`, `Observation_ID`, `Label_ID`, `Type`
frog_observations.aspect_labels
`ID`, `Title`
Sample data:
Member of staff:
`12345`, `Duncan`, `Wraight`
Observation:
`9888`, `12345`, `Formal`, `5`, `7`, `1`, `1`, `1`
Aspects:
`101`, `9888`, `2`, `P`
Aspect labels:
`2`, `Questioning`
What I'm trying to achieve:
I'd like to produce a list of our best teachers in a specific observation aspect. For example, I'd like to see my top 5 teachers for "Questioning".
On top of that, I'd like to filter the data somewhat:
Aspects from both formal (Type=Formal) and sharing best practice (Type=SBP) observations should be counted
Aspects from formal observations should only be counted if that observation's grades are at most 2 across the board (no higher, i.e. can't have two grades at 2 and one at 3) but sharing best practice observations all have 0 for the grades
The main positive aspect, recorded in the observations table, should be weighted in the count
For formal observations, the main positive aspect should be weighted at 3* the amount of a normal aspect
For sharing best practice observations, the main positive aspect should be weighted at 2* the amount of a normal aspect
My attempts:
This statement filters the observations by their grades, but doesn't include any main positive aspects (they weren't included in the original system)
SELECT
CONCAT(s.Firstname, " ", s.Surname) AS `Teacher`,
COUNT( * ) AS Total,
GREATEST(o.`Achievement_Grade`, o.`Behaviour_Grade`, o.`Teaching_Grade`) AS `Worst Grade`
FROM frog_observations.observations o
INNER JOIN frog_shared.staff s ON o.Teacher_ID = s.ID
INNER JOIN frog_observations.aspects a ON o.ID = a.Observation_ID
WHERE a.Aspect_ID = 4
AND ( GREATEST(o.`Achievement_Grade`, o.`Behaviour_Grade`, o.`Teaching_Grade`) BETWEEN 1 AND 2 ) AND o.Datetime > '2011-09-01'
AND a.Type = 'P'
GROUP BY s.ID
ORDER BY `Total` DESC
LIMIT 5
Now the way I'd like to do it is something like this:
SELECT
CONCAT(s.Firstname, " ", s.Surname) AS `Teacher`,
COUNT(mp.*) AS `Number of Appraisal Main Positives`,
COUNT(sp.*) AS `Number of SBP Main Positives`,
COUNT(a.*) AS `Number of Other Positives`,
SUM(`Number of Appraisal Main Positives` + `Number of SBP Main Positives` `Number of Other Positives`) AS `Total`
FROM
`frog_observations`.`observations` o,
( `frog_observations`.`observations` WHERE `Type` = 'Formal') AS mp,
( `frog_observations`.`observations` WHERE `Type` = 'SBP') AS sp
INNER JOIN
`frog_observations`.`aspects` a ON a.Observation_ID = o.ID
GROUP BY s.ID
Unfortunately, other than knowing the headings I want (i.e. number of appraisal main positives, number of sharing best practice positives, number of standard positives and a weighted total), I have no idea how to write what I presume will be subqueries to get all of this information from a single statement.
Any guidance appreciated.
Edit: Sample input/output
Input
User wants to view Top 5 members of staff for the aspect Questioning
Process
- Aspect Questioning has ID 4 in the aspect_labels table.
- System should then, for each member of staff, count the observations they have with Main_Positive_Aspect set to 4. Some of these observations will be of Type Formal and some will be of Type SBP.
- System should also then count the number of rows in the aspects table for each member of staff where the Aspect_ID is 4.
Output
Key:
FMP = Number of rows in the observations table where Type is Formal; GREATEST(Achievement_Grade, Behaviour_Grade, Teaching_Grade) BETWEEN 1 AND 2; and the Main_Positive_Aspect is 4 (i.e. Questioning in the aspects_label table)
SMP = Number of rows in the observations table where Type is SBP and the Main_Positive_Aspect is 4
Other = Number of rows in the aspects table where Aspect_ID is 4 for each member of staff (via the o.Observation_ID -> o.Staff_ID link)
Points = Weighted total for each of these - each FMP is worth 3 points, each SMP is worth 2 points and each Other is worth 1. Query should then be ordered, DESC, by this column.
Example output:
-------------------------------------------------------------
| Staff Name FMP SMP Other Points |
|------------------------------------------------------------
| D Wraight 2 1 4 12 |
| A Nother 3 0 0 9 |
| J Bloggs 0 4 1 9 |
| J Arthur 1 1 1 6 |
| M Turner 0 1 0 2 |
-------------------------------------------------------------
Here are the queries, written individually, which work on my database. Basically I need to amalgamate these into one query. If that's possible!
#=======#
# FMP #
#=======#
SELECT
CONCAT(s.Firstname, " ", s.Surname) AS `Teacher`,
COUNT( * ) AS `FMP`
FROM frog_observations.observations o
INNER JOIN frog_shared.staff s ON o.Teacher_ID = s.ID
WHERE o.Main_Positive = 4
AND o.Type = 'F'
AND ( GREATEST(o.`Achievement_Grade`, o.`Behaviour_Grade`, o.`Teaching_Grade`) BETWEEN 1 AND 2 )
GROUP BY s.ID
#=======#
# SMP #
#=======#
SELECT
CONCAT(s.Firstname, " ", s.Surname) AS `Teacher`,
COUNT( * ) AS `SMP`
FROM frog_observations.observations o
INNER JOIN frog_shared.staff s ON o.Teacher_ID = s.ID
WHERE o.Main_Positive = 4
AND o.Type = 'S'
GROUP BY s.ID
#=========#
# Other #
#=========#
SELECT
CONCAT(s.Firstname, " ", s.Surname) AS `Teacher`,
COUNT( * ) AS `Other`
FROM observations o
INNER JOIN frog_shared.staff s ON o.Teacher_ID = s.ID
INNER JOIN aspects a ON o.ID = a.Observation_ID
WHERE a.Aspect_ID = 4
AND a.Type = 'P'
GROUP BY s.ID
Would something like this work for the initial cut...
SELECT
CONCAT(s.Firstname, " ", s.Surname) AS Teacher,
sum(Case when `Type` = 'Formal' then 1 else 0 end) AS `Number of Appraisal Main Positives`,
sum(Case when `Type` = 'SBP' then 1 else 0 end) AS `Number of SBP Main Positives`,
coalesce(OtherPositives,0) AS `Number of Other Positives`,
sum(Case when `Type` in ( 'Formal' ,'SBP') then 1 else 0 end) + coalesce(OtherPositives,0) AS `Total`
FROM observations o
INNER JOIN staff s ON o.Teacher_ID = s.ID
LEFT JOIN (select Observation_ID, count(*) as OtherPositives) from aspects where Label_ID=4 group by Observation_ID) a ON a.Observation_ID = o.ID
WHERE Main_Positive =4
GROUP BY s.ID