Best way to handle NULL in MySQL logical subexpressions - mysql

I am not used to working with NULL and i am facing some problems. For example one would think that this query returns all customers:
SELECT * from customers WHERE ( job LIKE '%expert%' OR job NOT LIKE '%expert%' )
But reality is that it won't return customers with a NULL job.
That's an example. A more real world query could be:
SELECT * from customers WHERE NOT ( job LIKE '%expert%' )
And to work properly it could be rewritten like:
SELECT * from customers WHERE NOT ( job IS NOT NULL AND job LIKE '%expert%' )
What's the best way of dealing with this NULL in logical subexpressions? I am mainly a PHP programmer for small scripts (i'm not used to ORM or other frameworks).

You can get the proper results by using this query
SELECT * from customers WHERE NOT ( job LIKE '%expert%' ) OR job IS NULL

I guess COALESCE() is quite handy in most cases.
My last example could be like:
SELECT * from customers WHERE NOT ( COALESCE(job,'') LIKE '%expert%' )
It replaces job with an empty string if it happened to be NULL.

Related

How to enforce conditions in Common Table Expressions

We have numerous complex functions that return lists based on passed JSON filter settings. The problem is, MySQL appears (using EXPLAIN) to execute the SQL within a SELECT statement even if a flag is set as false. I'm not sure how to get around this. For example:
WITH cte1 AS
(
SELECT pm.ID
FROM person_main pm
),
cte2 AS
(
SELECT IF
(
#p_filter_job_state_array IS NULL,
(
SELECT NULL
),
(
SELECT sis.ID
FROM cte1 sis
INNER JOIN external_link el ON el.ref_id = sis.ID
WHERE el.headline_value LIKE '%test%'
)
) AS ID
)
SELECT * FROM cte2;
Even though #p_filter_job_state_array is NULL, the engine is still executing the SELECT joining external_link. We have dozens of "frontloading" CTEs that do this, and as the number of filters grow, the execution time is doubling and tripling even though we have no filters set (because the engine is executing the SELECT statements when I thought it was ignoring them based on NULL values). I need to figure out how to write a single query using CTEs and instruct the compiler to avoid executing CTE SELECT statements based on variable settings... is this possible, and if so, how can I do it?
Another example of why this is important is because we do a LOT of frontloading and filtering, and sometimes the caller just wants an array of indexes rather than the entire structure, so we have something like this:
SELECT IF
(
p_array_only,
(
SELECT JSON_ARRAYAGG(jm.ID)
FROM
(
SELECT fs.ID
FROM final_sort fs
LIMIT var_offset, var_rowcount
) jm
),
(
SELECT JSON_OBJECT
(
'data',
(
SELECT JSON_ARRAYAGG(JSON_OBJECT
(
'data_main', get_json_data_main_list(jm.ID)
))
FROM
(
SELECT fs.ID
FROM final_sort fs
LIMIT var_offset, var_rowcount
) jm
)
)
)
)
Of course the issue is if they just want the array, MySQL still executes the other portion of the code. Yes, I could break this into two separate SQL statements, but they use 100% of the same "frontloading" of the code, so it would be inefficient. But this is the main problem; we have a very specific set of filters we need to process, but we also need to skip filters and focus on outputting different structures based on variable settings.
Unfortunately, there wasn't any response from the community; Prepared Statements are probably the best solution (and work fine), but as many know, it turns managing complex code into spaghetti, which can be very frustrating. But it's fast.

SQL Query - Select a value, then use it again in following statements

