MySQL - query to return NULL - mysql

I have the following code:
SELECT q25, (
(
AVG( q1 ) + AVG( q2 ) + AVG( q3 ) ) /3 ) AS Overall
FROM t_results
WHERE brand = 'XYZ'
AND DATE = 'MAY2012'
GROUP BY q25
ORDER BY Overall
DESC LIMIT 1
If there is no data found by the query phpmyadmin returns the following message (which is quite correct):
MySQL returned an empty result set (i.e. zero rows). ( Query took 0.0178 sec )
However, what I'd like is to actually return a NULL value, is this possible? I appreciate this might not be best practise but I'm working with inherited code and this might be the simplist and quickest route to a solution.
Thanks as always,
H.

Create a table with exactly one row. Then you can use left join to achieve the desired NULL result.
CREATE TABLE dummy (d TINYINT NOT NULL);
INSERT INTO dummy SET d = 1;
SELECT q25,
( ( AVG( q1 ) + AVG( q2 ) + AVG( q3 ) ) /3 ) AS Overall
FROM dummy LEFT JOIN t_results
ON brand = 'XYZ'
AND DATE = 'MAY2012'
GROUP BY q25
ORDER BY Overall DESC
LIMIT 1
You can also replace the dummy table with a subquery:
SELECT q25,
( ( AVG( q1 ) + AVG( q2 ) + AVG( q3 ) ) /3 ) AS Overall
FROM (SELECT 1) AS dummy LEFT JOIN t_results
ON brand = 'XYZ'
AND DATE = 'MAY2012'
GROUP BY q25
ORDER BY Overall DESC
LIMIT 1
Tested this via sqlfiddle, where you can also experiment with alternatives.
The conditions selecting the result, which used to be in the WHERE clause, now have to go into the ON clause. Otherwise the left join would produce non-NULL rows which would be removed by the WHERE, instead of generating a single NULL row if no matching row could be found. If there were no WHERE conditions in the original query, ON 1 could be used to express any row matches.

You can use a UNION combined with a LIMIT to supply the NULL values:
(SELECT q25,
(AVG(q1) + AVG(q2) + AVG(q3))/3 AS Overall
FROM t_results
WHERE brand = 'XYZ'
AND DATE = 'MAY2012'
GROUP BY q25
ORDER BY Overall DESC
LIMIT 1
)
UNION ALL
(SELECT NULL, NULL)
LIMIT 1;
This only works when you know that the first query will never yield more than one result, though. Which is the case here, so this might be the best solution for you, but the approach given in my other answer is more general.
There is a fiddle for this to experiment with.

The coalesce() function can be used to return the first non-null value from a number of comma separated columns or strings. The values/columns are evaluated left to right, so if you want to pop a string into the arguments that isn't null, make sure you place it to the right of the columns that you are testing against.
select
coalesce(
(
SELECT
q25
FROM
t_results
WHERE
brand = 'XYZ'
AND DATE = 'MAY2012'
GROUP BY
q25
LIMIT 1
), 'null') as q25,
coalesce(
(
SELECT
((AVG( q1 ) + AVG( q2 ) + AVG( q3 ) ) /3 ) AS Overall
FROM t_results
WHERE
brand = 'XYZ'
AND DATE = 'MAY2012'
LIMIT 1
), 'null') as Overall
from
t_results
group by
1, 2;
If you don't have data that matches your where clause, this will return null, null as a row.

Related

Sql query with three conditions

