SQL query problems transpose MySQL - 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.

Related

Select statement with conditions depending on the value of another column - same table (mysql)

I have table with 3 columns:
ID,
Cancellation_Policy_Type
Cancellation_Policy_Hours.
The query I would like to get to will allow me to select:
the min Cancellation_Policy_Hours which correspond to the Free Cancellation (if exists)
if the above doesn't exist for the specific ID, then I want to check if there is a partially refundable
if none of the above exist, then check if there is No Refundable.
The below query is not correct but it may give a better idea about what I am trying to achieve:
IF (SELECT ID, Cancellation_Policy_Type, MIN(Cancellation_Policy_Hours) from MYTABLE WHERE Cancellation_Policy_Type = 'Free Cancellation') IS NOT NULL)
THEN (SELECT ID, Cancellation_Policy_Type, MIN(Cancellation_Policy_Hours) from MYTABLE WHERE Cancellation_Policy_Type = 'Free Cancellation')
ELSEIF (SELECT ID, Cancellation_Policy_Type, MIN(Cancellation_Policy_Hours) from MYTABLE WHERE Cancellation_Policy_Type = 'Free Cancellation') IS NULL AND (SELECT ID, Cancellation_Policy_Type, MIN(Cancellation_Policy_Hours from MYTABLE WHERE Cancellation_Policy_Type = 'Partially Refundable') IS NOT NULL Then (SELECT ID, Cancellation_Policy_Type, MIN(Cancellation_Policy_Hours) from MYTABLE WHERE Cancellation_Policy_Type = 'Partially Refundable')
ELSEIF (SELECT ID, Cancellation_Policy_Type, MIN(Cancellation_Policy_Hours) from MYTABLE WHERE Cancellation_Policy_Type = 'Free Cancellation') IS NULL AND (SELECT ID, Cancellation_Policy_Type, MIN(Cancellation_Policy_Hours) from MYTABLE WHERE Cancellation_Policy_Type = 'Partially Refundable') IS NULL THEN (SELECT ID, Cancellation_Policy_Type, MIN(Cancellation_Policy_Hours) from MYTABLE WHERE Cancellation_Policy_Type = 'No Refundable')
END
Below you will find an example of my dataset:
This is the table which contains all data regarding the cancellation policies of every single ID:
ID
Cancellation_Policy_Type
Cancellation_Policy_Hours
1
No Refundable
17520
1
Partially Refunable
168
1
Free Cancellation
96
2
No Refundable
17520
2
Partially Refunable
336
2
Free Cancellation
48
3
No Refundable
17520
3
Partially Refunable
336
4
No Refundable
17520
Below is the desired result, that is a table which contains other pieces of information (including production) and the 2 columns where for every single ID repeats the best available cancellation policy type and hours:
ID
Most Flexible Cancellation Type
Most Flexible Cancellation Hours
Other Columns (including buckets)
1
Free Cancellation
96
a
1
Free Cancellation
96
b
1
Free Cancellation
96
c
2
Free Cancellation
48
a
2
Free Cancellation
48
b
2
Free Cancellation
48
c
3
Partially Refunable
336
a
3
Partially Refunable
336
b
3
Partially Refunable
336
c
4
No Refundable
17520
a
4
No Refundable
17520
b
4
No Refundable
17520
c
SELECT
a.ID
, Most_Flexible_Policy_Type
, Most_Flexible_Cancellation_Hours
, a.BookingWindowBuckets
FROM Production a
LEFT JOIN Property b on a.ID = b.ID
GROUP BY
1,2,3,4
Thank you
I understand that, for each row in production, you want to bring from table property the cancellation policy with the least policy hours.
We can do this with window functions to rank the policies of feach id, and a join to production:
select d.id,
p.cancellation_type_policy most_flexible_cancellation_type,
p.cancellation_policy_hours most_flexible_cancellation_hours
from production d
inner join (
select p.*, row_number() over(partition by id order by cancellation_policy_hours) rn
from property p
) p on p.id = d.id
where rn = 1
You have not given enough information to be able to help you with any degree of confidence that it is the "right" way but (guessing here) you could try -
SELECT
ID,
MIN(IF(Cancellation_Policy_Type = 'Free Cancellation', Cancellation_Policy_Hours, NULL)) AS minFreeCancellation,
MIN(IF(Cancellation_Policy_Type = 'Partially Refundable', Cancellation_Policy_Hours, NULL)) AS minPartiallyRefundable,
MIN(IF(Cancellation_Policy_Type = 'No Refundable', Cancellation_Policy_Hours, NULL)) AS minNoRefundable
FROM MYTABLE
WHERE ID = ?
GROUP BY ID;
If you provide an example in the form of full table structure (CREATE statement), some sample data, some stats and the desired outcome you are more likely to get the "right" answer. The above example of conditional aggregation is unlikely to be the best way to do it but it will probably provide what you are looking for. Depending on your dataset, just running the separate queries may be the best solution.
UPDATE
Here is one way of doing this using a window function (MySQL 8) -
WITH Properties (ID, Cancellation_Policy_Type, Cancellation_Policy_Hours, Most_Flexible) AS (
SELECT
ID, Cancellation_Policy_Type, Cancellation_Policy_Hours,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY
CASE Cancellation_Policy_Type
WHEN 'Free Cancellation' THEN 0
WHEN 'Partially Refundable' THEN 1
WHEN 'No Refundable' THEN 2
END ASC, Cancellation_Policy_Hours ASC
)
FROM Property
)
SELECT
a.ID,
b.Cancellation_Policy_Type,
b.Cancellation_Policy_Hours,
a.BookingWindowBuckets
FROM Production a
LEFT JOIN Properties b on a.ID = b.ID
WHERE b.Most_Flexible = 1;