I've tried looking it up, and while I think this should be possible I can't seem to find the answer I need anywhere.
I need to lookup a date from one table, then store it for use in a following query.
Below is statements that should work, with my setting the variable (which I know won't work, but I'm unsure the best way to do/show it otherwise - bar maybe querying it twice inside the if statement.)
I'm then wanting to in the latter statement, use either the date given in the second query, or if the date from the first query (that I'm thinking to set as a variable) is newer, use that instead.
startDateVariable = (SELECT `userID`, `startDate`
FROM `empDetails`
WHERE `userID` = 1);
SELECT `userID`, SUM(`weeksGROSS`) AS yearGROSS
FROM `PAYSLIP`
WHERE `date` <= "2021-11-15"
AND `date` >= IF( "2020-11-15" > startDateVariable , "2020-11-15" , startDateVariable )
AND `userID` IN ( 1 )
GROUP BY `userID`
Naturally all dates given in the query ("2021-11-15" etc) would be inserted dynamically in the prepared statement.
Now while I've set the userID IN to just query 1, it'd be ideal if I can lookup multiple users this way at once, though I can accept that I may need to make an individual query per user doing it this way.
Much appreciated!
So turns I was going about this the wrong way, looks like the best way to do this or something similar is by using SQL JOIN
This allows you to query the tables as if they are one.
I also realised rather then using an IF, i could simply make sure i was looking up newer or equal to both the date given and the start date.
Below is working as required. And allows lookup of multiple users at once as wanted.
SELECT PAYSLIP.userID, employeeDetails.startDate, SUM(PAYSLIP.weeksGROSS) AS yearGROSS
FROM PAYSLIP
INNER JOIN employeeDetails ON employeeDetails.userID=PAYSLIP.userID
WHERE PAYSLIP.date <= "2021-11-15"
AND PAYSLIP.date >= "2020-11-15"
AND PAYSLIP.date >= employeeDetails.startDate
AND PAYSLIP.userID IN ( 1,2,8 )
GROUP BY PAYSLIP.userID
See here for more usage examples: https://www.w3schools.com/sql/sql_join.asp
However along the lines of my particular question, it's possible to store variables. I.E.
SET #myvar= 'Example showing how to declare variable';
Then use it in the SQL statement by using
#myvar where you want the variable to go.

sql injection with union

So I am doing a little sql injection challenge because I wanted to learn about it and I have a question. I type 'hi' into the HTML form and I get this back as a response
Error: The following error occurred: [near "hi": syntax error]
Query: SELECT * FROM personnel WHERE name=''hi''
The information we need to get is located in a table called users. I was looking at sql and I see here the union operator which combines the results of 2+ select statements.
So, I try this as input: 1 UNION SELECT * FROM users and I get nothing back so it looks like it searched from that input as a name in table personnel. I thought this would work because the query would look like: SELECT * FROM personnel WHERE name=1 UNION SELECT * FROM users. Am I not understanding how the union operator works or is something else wrong in my input
This one:
set #hi = "'hi'";
select #hi;
SELECT * FROM personnel WHERE name="'hi'"
Simulate:
insert into personnel (`name`) values("'hi'");
insert into personnel (`name`) values("'hello'");
select * from personnel where `name` != "'hi'"
-- you can't use a double '' in sql query
Query: SELECT * FROM personnel WHERE name='hi'
Probably the SQL is invalid because personnel and users have different shape. You need to inject something that is identical to the initial select.
Also your entire problem goes away if you have parameterised queries instead of concatenating into SQL.

MySQL IF on Where clause

Is it possible to make a query that changes the where clause acording to some condition? For instance I want to select * from table1 where data is 19/July/2016 but if field id is null then do nothing, else compare id to something else. Like the query bellow?
Select * from table1 where date="2016-07-19" if(isnull(id),"",and id=(select * from ...))
Yes. This should be possible.
If we assume that date and id are references to columns in (the unfortunately named) table table1, if I'm understanding what you are attempting to achieve, we could write a query like this:
SELECT t.id
, t.date
, t....
FROM table1 t
WHERE t.date='2016-07-19'
AND ( t.id IS NULL
OR t.id IN ( SELECT expr FROM ... )
)
It would also be possible to incorporate the MySQL IF() and IFNULL() functions, if there's some requirement to do that.
As far as dynamically changing the text of the SQL statement after the statement is submitted to the database, no, that's not possible. Any dynamic changes to the SQL text would need to be done when the SQL statement is generated, before it is submitted to the database.
My personal preference would be to use a join operation rather than a IN (subquery) predicate.
I think you're trying too hard. If id is NULL that's equivalent to having a FALSE in the where clause. So:
Select * from table1 where date="2016-07-19" and id=(select * from ...)
Should only match the records you want. If id is NULL you get nothing.

MySQL Opposite of Condition

I know in other languages you can use something (usually a !) to denote the opposite of whatever follows. Is there a way to do something like this in MySQL?
For example, I have this query to select everything that isn't null or blank:
select * from `invoices` where `Notes`>'';
But is there a way way to do the opposite? Because currently I only know that you can rewrite the query such as this
select * from `invoices` where ifnull(`Notes`,'')='';
But this removes the opportunity for indexes on the Notes column or this way
select * from `invoices` where (`Notes` is null or `Notes`='');
But that one is a lot longer than something like
select * from `invoices` where !`Notes`>'';
which would be ideal.
SQL has a not operator:
SELECT * FROM `invoices` WHERE NOT (`Notes`>'');
-- Here -----------------------^