I have a database with a table having content as below :
message_number message_type message_chat
0 IN Hi
1 OB Hello
2 IN Help
3 IN Want to find this thing
4 OB Sure
5 OB Please let me know
I have written 5 rows since i want to incorporate all possible cases that i want in my query in the example table that i showed.
Now in my query output, i want something like :
message_in message_out
Hi Hello
Help NULL
Want to find this string Sure
NULL Please let me know
So the cases that i want to consider are :
suppose if message_number=0 and message_number=1 both have message_type value as IN then put message_chat_in as message_chat(at message_number=0) and message_chat out as NULL and the iterate over message_number=1
if message_number =0 have message_type=IN and message_number =1 have message_type=OB, then show message_chat(at message_number=0) as message_chat_in and message_chat(at message_number=1) as message_out and dont iterate over message_number=1;
hope i have clarified the condition though i have included all three condition in the expected output.How should my sqlquery look like?
Edit : I am using mysql version 5.5.8
Try the following query
SELECT
q1.message_number in_num,
q1.message_chat in_chat,
q2.message_number out_num,
q2.message_chat out_chat
FROM
(
SELECT *,#i1:=IFNULL(#i1,0)+1 num
FROM Chat
ORDER BY message_number
) q1
LEFT JOIN
(
SELECT *,#i2:=IFNULL(#i2,0)+1 num
FROM Chat
ORDER BY message_number
) q2
ON q2.num=q1.num+1 AND q2.message_type<>q1.message_type
WHERE q1.message_type='IN'
UNION ALL
SELECT
q1.message_number in_num,
q1.message_chat in_chat,
q2.message_number out_num,
q2.message_chat out_chat
FROM
(
SELECT *,#i3:=IFNULL(#i3,0)+1 num
FROM Chat
ORDER BY message_number
) q1
RIGHT JOIN
(
SELECT *,#i4:=IFNULL(#i4,0)+1 num
FROM Chat
ORDER BY message_number
) q2
ON q2.num=q1.num+1 AND q2.message_type<>q1.message_type
WHERE q2.message_type='OB'
AND q1.message_type IS NULL
ORDER BY IFNULL(in_num,out_num)
SQL Fiddle - http://sqlfiddle.com/#!9/95a515/1
The second variant
SET #i1 = 0;
SET #i2 = 0;
SET #i3 = 0;
SET #i4 = 0;
-- the same query
SQL Fiddle - http://sqlfiddle.com/#!9/95a515/2
Or
SELECT 0,0,0,0 INTO #i1,#i2,#i3,#i4;
-- the same query
SQL Fiddle - http://sqlfiddle.com/#!9/95a515/5
why not using a analytic function here? I would do it with Lead() like this:
with inc as (
--Do the incorporation in this block. could be subquery too
--but its easier to read this way.
select
case when message_type = 'IN'
then message_chat
end as message_in
,case when LEAD(message_type) OVER (Order by message_number) = 'OB' --get the next message by number if it is type OB
then LEAD(message_chat) OVER (order by message_number)
end as message_out
from input
)
select *
from inc
where coalesce(message_in, message_out) is not null --filter out rows where with in & out is null
Ok, since there is no analytical functions in MySQL less than 8 the code may not be easy to follow:
with data_rn as
(
-- this isolate consecutive rows with the same message_type
select d1.*, count(d2.message_number) rn
from data d1
left join data d2 on d1.message_number > d2.message_number and d1.message_type != d2.message_type
group by d1.message_number
),
data_rn2 as
(
-- this marks the rows where new rows has to be added (i.e. when rn2 != 0)
select d1.*, count(d2.message_number) rn2
from data_rn d1
left join data_rn d2 on d1.rn = d2.rn and d1.message_type = d2.message_type and d1.message_number > d2.message_number
group by d1.message_number
),
data_added as
(
-- this add new rows
select message_number, message_type, message_chat
from data_rn2
union all
select message_number - 0.5, 'OB', NULL from data_rn2 where message_type = 'IN' and rn2 != 0
union all
select message_number - 0.5, 'IN', NULL from data_rn2 where message_type = 'OB' and rn2 != 0
order by message_number
), data_added_rn as
(
-- this compute new row numbering
select d1.*, ceil((count(d2.message_number)+1)/2) rn
from data_added d1
left join data_added d2 on d1.message_number > d2.message_number
group by d1.message_number
)
-- this will do the final formating
select max(case when message_type = 'IN' then message_chat end) message_in,
max(case when message_type = 'OB' then message_chat end) message_out
from data_added_rn
group by rn
demo
I have tried to comment each section appropriately.

MySQL Query to average 3 columns and exclude 0's?

