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.
Related
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.
i tried lots of thing but not of them worked hope someone may help me with this query
let me show my query first then issue
select log.*,client.client_name
from ( select * from sessions
where ( `report_error_status` like CONCAT('%' ,'consec', '%')
or `ipaddress` like CONCAT('%' ,'consec', '%') or `last_updated` like CONCAT('%' ,'consec', '%') )
ORDER BY `id` DESC LIMIT 10 OFFSET 0 )
log
inner join
(select * from clients
where ( `client_name` like CONCAT('%' ,'consec', '%') ) )
client on log.client_id = client.id
in order to prevent exponential reducing query speed i'm applying limit in my table session above query working perfectly fine without "where", but my problem lies over here if user from front end try to search any thing in datatable , where clause is dynamically get attached in backend (above query with where) now my problem is that suppose table (session) does not contain user search value consec ,but table (client) contain then final query still return null value now is there any way to apply conditional where like below query
ifnull((select id from sessions where
(`report_error_status` like CONCAT('%' ,'consec', '%')
or `ipaddress` like CONCAT('%' ,'consec', '%')
or `last_updated` like CONCAT('%' ,'consec', '%'))
),
(select * from sessions ORDER BY `id` DESC LIMIT 10 OFFSET 0) ))
it will resolve all my problem is there any way to achieve in mysql.
if table session contain 100 000 data it will search with client table one by one against 100k records. suppose time taken to execute is 1 sec now what if my session table has 200k data again time will increase exponentially in inner join, to avoid this i'm using subquery in session with limit
Note report_error_status,ipaddress, client_name etc in index
There is no way to optimize a MySQL SELECT statement that use a regex opening with the wildcard. Your REGEX is %consec%, and you could add an index, but to quote the official MySQL documentation...
The index also can be used for LIKE comparisons if the argument to LIKE is a constant string that does not start with a wildcard character. For example, the following SELECT statements use indexes:
SELECT * FROM tbl_name WHERE key_col LIKE 'Patrick%';
SELECT * FROM tbl_name WHERE key_col LIKE 'Pat%_ck%';
Source: Dev.MySQL.com: Comparison of B-Tree and Hash Indexes; B-Tree Index Characteristics
Your query falls outside of this use case, so indices will not help. Here's another answer suggesting the same.
I am going to suggest Database Normalization...
You're selecting fields that are LIKE %consec%. Why? What is this value? Is it a special, internal code that means something special for your software and your software alone? After all, look the names of the fields -- report_error_status, ipaddress, last_updated. Except for maybe the error code one, there's no reason "consec" would appear in these, unless it had some internal significance.
For instance, table.field has value of "userconsec", sometimes you want to search for "user", other times "consec".
In that case, you'd want a new table; "tableType", with tableType.tableid pointing to the other table and tableType.Type being the Type value ("user", "consec", etc.), an index on both tableid and Type, and then you can drop from your query WHERE LIKE ... and add instead JOIN ON tableType.tableid = table.id AND tableType.Type = "consec";.
It will be faster because...
It is not looking through all the text of several text fields.
It is looking through an ordered list of integers to identify the record you need.
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.
I cannot create a virtual table for this. Basically what I have, is a list of values:
'Succinylcholine','Thiamine','Trandate','Tridol Drip'
I want to know which of those values is not present in table1 and display them. Is this possible? I have tried using left joins and creating a variable with the list which I can compare to the table, but it returns the wrong results.
This is one of the things I have tried:
SET #list="'Amiodarone','Ammonia Inhalents','Aspirin';
SELECT #list FROM table1 where #list not in (
SELECT Description
FROM table1
);
With only narrow exceptions, you need to have data in table form to be able to obtain those data in your result set. This is the essential problem that all attempts at a solution to this problem run into, given that you cannot create a temporary table. If indeed you can provide the input in any form or format (per your comment), then you can provide it in the form of a subquery:
(
SELECT 'Amiodarone' AS description
UNION ALL
SELECT 'Ammonia Inhalents'
UNION ALL
SELECT 'Aspirin'
)
(Note that that exercises the biggest of the exceptions I noted: you can select scalars directly, without a base table. If you like, you can express that explicitly -- in MySQL and Oracle, at least -- by selecting FROM DUAL.)
In that case, this should work for you:
SELECT
a.description
FROM
(
SELECT 'Amiodarone' AS description
UNION ALL
SELECT 'Ammonia Inhalents'
UNION ALL
SELECT 'Aspirin'
) a
LEFT JOIN table1
ON a.description = table1.description
WHERE table1.description IS NULL
That won't work. the variable's contents will be treated as a monolithic string - one solid block of letters, not 3 separate comma-separated values. The query will be parsed/executed as:
SELECT ... WHERE "'Amio.....rin'" IN (x,y,z,...)
^--------------^--- string
Plus, since you're just doing a sub-select on the very same table, there's no point in this kind of a construct. You could try mysql find_in_set() function:
SELECT #list
FROM table1
WHERE FIND_IN_SET(Description, #list) <> ''
i'm preparing a presentation about one of our apps and was asking myself the following question: "based on the data stored in our database, how much growth have happend over the last couple of years?"
so i'd like to basically show in one output/graph, how much data we're storing since beginning of the project.
my current query looks like this:
SELECT DATE_FORMAT(created,'%y-%m') AS label, COUNT(id) FROM table GROUP BY label ORDER BY label;
the example output would be:
11-03: 5
11-04: 200
11-05: 300
unfortunately, this query is missing the accumulation. i would like to receive the following result:
11-03: 5
11-04: 205 (200 + 5)
11-05: 505 (200 + 5 + 300)
is there any way to solve this problem in mysql without the need of having to call the query in a php-loop?
Yes, there's a way to do that. One approach uses MySQL user-defined variables (and behavior that is not guaranteed)
SELECT s.label
, s.cnt
, #tot := #tot + s.cnt AS running_subtotal
FROM ( SELECT DATE_FORMAT(t.created,'%y-%m') AS `label`
, COUNT(t.id) AS cnt
FROM articles t
GROUP BY `label`
ORDER BY `label`
) s
CROSS
JOIN ( SELECT #tot := 0 ) i
Let's unpack that a bit.
The inline view aliased as s returns the same resultset as your original query.
The inline view aliased as i returns a single row. We don't really care what it returns (except that we need it to return exactly one row because of the JOIN operation); what we care about is the side effect, a value of zero gets assigned to the #tot user variable.
Since MySQL materializes the inline view as a derived table, before the outer query runs, that variable gets initialized before the outer query runs.
For each row processed by the outer query, the value of cnt is added to #tot.
The return of s.cnt in the SELECT list is entirely optional, it's just there as a demonstration.
N.B. The MySQL reference manual specifically states that this behavior of user-defined variables is not guaranteed.