Simplify and correct following query - mysql

How do I fix, and possibly simplify I guess, this SQL query, please?
SELECT * FROM table WHERE
M IN (NULL,1) AND T IN (NULL,1) AND J IN (NULL,1) AND B IN (NULL,1)
AND (ISNULL(M,0)+ISNULL(T,0)+ISNULL(J,0)+ISNULL(B,0))<4
ORDER BY (ISNULL(M,0)+ISNULL(T,0)+ISNULL(J,0)+ISNULL(B,0)) DESC
table contains 4 columns (M,T,J,B) with 3 possibles values only NULL, 0, 1.
First-line filters to get only entries containing NULL or 1.
Second-line makes an addition to get the total of M+J+T+B and filters only if <4.
Third-line ORDER BY the same Total, M+J+T+B.
error #1582 - Incorrect parameter count in the call to native function 'ISNULL'
MySQL equivalent to ISNULL is IFNULL... answered here

select * from (
select *, coalesce(M,0) M2, coalesce(T,0) T2, coalesce(J,0) J2, coalesce(B,0) B2
from table
where
(M is null or M=1) and
(T is null or T=1) and
(J is null or J=1) and
(B is null or B=1)
)x
where M2+T2+J2+B2 < 4
order by M2+T2+J2+B2 desc

Nulls act strangely in SQL. Any comparison test with a null will result in FALSE, so if you use the phrase Null=Null in a where clause, nothing will be retrieved because the result is always false. I would rewrite your second line to use IsNull functions instead,
IsNull(M,1)*IsNull(T,1)*IsNull(J,1)*IsNull(B,1)>0

Select
t.*,
coalesce(M,0) + coalesce(T,0) + coalesce(J,0) + coalesce(B,0) as calc
from table t
where
coalesce(M,0) in (0,1) and
coalesce(T,0) in (0,1) and
coalesce(J,0) in (0,1) and
coalesce(B,0) in (0,1) and
calc < 4
order by calc desc

Related

Check for all null values in a GROUP BY [duplicate]

This question already has answers here:
MySQL AVG() return 0 if NULL
(3 answers)
Closed last month.
I have the following structre
id val
1 ...
.
.
2 ...
.
.
3 null
3 null
3 null
4 ...
.
.
Basically each id has multiple no. of values. And an id has either all values as integers or all values as null
What I want is to perform an aggregate (like AVG) on val group by id. If that id has null values, I want to put 5 there.
#1
SELECT id, (CASE SUM(val) WHEN null THEN 5 ELSE AVG(val) END) AS ac FROM tt GROUP BY id
> executes ELSE even for id = 3
In CASE, there should be an aggregate function that when done on null values give null.
I checked SUM and MAX like
SELECT SUM(val) FROM tt WHERE id = 3
> null
and it gives null here but doesn't work in main statement. I guess it is related to the type of equality and hence tried WHEN IS NULL but its a syntax error.
Also, is there some more standard way of indicating group of values as all null rather than using SUM or MAX.
You can use if condition :
select id, If(sum(val) is null, 5, AVG(val)) as average
FROM tt
group by id
check here : https://dbfiddle.uk/Uso9nNTM
The exact problem with your CASE expression is that to check for null in MySQL we have to use IS NULL rather than equality. So use this version:
CASE WHEN SUM(val) IS NULL THEN 5 ELSE AVG(val) END
But we might as well just use COALESCE() to assign an average of 5 for those id groups having all null values.
SELECT id, COALESCE(AVG(val), 5) AS avg_val
FROM tt
GROUP BY id;
Note that the AVG() function by default ignores nulls. Therefore, the expression AVG(val) would only be null if every record in an id group were having null for val.

UNION in MySQL 5.7.2

