I have the following SQL statement running against a MariaDB 10.1.26 with ~2.000 rows with instant results.
select value, datetime from Schuppen
where (value = (select min(value) from Schuppen where (measure = 'temp')
and datetime between '2018-11-01 00:00:00' and '2018-11-02 00:00:00'))
and datetime between '2018-11-01 00:00:00' and '2018-11-02 00:00:00';
When I use the following statement with variables for the datetime fields, the execution takes ~5.5 seconds.
set #startdate = cast('2018-11-01 00:00:00' as datetime);
set #enddate = cast('2018-11-02 00:00:00' as datetime);
select value, datetime from Schuppen
where (value = (select min(value) from Schuppen where (measure = 'temp')
and datetime between #startdate and #enddate))
and datetime between #startdate and #enddate;
The more data rows I have, the longer it takes to execute the statement. Seems like the variables change the behaviour of the statement somehow.
What's wrong here?
I use MySQL Workbench and #variables are very useful to query/search different tables for a given attribute. I ran into a similar issue. After scouring through different threads and trying different things, it worked well when I set the #variable to be of exactly the same type and same encoding as the column in the table(s) that I am searching for that variable.
For example:
SET #keyword = CONVERT(CAST("KEYWORD" AS CHAR(8)) USING ASCII);
In this case, the search column cname in my table customer is of type CHAR(8) and encoded using ASCII:
SELECT * FROM CUSTOMERS WHERE cname=#keyword;
If you have multiple tables to query, where cname is CHAR(10) in one and CHAR(8) in another, then you can do the following:
SET #keyword = "KEYWORD";
SELECT * FROM CUSTOMERS WHERE cname=CONVERT(CAST(#keyword AS CHAR(8)) USING ASCII);
SELECT * FROM EMPLOYEES WHERE cname=CONVERT(CAST(#keyword AS CHAR(10)) USING ASCII);
The problem is that the query optimizer does a bad job on finding a suitable index when using variables. This is a known issue.
If you use EXPLAIN on both queries, you will see the difference. Just try to avoid variables when not necessary.
For the first query, the optimizer "sees" the chosen values and decides an index can be perfectly used to satisfy the selected range more efficiently.
For the second query, the optimizer is unaware of the two values that define the range, and decides to fall back to a FULL SCAN instead.
Related
I have a query that has a user-defined variable set on top of the main query. Its something like this.
set #from_date = '2019-10-01', #end_date = '2019-12-31';
SELECT * FROM myTable
WHERE create_time between #from_date AND #end_date;
It works just fine when I executed it in MySQL Workbench, but when I put it to Excel via MySQL ODBC it always shows an error like this.
I need that user-defined variable to works in Excel. What am I supposed to change in my query?
The ODBC connector is most likely communicating with MySQL via statements or prepared statements, one statement at a time, and user variables are not supported. A prepared statement would be one way you could bind your date literals. Another option here, given your example, would be to just inline the date literals:
SELECT *
FROM myTable
WHERE create_time >= '2019-10-01' AND create_time < '2020-01-01';
Side note: I expressed the check on the create_time, which seems to want to include the final quarter of 2019, using an inequality. The reason for this is that if create_time be a timestamp/datetime, then using BETWEEN with 31-December on the RHS would only include that day at midnight, at no time after it.
Use subquery for variables values init:
SELECT *
FROM myTable,
( SELECT #from_date := '2019-10-01', #end_date := '2019-12-31' ) init_vars
WHERE create_time between #from_date AND #end_date;
Pay attention:
SELECT is used, not SET;
Assinging operator := is used, = operator will be treated as comparing one in this case giving wrong result;
Alias (init_vars) may be any, but it is compulsory.
Variable is initialized once but may be used a lot of times.
If your query is complex (nested) the variables must be initialized in the most inner subquery. Or in the first CTE if DBMS version knows about CTEs. If the query is extremely complex, and you cannot determine what subquery is "the most inner", then look for execution plan for what table is scanned firstly, and use its subquery for variables init (but remember that execution plan may be altered in any moment).
I am looking for a way to use a parameter as a result that can be plugged in to another select statement later down the line.
This is the CTE select statement that I am able to pull by manually changing all four of the dates listed to what I want
SELECT CurrentYearEmp.PRCo,
CurrentYearEmp.Employee,
CASE
WHEN CurrentYearEmp.PREndDate <> '2016-11-20'
THEN '2016-11-20 00:00:00'
WHEN CurrentYearEmp.PREndDate = '2016-11-20'
THEN '2016-11-20 00:00:00'
END AS 'ParameterPREndDate'
I am able to replace the first half of the WHEN statement with a parameter like this
SELECT CurrentYearEmp.PRCo,
CurrentYearEmp.Employee,
CASE
WHEN CurrentYearEmp.PREndDate <> #PREndDate
THEN '2016-11-20 00:00:00'
WHEN CurrentYearEmp.PREndDate = #PREndDate
THEN '2016-11-20 00:00:00'
END AS 'ParameterPREndDate'
But it will only produce the results I am looking for if the parameter is 2016-11-20. I want to be able to have #PREndDate as '2017-02-14' it will post 2017-02-14 as a result of the select statement.
You can use a Table Valued Function as HABO has already said. Here is a simple example for one:
/*
--Create some dummy data in a db
CREATE TABLE SomeData
(
DataId INT IDENTITY
, Val VARCHAR(8)
, Dt DATE
)
INSERT INTO dbo.SomeData (Val, Dt) VALUES ('A', '2017-1-1'),('B', '2017-1-2'),('C', '2017-1-3'),('D', '2017-1-4'),('E', '2017-1-5')
--Create a table valued function
CREATE FUNCTION ReturnData (#StartDt DATE, #EndDt Date)
RETURNS TABLE
AS
Return
Select *
From SomeData
WHERE DT between #StartDt and #EndDt
*/
Select *
From ReturnData('1-1-2017', '1-3-2017')
The best thing IMHO about Table Functions is they can join to existing things as they are well formed objects in the database. When you do Procedures and Dynamic SQL, you get a result set but that does not mean your code can be joined. Table Functions can be cross applied to run off of cte's and they can be joined to other tables. So essentially they are better for reuse but they have rules around them so you cannot do more advanced things like dynamic sql in them(as far as I know unless they changed it recently) and other things. But if you want a reusable data set with rules governing input, that is pretty much exactly what they are made for.
I have a table with a DATETIME field called date_created, and need to check for some data with this kind of query:
SELECT * FROM table WHERE UNIX_TIMESTAMP(date_created) + $number < UNIX_TIMESTAMP()
Is it possible to do this without the UNIX_TIMESTAMP() function? Maybe with NOW())?
And if it is, will it be faster?
Is it possible to do this without the UNIX_TIMESTAMP() function? Maybe with NOW())?
Not exactly. Whilst you could do the following, it won't achieve the exact same results:
SELECT * FROM `table` WHERE date_created + INTERVAL $number SECOND < NOW()
The reason is that, unlike TIMESTAMP, DATETIME is neither intended nor capable of representing a specific instance in time; rather, it effectively represents the display of a calendar/clock (not the same thing).
When using the above query, which does not go via a UTC timestamp, the comparison is merely a question of whether a clock right now would show a date/time later than that which was recorded in the database (plus $number seconds).
However, when converting to a UTC timestamp, the timezone of the respective clock displays become relevant and because many timezones are not constant (e.g. they often move around for daylight savings), multiple DATETIME values could give rise to the same UTC timestamp.
For example:
CREATE TABLE `table` (date_created DATETIME);
INSERT INTO `table` VALUES ('2012-10-28 01:00:00'), ('2012-10-28 02:00:00');
Then compare the results of the two queries when run at 2012-10-28 02:00:00 in the UK:
Your original query:
SET SESSION time_zone = 'Europe/London';
SELECT *
FROM `table`
WHERE UNIX_TIMESTAMP(date_created) + 100
< UNIX_TIMESTAMP('2012-10-28 02:00:00');
The alternative query above:
SELECT *
FROM `table`
WHERE date_created + INTERVAL 100 SECOND
< '2012-10-28 02:00:00';
And if it is, will it be faster?
Probably (a lexicographic order for the comparison will suffice, versus parsing & converting the DATETIME values to UTC according the session timezone's rules followed by subtraction and sign inspection), but I'd advise performing your own benchmarks.
Using eval expression in WHERE clause should be avoided if possible. It prevents correct utilization of indexes. If possible do the math in code and send the values as query parameters.
In MySQL, I need to write a query (if possible) that finds all rows of a table where the date_created is the same as last_updated. The rub is that I need to ignore the time. Basically, I'm looking for user rows that were created and activated the same day (we don't store an activation date). So presumably the dates would be the same but the times may be different.
You could use the DATE() function, which returns only the date portion of a datetime value. This allows you to compare just the date portion of the values:
SELECT * FROM table_name
WHERE DATE(date_created) = DATE(last_updated)
The timezone may be relevant here. So you may want to cast the datetime values to the user's timezone prior to using the DATE() function, using CONVERT_TZ().
Try this:
SELECT *
FROM table_name
WHERE DATE_FORMAT(date_created, '%Y-%m-%d') = DATE_FORMAT(last_updated, '%Y-%m-%d')
not pretty but works:
SELECT *
FROM table_name
WHERE day(date_created) = day(last_updated) and
month(date_created) = month(last_updated) and
year(date_created) = year(last_updated)
I have quite a complex MySQL query with a problem but I have narrowed the problem down to the SQL below. The problem is that the MySQL date functions (WEEK, YEAR etc.) don't accept the datetime stored in a user variable.
SELECT
#test := datetime
,datetime
FROM `agenda`
WHERE YEAR(#test) = 2011
This doesn't give me any results, however, the following SQL gives me results:
SELECT
#test := datetime
,datetime
FROM `agenda`
WHERE YEAR(datetime) = 2011
(datetime is a fieldname in the agenda table.)
What is the problem here?
In the first query you're attempting to set #test equal to the datetime fields value based on a WHERE clause that is itself referring to the value of #test.
Unless #test has a value up front then you can't expect this to produce any meaningful results.