This is obviously wrong, but what would be the correct way to average the SUM of 3 columns and exclude the 0's?
SELECT (
AVG(NULLIF(`dices`.`Die1`,0)) +
AVG(NULLIF(`dices`.`Die2`,0)) +
AVG(NULLIF(`dices`.`Die3`,0))
) /3 as avgAllDice
FROM (
SELECT `Die1`,`Die2`,`Die3` FROM `GameLog`
WHERE PlayerId = "12345"
) dices
Thanks.
If I was keeping the inline view query (it's not clear why it's needed). I'd probably do something like this:
SELECT AVG( NULLIF( CASE d.i
WHEN 1 THEN dices.`Die1`
WHEN 2 THEN dices.`Die2`
WHEN 3 THEN dices.`Die3`
END
,0)
) AS `avgAllDice`
FROM ( SELECT gl.`Die1`
, gl.`Die2`
, gl.`Die3`
FROM `GameLog` gl
WHERE gl.playerId = '12345'
) dices
CROSS
JOIN ( SELECT 1 AS i UNION ALL SELECT 2 UNION ALL SELECT 3 ) d
The trick is the cross join operation, giving me three rows for each row returned from dices, and an expression that picks out values of Die1, Die2 and Die3 on each of three rows, respectively.
To exclude values of 0, we replace 0 with with NULL (since AVG doesn't include NULL values.)
Now with all of the non-zero DieN values stacked into a single column, we can just use the AVG function.
Another way to do it would be to get the numerator and denominator for each of Die1, Die2, Die3.... and then total up the numerators, total up the denominators, and then divide the total numerator by the total denominator.
This will should give an equivalent result.
SELECT ( IFNULL(t.n_die1,0) + IFNULL(t.n_die2,0) + IFNULL(t.n_die3,0) )
/ ( t.d_die1 + t.d_die2 + t.d_die3 )
AS avgAllDice
FROM ( SELECT SUM( NULLIF(gl.die1,0)) AS n_die1
, COUNT(NULLIF(gl.die1,0)) AS d_die1
, SUM( NULLIF(gl.die2,0)) AS n_die2
, COUNT(NULLIF(gl.die2,0)) AS d_die2
, SUM( NULLIF(gl.die3,0)) AS n_die3
, COUNT(NULLIF(gl.die3,0)) AS d_die3
FROM `GameLog` gl
WHERE gl.playerid = '12345'
) t
(I didn't work out what gets returned in the edge and corner cases... no matching rows in GameLog, all values of Die1, Die2 and Die3 are zero, etc., for either query. The results might be slightly different, returning a zero instead of NULL, divide by zero edge case, etc.)
FOLLOWUP
I ran a quick test of both queries.
CREATE DATABASE d20170228 ;
USE d20170228 ;
CREATE TABLE GameLog
( playerid VARCHAR(5) DEFAULT '12345'
, die1 TINYINT
, die2 TINYINT
, die3 TINYINT
);
INSERT INTO GameLog (die1,die2,die3)
VALUES (3,0,0),(2,1,0),(4,3,3),(3,3,3),(0,0,0),(4,4,4),(5,4,0),(0,0,2)
;
SELECT (3+2+1+4+3+3+3+3+3+4+4+4+5+4+2)/15 AS manual_avg
manual_avg is coming out 3.2.
Both queries are also returning 3.2
If you want to eliminate zeroes and NULLs, you can simply SELECT from the filtered master set multiple times, doing a UNION ALL on the results, then averaging against that.
SELECT AVG(`allDice`.`DieResult`)
FROM (
SELECT `Die1` AS `DieResult` FROM `GameLog` WHERE COALESCE(`Die1`, 0) <> 0 AND PlayerId = '12345'
UNION ALL
SELECT `Die2` FROM `GameLog` WHERE COALESCE(`Die2`, 0) <> 0 AND PlayerId = '12345'
UNION ALL
SELECT `Die3` FROM `GameLog` WHERE COALESCE(`Die3`, 0) <> 0 AND PlayerId = '12345'
) AS `allDice`
There's no need to overthink this one, it's not too difficult a problem

MySql sort within sorted results set

I have the following query which queries a table of sports results for the last 20 matches that involved a teams, returning goals conceeded in each of these matches.
SELECT *, `against` AS `goalsF` , `for` AS `goalsA`
FROM `matches` , `teams` , `outcomes`
WHERE (
`home_team_id`=7 AND `matches`.away_team_id = `teams`.team_id
OR
`away_team_id`=7 AND `matches`.home_team_id = `teams`.team_id
)
AND `matches`.score_id = `outcomes`.outcome_id
ORDER BY `against', `date` DESC
LIMIT 0 , 20
I want sort the results by goals conceeded and then within each group of goals conceeded by date so for example.
the first 4 results where goals conceded=1 in date order
then the next 3 might be results where conceded=2 in date order
I have tried ORDER by date,against - this gives me a strict date order
I have tried ORDER by against,date - this gives me matches beyond the last 20
Is it possible to do what I want to do?
Thanks everyone, I found this worked. This solution was posted by another user but then was removed, not sure why?
SELECT * FROM (
SELECT *, `against` AS `goalsF` , `for` AS `goalsA`
FROM `matches` , `teams` , `outcomes`
WHERE (
`home_team_id`=7 AND `matches`.away_team_id = `teams`.team_id
OR
`away_team_id`=7 AND `matches`.home_team_id = `teams`.team_id
)
AND `matches`.score_id = `outcomes`.outcome_id
ORDER by `goalsF`
LIMIT 0 , 20
) res
ORDER BY `date` DESC
If you want to limit by date, add the date range you are looking for into your WHERE clause and then order by the number of goals conceded.

MySQL Join Two Queries Horizontally

I have a query that works correctly to pull a series of targets and total hours worked for company A. I would like to run the exact same query for company B and join them on a common date, which happens to be grouped by week. My current query:
SELECT * FROM (
SELECT org, date,
( SELECT SUM( target ) FROM target WHERE org = "companyA" ) AS companyA_target,
SUM( hours ) AS companyA_actual
FROM time_management_system
WHERE org = "companyA"
GROUP BY WEEK( date )
ORDER BY DATE
) q1
LEFT JOIN (
SELECT org, date,
( SELECT SUM( target ) FROM target WHERE org = "companyB" ) AS companyB_target,
SUM( hours ) AS companyB_actual
FROM time_management_system
WHERE org = "companyB"
GROUP BY WEEK( date )
ORDER BY DATE
) q2
ON q1.date = q2.date
The results show all of the dates / information of companyA, however companyB only shows sporadic data. Separately, the two queries will show the exact same set of dates, just with different information in the 'target' and 'actual' columns.
companyA 2012-01-28 105.00 39.00 NULL NULL NULL NULL
companyA 2012-02-05 105.00 15.00 NULL NULL NULL NULL
companyA 2012-02-13 105.00 60.50 companyB 2012-02-13 97.50 117.50
Any idea why I'm not getting all the information for companyB?
As a side note, would anybody be able to point in the direction of converting each row's week value into a column? With companyA and companyB as the only two rows?
I appreciate all the help! Thanks.
WITH no date apparent in the target table, the summation will be constant across all weeks. So, I have performed a pre-query for only those "org" values of company A and B with a group by. This will ensure only 1 record per "org" so you don't get a Cartesian result.
Then, I am querying the time_management_system ONCE for BOTH companies. Within the field computations, I am applying an IF() to test the company value and apply when correct. The WEEK activity is the same for both in the final result, so I don't have to do separately and join. This also prevents the need of having the date column appear twice. I also don't need to explicitly add the org column names as the final column names reflect that.
SELECT
WEEK( tms.date ) as GrpWeek,
IF( tms.org = "companyA", TargetSum.CompTarget, 00000.00 )) as CompanyATarget,
SUM( IF( tms.org = "companyA", tms.hours, 0000.00 )) as CompanyAHours,
IF( tms.org = "companyB", TargetSum.CompTarget, 00000.00 )) as CompanyBTarget,
SUM( IF( tms.org = "companyB", tms.hours, 000.00 )) as CompanyBHours
from
Time_Management_System tms
JOIN ( select
t.org,
SUM( t.target ) as CompTarget
from
Target T
where
t.org in ( "companyA", "companyB" )
group by
t.org ) as TargetSums
ON tms.org = TargetSums.org
where
tms.org in ( "companyA", "companyB" )
group by
WEEK( tms.date )
order by
WEEK( tms.date )
Both of your subqueries are wrong.
Either you want this:
SELECT
org,
WEEK(date),
( SELECT SUM( target ) FROM target WHERE org = "companyB" ) AS companyB_target,
SUM( hours ) AS companyB_actual
FROM time_management_system
WHERE org = "companyB"
GROUP BY WEEK( date )
Or else you want this:
SELECT
org,
date,
( SELECT SUM( target ) FROM target WHERE org = "companyB" ) AS companyB_target,
SUM( hours ) AS companyB_actual
FROM time_management_system
WHERE org = "companyB"
GROUP BY date
The way you are doing it now is not correctly formed SQL. In pretty much any other database your query would fail immediately with an error. MySQL is more lax and runs the query but gives indeterminate results.
GROUP BY and HAVING with Hidden Columns

MySQL Complex Inner Join

Suppose equity has a column called TickerID. I would like to replace the 111's with equity.TickerID. MySQL can't seem to resolve the scope and returns an unknown column when I try that. This SQL statement works but I need to run it for each ticker. Would be nice if I could get a full table.
SELECT Ticker,
IF(tbl_m200.MA200_Count = 200,tbl_m200.MA200,-1) AS MA200,
IF(tbl_m50.MA50_Count = 50,tbl_m50.MA50,-1) AS MA50,
IF(tbl_m20.MA20_Count = 20,tbl_m20.MA20,-1) AS MA20
FROM equity
INNER JOIN
(SELECT TickerID,AVG(Y.Close) AS MA200,COUNT(Y.Close) AS MA200_Count FROM
(
SELECT Close,TickerID FROM equity_pricehistory_daily
WHERE TickerID = 111
ORDER BY Timestamp DESC LIMIT 0,200
) AS Y
) AS tbl_m200
USING(TickerID)
INNER JOIN
(SELECT TickerID,AVG(Y.Close) AS MA50,COUNT(Y.Close) AS MA50_Count FROM
(
SELECT Close,TickerID FROM equity_pricehistory_daily
WHERE TickerID = 111
ORDER BY Timestamp DESC LIMIT 50
) AS Y
) AS tbl_m50
USING(TickerID)
INNER JOIN
(SELECT TickerID,AVG(Y.Close) AS MA20,COUNT(Y.Close) AS MA20_Count FROM
(
SELECT Close,TickerID FROM equity_pricehistory_daily
WHERE TickerID = 111
ORDER BY Timestamp DESC LIMIT 0,20
) AS Y
) AS tbl_m20
USING(TickerID)
This seems to be some bug or "feature" of MySQL. Many persons seems to have the same problem with outer tables being out of scope.
Anyway... You could create functions that retrieve the information you want:
DROP FUNCTION IF EXISTS AveragePriceHistory_20;
CREATE FUNCTION AveragePriceHistory_20(MyTickerID INT)
RETURNS DECIMAL(9,2) DETERMINISTIC
RETURN (
SELECT AVG(Y.Close)
FROM (
SELECT Z.Close
FROM equity_pricehistory_daily Z
WHERE Z.TickerID = MyTickerID
ORDER BY Timestamp DESC
LIMIT 20
) Y
HAVING COUNT(*) = 20
);
SELECT
E.TickerID,
E.Ticker,
AveragePriceHistory_20(E.TickerID) AS MA20
FROM equity E;
You would get NULL instead of -1. If this is undesirable, you could wrap the function-call with IFNULL(...,-1).
Another way of solving this, would be to select for the time-frame, instead of using LIMIT.
SELECT
E.TickerID,
E.Ticker,
(
SELECT AVG(Y.Close)
FROM equity_pricehistory_daily Y
WHERE Y.TickerID = E.TickerID
AND Y.Timestamp > ADDDATE(CURRENT_TIMESTAMP, INTERVAL -20 DAY)
) AS MA20
FROM equity E;