How to get all entries with SQL query with join

kon
id | name
1 alex
2 peter
3 john
ticket
id | amount | kon_id | package
122 13 1 234
123 12 1 234
124 20 2 NULL
125 23 2 235
126 19 1 236
I would like to get a list of all contacts with the sum of the amount, except tickets, where the package entry is NULL.
My problem is, that I only get the contacts which have a ticket, because of the WHERE clause.
SELECT
kon.id,
kon.name,
SUM(ticket.amount)
FROM kon LEFT JOIN ticket ON kon.id = ticket.kon_id
WHERE ticket.package IS NOT NULL
GROUP BY kon.id
At the moment, the output looks like this
1 alex 44
2 peter 23
but it should look like this
1 alex 44
3 john NULL
2 peter 23
I use a MySQL Server.
Is it possible to solve this?
Replace Where with AND
SELECT
kon.id,
kon.name,
SUM(ticket.amount)
FROM kon LEFT JOIN ticket ON kon.id = ticket.kon_id AND ticket.package IS NOT NULL
GROUP BY kon.id
Check This.
SELECT
k.id,
k.name ,
coalesce (SUM(t.amount) ,0)
FROM kon k LEFT JOIN
( select id,amount,kon_id,package from ticket where package is not null ) t
ON k.id = t.kon_id
GROUP BY k.id, k.name
OutPut :
Begin Tran
Create Table #Kon (id INt , name Nvarchar(255))
Insert into #Kon
Select 1,'alex' UNION ALL
Select 2,'peter' UNION ALL
Select 3,'john'
Create Table #Ticket (id int,amount int,Kon_Id Int,Package Int)
INSERT INTO #Ticket
SELECT 122,13,1,234 UNION ALL
SELECT 123,12,1,234 UNION ALL
SELECT 124,20,2,NULL UNION ALL
SELECT 125,23,2,235 UNION ALL
SELECT 126,19,1,236
SELECT K.id, Name,SUM(amount) amount
FROM #Kon k
LEFT JOIN #Ticket T ON K.id=T.Kon_Id
GROUP BY K.id,Name
RollBAck Tran
Generally, "ticket.package IS NOT NULL" is wrong condition: your query becomes inner join from left join. If ticket.package should be NOT NULL to add from amount, it should be not in condition, but inside SUM agregate function.
working example for MS SQL
SELECT
kon.id,
min(kon.name),
SUM(case when package is NULL then 0 else ticket.amount end)
FROM #kon kon LEFT JOIN #ticket ticket ON kon.id = ticket.kon_id
GROUP BY kon.id
Answer from Mr. Bhosale is right too, but for big tables will have worse performance (the reason is subquery)
the following query return your expected result
SELECT
kon.id,
kon.name,
SUM(ticket.amount) as 'amount'
FROM kon LEFT JOIN ticket ON kon.id = ticket.kon_id
GROUP BY kon.id, kon.name
attached image shows the result
I figured out the fastest way to solve the problem. It takes about 0.2s compared to the other solutions (2s - 2min). The CAST is important, otherwise the summation of double variables is wrong (float string problem).
SELECT
kon1,
kon2,
SUM(CAST(kon3 AS DECIMAL(7,2)))
FROM (
SELECT k.id kon1, k.name kon2, t.amount kon3 FROM kon as k
LEFT JOIN ticket t ON k.id = t.ticket_kon
WHERE t.package IS NOT NULL
UNION ALL
SELECT k.id kon1, k.name kon2, NULL kon3 FROM kon k WHERE) t1
GROUP BY kon1, kon2

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