I'm using MySQL 5.7.
I am getting bad results by a UNION of COUNT(*).
SELECT
COUNT(*) AS Piezas
, ''Motor
from parque
where `parque`.`CausasParalizacion` = 2
UNION
SELECT
''Piezas
, COUNT(*) AS Motor
from parque
where `parque`.`CausasParalizacion` = 3
The result should be 30 and 12, and I am getting 3330 and 3132.
Can anyone help?
I don't think MySQL is returning a "bad" result. The results returned by MySQL are per the specification.
Given no GROUP BY each of the SELECT statements will return one row. We can verify by running each SELECT statement separately. We'd expect the UNION result of the two SELECT to be something like
Piezas Motor
------ -----
mmm
ppp
You say the results should be '30' and '12'
My guess is that MySQL is returning the characters '30' and '12'.
But we should be very suspicious, and note the hex representation of the ASCII encoding of those characters
x'30' -> '0'
x'31' -> '1'
x'32' -> '2'
x'33' -> '3'
As a demonstration
SELECT HEX('30'), HEX('12')
returns
HEX('30') HEX('12')
--------- ---------
3330 3132
I don't think MySQL is returning "bad" results. I suspect that the column metadata for the columns is confusing the client. (We do note that both of the columns is a mix of two different datatypes being UNION'd. On one row, the datatype is string/varchar (an empty string), and the other row is integer/numeric (result of COUNT() aggregate.)
And I'm not sure what the resultset metadata for the columns ends up as.
I suspect that the issue with the client interpretation the resultset metadata, determining the datatype of the columns. And the client is deciding that the most appropriate way to display the values is as a hex representation of the raw bytes.
Personally, I would avoid returning a UNION result of different/incompatible datatypes. I'd prefer the datatypes be consistent.
If I had to do the UNION of incompatible datatypes, I would include an explicit conversion into compatible/appropriate datatypes.
But once I am at that point, I have to question why I need any of that rigmarole with the mismatched datatypes, why we need to return two separate rows, when we could just return a single row (probably more efficiently to boot)
SELECT SUM( p.`CausasParalizacion` = 2 ) AS Piezas
, SUM( p.`CausasParalizacion` = 3 ) AS Motor
FROM parque p
WHERE p.`CausasParalizacion` IN (2,3)
To avoid the aggregate functions returning NULL,
we can wrap the aggregate expressions in an IFNULL (or ANSI-standard COALESCE) function..
SELECT IFNULL(SUM( p.`CausasParalizacion` = 2 ),0) AS Piezas
, IFNULL(SUM( p.`CausasParalizacion` = 3 ),0) AS Motor
FROM parque p
WHERE p.`CausasParalizacion` IN (2,3)
-or-
we could use a COUNT() of an expression that is either NULL or non-NULL
SELECT COUNT(IF( p.`CausasParalizacion` = 2 ,1,NULL) AS Piezas
, COUNT(IF( p.`CausasParalizacion` = 3 ,1,NULL) AS Motor
FROM parque p
WHERE p.`CausasParalizacion` IN (2,3)
If, for some reason it turns out it is faster to run two separate SELECT statements, we could still combine the results into a single row. For example:
SELECT s.Piezas
, t.Motor
FROM ( SELECT COUNT(*) AS Piezas
FROM parque p
WHERE p.`CausasParalizacion` = 2
) s
CROSS
JOIN ( SELECT COUNT(*) AS Motor
FROM parque q
WHERE q.`CausasParalizacion` = 3
) t
Spencer, I think that the problem was about encoding. Ej. When I execute the consult in console, the result was the expected, the otherwise in the phpmyadmin.
However, I must say that your first solution works perfectly, Thanks a lot bro.

mySQL calculation giving incorrect data

I have written the below query and get good data,
What is the correct way to then have the gross hire less the discount show as net hire?? the script I wrote gives me a null value for 1 branch and I know this is incorrect?
SELECT branch_name, gross_hire_$, discount_$
FROM m_rental R, m_vehicle V, m_branch B
WHERE r.rego_no = v.rego_no
AND v.branch_code = b.branch_code
GROUP BY b.branch_code
ORDER BY branch_name
Assuming that your query won't throw ambiguous column error, this can work out for you. If you know that the discount can be null, use ifnull() while doing aggregates. If you are doing sums, then you can use sum(ifnull(discount,0)) but if you are doing averages and you want averages for non-zero records only then you'll have to do something like this:
sum(case when discount is not null then discount else 0 end)/
sum(case when discount is not null then 1 else 0 end)
But in the current scenario, the following should work out fine.
select branch_name, sum(ifnull(gross_hire,0)) as gross_hire,
sum(ifnull(discount,0)) as discount from m_rental r,
m_vehicle v, m_branch b where r.rego_no = v.rego_no
and v.brand_code = b.branch_code group by b.branch_code order by branch_name;
You can perform arithmetic in the column list of a SELECT, and anytime you have a column which might be null (and where you can replace null with some non-null value) you can use the SQL COALESCE function.
SELECT branch_name, gross_hire_$, discount_$, (gross_hire_$ - COALESCE(discount_$, 0)) AS net_hire_$
FROM m_rental R, m_vehicle V, m_branch B
WHERE r.rego_no = v.rego_no
AND v.branch_code = b.branch_code
GROUP BY b.branch_code
ORDER BY branch_name
should serve your purpose. The call COALESCE(discount_$, 0) returns the value of discount_$ unless it is null, in which case it returns 0.
Note that I have not attempted to sum any columns, but the principle still holds.

SQL: Sum returns null

So i have two tables academy_attempt & module_attempt
I am attempting to add two values from each of these tables together:
round(((select
sum(`academy_attempt`.`score`)
from
`academy_attempt`
where
((`academy_attempt`.`module_type_id` in (3 , 4, 5, 6))
and (`academy_attempt`.`user_id` = `U`.`id`))) + (select
sum(ifnull(`module_attempt`.`score`, 0))
from
`module_attempt`
where
((`module_attempt`.`module_type_id` in (3 , 4, 5, 6))
and (`module_attempt`.`user_id` = `U`.`id`)))),
2) AS `total_score`
in academy_attempt the where statement is met and in one row it returns the right amount (if it is alone) however module_attempt does not have any values that matches the where statement and therefor returns null.
Sadly this does not turn into 0 and since im guessing you can't do the operation: 17 + null = 17 it instead returns null.
To counter this i have attempt an IFNULL statement as you can see above but sadly this did not fix the problem
You have to apply the IFNULL() higher up, because an empty result set is considered to be null:
SELECT (...
) + IFNULL((SELECT SUM(`module_attempt`.`score`) ...), 0) AS total_score
NULL represents an unknown value, so naturally trying to add an unknown value to a number still results in an unknown value, albeit a different unknown value (hence NULL != NULL)
I think you actually want the COALESCE function, which returns the first non-null argument. Thus, you can wrap your null value with this function, and sum it as normal. COALESCE( NULL, 0 ) will return 0, and COALESCE(1,0) will return 1
https://dev.mysql.com/doc/refman/5.0/en/comparison-operators.html#function_coalesce

Get the earliest time if rows are more than 1

I need help with some MySQL pain in the ****... Anyway, i got the following sql :
SELECT id,count(*),
CASE
WHEN count(*) > 1 THEN
// I need the minimal `taskdate_time` column from the selected rows
// where a certain boolean is active
ELSE taskdate_time
END
FROM timehistory th
WHERE `send`=true
GROUP BY date_format(taskdate_time, "%Y-%m-%d"), user_id
As described in the comments, i need to get the earliest time out for the two rows where a column called removed is not FALSE
How do i achieve this?
My columns are :
`id` - int
`taskdateuser_id` int
`user_id` int
`changed_by` int
`batch_id` int
`taskdate_time` timestamp
`send` tinyint
`isread` tinyint
`update` tinyint
`removed` tinyint
Many thanks in advance!!!
EDIT:
I might explain it a bit more. If i got the following table rows :
The red marked rows are captured by the CASE count(*) > 1, because there are 2 rows returned by the group by. Then i need to to a SELECT from that 2 captured rows where removed=false and min(taskdate_time). So if 4 rows are returned for that group by, and 2 of the rows are removed=false and the other are removed=true then i need to do a subselect for the minimum taskdate_time that 2 rows where removed=false.
SELECT id,
count(*),
CASE WHEN count(*) > 1
THEN (SELECT MAX(taskdate_time) FROM timehistory f WHERE f.id = th.id AND removed = 0)
ELSE taskdate_time
END
FROM timehistory th
WHERE `send` = true
GROUP BY date_format(taskdate_time, "%Y-%m-%d"), user_id
You could try something like this:
SELECT TH.user_id, COUNT(*),
CASE WHEN COUNT(*) > 1
THEN MIN(IF(TH.removed, TH.taskdate_time, NULL))
ELSE TH.taskdate_time
END
FROM TimeHistory TH
...
Sample Fiddle Demo
However, if COUNT > 1 AND there aren't any records where TH.removed is true, then this will return NULL for that value. What should it return in those cases?
--EDIT--
In response to comments, then this should work just wrapping it with COALESCE:
COALESCE(
CASE
WHEN COUNT(*) > 1
THEN MIN(IF(TH.removed, TH.taskdate_time, NULL))
ELSE TH.taskdate_time
END, MIN(TH.taskdate_time))