In the BETWEEN documentation description (http://dev.mysql.com/doc/refman/5.5/en/comparison-operators.html#operator_between) I've noticed a weird expression that I cannot understand entirely:
For best results when using BETWEEN with date or time values, use CAST() to explicitly convert the values to the desired data type. Examples: If you compare a DATETIME to two DATE values, convert the DATE values to DATETIME values. If you use a string constant such as '2001-1-1' in a comparison to a DATE, cast the string to a DATE.
So I have the following questions:
Could anyone provide an example when it's really necessary (with explanation of that) and when cast would change the result or performance dramatically. Let's assume we use one of number of date literals mysql defines (http://dev.mysql.com/doc/refman/5.5/en/date-and-time-literals.html). For sake of simplicity let it be of YYYY-MM-DD ('2013-07-26') format (with any date you like)
Could anyone clarify what "best results" mean? The result is either expected, or not - what is the "best" in this case?
PS: the target mysql version is the latest 5.5 available and newer.
PPS: to make things clear:
The question assumes we use datetime/date compatible columns, not varchars etc
The question is about expanding type, not about narrowing
Look at simple example (MySql 5.6):
drop table dattes;
create table dattes(
id int,
da_te date,
name varchar(200)
);
set #x = 0;
insert into dattes
select (#x:=#x+1),
'2013-5-1' + interval #x day,
t.column_name
from information_schema.columns t
;
and here is a query:
Set #start_date = cast( '2013-5-2' as date);
Set #start_datetime = cast( '2013-5-2 15:00' as datetime);
Set #end_date = cast( '2013-5-10' as date);
select (Select sum( id ) from dattes
where da_te between #start_date and #end_date) da_test1,
(Select sum( id ) from dattes
where da_te between #start_datetime and #end_date) da_test2,
(Select sum( id ) from dattes
where da_te between cast( #start_datetime as date)
and #end_date) da_test3
;
+ ------------- + ------------- + ------------- +
| da_test1 | da_test2 | da_test3 |
+ ------------- + ------------- + ------------- +
| 45 | 44 | 45 |
+ ------------- + ------------- + ------------- +
One would expect that in 2nd case MySql would implicitely convert #start_datetime to date (because the column da_te in the table is date) -> but MySql did the opposite, it widen other arguments to datetime. Because of this one record from the table was skipped ( '2013-5-2 00:00' < '2013-5-2 15:00').
This affects performance of course (maybe not dramatically, but a little), because MySql must convert all column values (or values of an index, if is any) from date to datetime.
Related
I'm trying to perform a field update on mysql with a MAX() value getting other columns of the same table .
For instance, I've this table:
id starting_date activity_1 activity_2 activity_3
1 0000-00-00 10 5 12
I'm trying this query (it doesn't work):
$today="2022-07-20"; //It's a dynamic var generate via date()
UPDATE table_name SET starting_date = DATE_ADD('2022-07-20',(INTERVAL (SELECT GREATEST(activity_1,activity_2,activity_3) FROM table_name WHERE id ='1') MONTH) WHERE id ='1'
My desire is to add 12 months (or the greatest value) to 2022-07-20...
I'm trying several queries with no positive result
Any idea around?
Thanks
Use multiple-table UPDATE syntax.
UPDATE table_name
JOIN ( SELECT id,
GREATEST(activity_1,activity_2,activity_3) interval_for_update
FROM table_name ) value_for_update USING (id)
SET starting_date = '2022-07-20' + INTERVAL interval_for_update MONTH
-- WHERE id = 1
PS. Never enclose numeric literal values with the quote chars - this converts them to strings and causes excess implicit data convertions.
I have a table with this fields:
Id int(11) pk,
Start_date date,
End_date date,
type tinyint(1),
Frequency int.
I want to do a select on this table where start_date+frequency = #date(a variable date) until end_date(loop).
How do this with sql?
Thanks in advance.
EDIT:
Variable date is (for example):
SET #date = '2017-03-30'
type can be 0 or 1:
if type = 0 my query is :
select * from table
where type = 0 and start_date <= #date AND end_date>=#date
if type = 1, frequency is a field with an integer number(a interval of days). So I have to check if adding this value to start_date is equals to #date.
if yes, I have to return the current record
if no, I have to iterate this operation
Date current = start_date + interval of 'frequency' days
while(current < end_date){
if(current == #date)
(this is the record I want)
else
current+=frequency
}
The result of query of type 1 can be more than one record. And finally I want to UNION the result of type 0 and 1 in unique select.
Based on a comment/confirmation below the question:
So, to put it in less focussed on the wrong solution terms, you want to determine whether the difference between start_date and #date, in days, is an integer multiple of frequency? –
Looks like you want something like:
select * from table
where
start_date <= #date AND
end_date>=#date AND
(
type = 0 OR
(
type = 1 AND
mod(datediff(#date,start_date),frequency) = 0
)
)
Once we determined the actual requirement, above, and it was clear we just need to find out if one number is a multiple of another, we use mod to compute that. The rest of the structure of the WHERE clause essentially follows the bullet-pointed section of the question.
I want to get data for sum(cost) in from date and to date. my table fields data type is id is INT AUTO INCREMENT, dd is varchar, mm is varchar, yyyy varchar and dd value is 01 to 31, mm value is 01 to 12 and year value is 4 digit year(2016 or 2015...,)
I want exactly sum(cost) = 143
SELECT SUM(cost) FROM costing
WHERE
(( dd> CAST('05' AS SIGNED) AND mm= CAST('12' AS SIGNED) AND yyyy= CAST('2015' AS SIGNED))
AND
((dd<= CAST('23' AS SIGNED) AND mm = CAST('01' AS SIGNED) AND yyyy= CAST('2016' AS SIGNED)))
order by id
You can use the CAST AS DATE function like this:
SELECT
SUM(cost) FROM costing
WHERE
CAST(CONCAT(yyyy,mm,dd) AS DATE) BETWEEN '2015-12-05' AND '2016-01-23'
First you should consider about your data types. Dates should not in varchar or integer data types. refer this http://www.w3schools.com/sql/sql_datatypes.asp
Then you can use between. refer this http://www.w3schools.com/sql/sql_between.asp
Then you no need order by id in your query.
Your query will like
SELECT SUM(cost) FROM costing WHERE costingdate BETWEEN "5-12-2015" AND 23-1-2015"
I am wanting to use a case statement to change the time piece only of a datetime field in SQL Server 2008 I have a query that will change the time piece, but I need to know how to keep the date portion intact so only the time piece is altered. Meaning if the datetime is
01/01/2015 08:45:10.863
with my syntax I would want to alter it to
01/01/2015 08:30:00.000
This is my syntax which as I said will change the time portion but it does not retain the date. How can I keep the date and change the time portion only?
Create Table #Test
(
[charactername] varchar(100)
,[lefttabletime] datetime
)
Insert Into #Test Values
('Bob Goblin', '01/01/2015 08:14:23.000'),
('Grab Crab', '01/01/2015 08:30:56.023'),
('Mike Knight', '01/01/2015 08:45:10.863')
Select
[charactername]
,case when CAST([lefttabletime] As TIME) > '08:40:00.000' THEN '08:30:00.000'
else [lefttabletime]
FROM #Test
Drop Table #Test
EDIT Additional Syntax Attempted
This threw an error of:
Msg 241, Level 16, State 1, Line 10
Conversion failed when converting date and/or time from character string.
And I tried this syntax
Select
[charactername]
,case when CAST([lefttabletime] As TIME) > '08:40:00.000'
THEN CAST(CAST(CONVERT(DATE, [lefttabletime],101) AS VARCHAR)
+ '08:40:00.000' AS DATETIME) else [lefttabletime] end
FROM #Test
I find it easier if you split the leftabletime into a date and a time component first, the recombine them with the new time portion:
;WITH cte AS
(
SELECT charactername,
[date] = CAST(lefttabletime as date),
[time] = CAST(lefttabletime as time)
FROM #test
)
SELECT charactername,
CAST([date] as datetime)
+ CAST(CASE WHEN [time] > '08:40:00' THEN '08:30:00' ELSE [time] END as datetime)
FROM cte
You can merge the two statement together but I like the simplicity and clarity that a separate CTE provides.
I usually try to make datetime rounding problems fit the DATEADD/DATEDIFF pattern, and I've managed to do that here:
Create Table #Test
(
[charactername] varchar(100)
,[lefttabletime] datetime
)
Insert Into #Test Values
('Bob Goblin', '2015-01-01T08:14:23.000'),
('Grab Crab', '2015-01-01T08:30:56.023'),
('Mike Knight', '2015-01-01T08:45:10.863')
select charactername,
CASE WHEN DATEDIFF(minute,'20010101',lefttabletime)%60 >= 40 THEN
DATEADD(minute,((DATEDIFF(minute,'20010101',lefttabletime)/30)*30),'20010101')
ELSE lefttabletime END
from #Test
Drop Table #Test
The expression DATEADD(minute,((DATEDIFF(minute,'20010101',lefttabletime)/30)*30),'20010101') rounds the time of a datetime down to the nearest 30 minutes (20010101 is an arbitrary date and doesn't need to be adjusted in any way).
I also just use a separate DATEDIFF to find the required matching condition. Where possible, with datetime data, I try to keep it in datetime variables, or, at worst, ints. As soon as you convert to strings you have to start worrying about formats, etc, which I'd usually rather avoid.
Result:
charactername
--------------------- -----------------------
Bob Goblin 2015-01-01 08:14:23.000
Grab Crab 2015-01-01 08:30:56.023
Mike Knight 2015-01-01 08:30:00.000
I'm curious what the right way is to construct a query where the rows are pulled based on a timestamp that represents a specific month. Given that different months have different numbers of days, is there a way to generate a query that always gives you the rows where the timestamp contains the current month so that the results would only include the current month?
Do you mean something like this
SELECT * FROM tbl WHERE
MONTH(timesp) = MONTH(NOW()) AND
YEAR(timesp) = YEAR(NOW());
You can use the FROM_UNIXTIME() function:
SELECT *
FROM tableName
WHERE MONTH(FROM_UNIXTIME(timestampField))==6
Just use MONTH:
select *
from foo
where month_column = MONTH(getdate())
and year_column = YEAR(getdate())
Try this sql.
select *
from yourtable
where yourdatefield>=DATE_SUB(CURDATE(),INTERVAL 1 MONTH);
You're looking for something like this:
SELECT * FROM table where MONTH(date_row) = $month;
If you have an index on your date field, then this is efficient (T-SQL syntax, the idea applieas to any RDBMS though)
SELECT
*
FROM
tableName
WHERE
dateTimeField
BETWEEN
-- build the string 'YYYY-MM-01', cast back as a datetime
CAST(
CAST(YEAR(GETDATE()) AS varchar) + '-' + CAST(MONTH(GETDATE()) AS varchar) + '-01'
AS datetime
)
AND
-- add one month, subtract one day
DATEADD(mm, 1,
-- build the string 'YYYY-MM-01', cast back as a datetime
CAST(
CAST(YEAR(GETDATE()) AS varchar) + '-' + CAST(MONTH(GETDATE()) AS varchar) + '-01'
AS datetime
)
) - 1
Of course any other method to get two datetime values in the right range would work.
SQL Server has LEFT(CONVERT(varchar, GETDATE(), 120), 8) + '01' to convert a datetime to string, other Db servers have their own functions to do the same. Maybe you can calculate the two values in the calling application more easily - how you get them, is not the point.
The point is that BETWEEN can use an index, whereas the other solutions that work with WHERE MONTH(dateTimeField) = 6 will trigger a table scan, which is about the slowest operation you can do on a table.