MySQL SELECT to Rank A Number out of a set of numbers

I have rows of data from a SELECT query with a few prices (say three for this example). One is our price, one is competitor1 price, one is competitor2 price. I want to add a column that spits out the rank of our price as compared to the other two prices; if our price is the lowest it would spit out the number 1 if the highest it would spit out the number it is out of.
Something like this:
Make | Model | OurPrice | Comp1Price | Comp2Price | Rank | OutOf
MFG1 MODEL1 350 100 500 2 3
MFG1 MODEL2 50 100 100 1 3
MFG2 MODEL1 100 NULL 50 2 2
MFG2 MODEL2 9999 500 NULL 2 2
-Sometimes the competitor price will be NULL as seen above, and I believe this is where my issue lies. I have tried a CASE and it works when only on one competitor but when I add a AND statement it spits out the ranks as all NULL. Is there a better way of doing this through a MySQL query?
SELECT
MT.MAKE as Make,
MT.MODEL as Model,
MT.PRICE as OurPrice,
CT1.PRICE as Comp1Price,
CT2.PRICE as Comp2Price,
CASE
WHEN MT.PRICE < CT1.PRICE AND MT.PRICE < CT2.PRICE
THEN 1 END AS Rank
(CT1.PRICE IS NOT NULL) + (CT2.PRICE IS NOT NULL) + 1 as OutOf
FROM mytable MT
LEFT JOIN competitor1table as CT1 ON CT1.MODEL = MT.MODEL
LEFT JOIN competitor2table as CT2 ON CT2.MODEL = MT.MODEL
ORDER BY CLASS
Not tested, but you can try:
SELECT
a.MAKE AS Make,
a.MODEL AS Model,
a.PRICE AS OurPrice
MAX(CASE WHEN a.compnum = 1 THEN pricelist END) AS Comp1Price,
MAX(CASE WHEN a.compnum = 2 THEN pricelist END) AS Comp2Price,
FIND_IN_SET(a.PRICE, GROUP_CONCAT(a.pricelist ORDER BY a.pricelist)) AS Rank,
COUNT(a.pricelist) AS OutOf
FROM
(
SELECT MAKE, MODEL, PRICE, PRICE AS pricelist, 0 AS compnum
FROM mytable
UNION ALL
SELECT a.MAKE, a.MODEL, a.PRICE, CT1.PRICE, 1
FROM mytable a
LEFT JOIN competitor1table CT1 ON a.MODEL = CT1.MODEL
UNION ALL
SELECT a.MAKE, a.MODEL, a.PRICE, CT2.PRICE, 2
FROM mytable a
LEFT JOIN competitor2table CT2 ON a.MODEL = CT2.MODEL
) a
GROUP BY
a.MAKE, a.MODEL
(CT1.PRICE IS NOT NULL AND CT1.PRICE < MT.PRICE) + (CT2.PRICE IS NOT NULL AND CT2.PRICE < MT.PRICE) + 1 as Rank

Why do I get NULL on this query?

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