Large performance difference: Using sysdate vs using pre-formatted date - sql

Why is there such a huge performance difference between these two queries?
-- (89 seconds)
SELECT max(mydate) FROM mytable WHERE eqpid = 'ABCDEFG'
AND mydate < sysdate - 5
vs.
-- (0.6 seconds)
SELECT max(mydate) FROM mytable WHERE eqpid = 'ABCDEFG'
AND mydate < TO_DATE('05/27/2011 03:13:00', 'MM/DD/YYYY HH24:MI:SS') -- 5 days ago
Regardless of indexes, it seems that both to_date and sysdate just return "some date value".
Notes: A composite index exists on this table including eqpid and 2 other columns.
An index also exists for mydate. Both are b-tree. There are about 29 million rows.
Why would the optimizer choose such an obviously different (and in one case, awful) plan for these?

Jonathan Lewis has written about issues with sysdate in 9i; have a look at the 'surprising sysdate' section here, for example. Essentially the arithmetic on sysdate seems to confuse the optimizer, so in this case it thinks the index on mydate is more selective. This seems like quite an extreme example though. (Originally pointed in this direction from a not-really-related Ask Tom post).

I don't know Oracle, but in Postgresql a possible source of ignoring an index is mismatched types. Maybe doing the straight - 5 makes Oracle think the rhs is numeric. Can you do a cast to date (or whatever is the exact type of mydate) on sysdate - 5?

Related

Finding dates between two dates (Best practice)

I have 2 SQL scripts. One like this:
"Date" > '2014-04-11' AND "Date" <= '2014-04-12'
the other one like this:
"Date" BETWEEN '2014-04-11' AND '2014-04-12'
Now i wonder if there is any specific best practice, one reason to do one over the other and if one of them is better for some apparent reason I missed somewhere down the road.
You are asking for best practices. I think the following is the best practice:
"Date" >= DATE '2014-04-11' AND "Date" < DATE '2014-04-12' + 1
First, note the use of the DATE keyword. You question is about dates and yet you are using a date format that Oracle does not directly support. Happily, Oracle support the ANSI standard DATE keyword with the ISO standard format.
Second, I added a +1 so you can see the end of the time period, which is presumably what you want to see in the code. It shouldn't affect performance because the + 1 is on a constant.
Third, a date constant has a time component. When none is specified, it is midnight on the date. So, the expression:
"Date" BETWEEN '2014-04-11' AND '2014-04-12'
Is really:
"Date" >= TIMESTAMP '2014-04-11 00:00:00' AND "Date" <= TIMESTAMP '2014-04-12 00:00:00'
That is, exactly one time from the later date is included, the first instant at midnight. This is not usually what you want. Oracle makes this problem worse in two ways:
The date data type includes a time component.
The default way of presenting date values has no time component.
So, to be safest, use the following rules:
Do not use between on dates.
Use >= for the first date.
User < for the second.
Aaron Bertrand has a blog on exactly this topic. Although it is specifically about SQL Server, many of the ideas apply to Oracle -- especially because the date data type in Oracle includes times.
SQL> create table date_tab as select sysdate + level d from dual connect by rownum < 10;
Table created.
Using between:
SQL> set autotrace traceonly
SQL> select * from date_tab where d between sysdate + 1 and sysdate + 3;
Execution Plan
----------------------------------------------------------
Plan hash value: 290508398
...
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(SYSDATE#!+1<=SYSDATE#!+3)
2 - filter("D">=SYSDATE#!+1 AND "D"<=SYSDATE#!+3)
Using date intervals:
SQL> select * from date_tab where d > sysdate + 1 and d <= sysdate + 3;
Execution Plan
----------------------------------------------------------
Plan hash value: 290508398
...
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(SYSDATE#!+1<SYSDATE#!+3)
2 - filter("D">SYSDATE#!+1 AND "D"<=SYSDATE#!+3)
As you can see, oracle optimizer rewrites the between predicate to "D">=SYSDATE#!+1 AND "D"<=SYSDATE#!+3 So if you use the second variant oracle performs less action than the first variant for one step.

SQL (Teradata) Select data

I have a questions related to SQL (teradata in particular).
I have to generate the report for 1 day.
How can I achieve it?
For example, in ms access, I can do
WHERE DT>=#2011-01-01# and DT<=#2011-0101#
What about big-guys? (SQL Server, not MS Access).
I know that it is possible to use
DT between '2011-09-01' and '2011-09-02'
But this method is not precise. How can I specify 1 day using ranged WHERE statement?
I apologize, I don't have the SQL access and I can't test it; therefore I am asking for professional advise.
BETWEEN is range-inclusive, so this will do:
DT between '2011-09-01' and '2011-09-01'
And, yes, it is precise :)
Now, if your DT is a datetime field (not date field), then you must change your approach:
DT >= '2011-09-01' and DT < '2011-09-02'
Working with dates in Teradata can be a little tricky.
If DT is a "timestamp" field, you can simply convert it to a date and, because you are reporting for exactly one day, just test for equality.
Let's say you want to report on today, so pass in '03/20/2012':
-- Teradata: Select records where DT matches a certain day.
SELECT * -- etc...
WHERE CAST(DT as date) = TO_DATE('03/20/2012', 'mm/dd/yyyy')
MS SQL is similar:
SELECT * from [webdb].[mediaguide].[fileDirectories]
WHERE CAST('03/20/2012' AS date) = CAST(DT AS date)
Technically I'd use parameterization for passing in the date, but you get the idea.
When selecting over a range (especially dates and timestamps), it's best to do lower-bound inclusive, upper-bound exclusive. That is, you want things in the range lb <= x < ub. In your case, this amounts to:
SELECT [list of columns]
FROM [table]
WHERE dt >= :startDate
AND dt < :endDate
(the :variableName is how I input host variables on my system. You'll have to look up what it is on teradata.)
The strings you have listed for your between will work as-is - I think pretty much every major RDBMS recognizes *ISO formatting by default.
Simple answer would be:
WHERE DT BETWEEN Date_X AND Date_X + 1
If you want to be explicitly
WHERE DT BETWEEN Date_X AND Date_X + INTERVAL '1' DAY
You can always read Teradata Manual :)
Teradata Manual on BETWEEN
Following their manual, x BETWEEN y AND z == ((x >= y) AND (x <=z))

How to get one day ahead of a given date?

Suppose I have a date 2010-07-29. Now I would like to check the result of one day ahead. how to do that
For example,
SELECT *
from table
where date = date("2010-07-29")
How to do one day before without changing the string "2010-07-29"?
I searched and get some suggestion from web and I tried
SELECT *
from table
where date = (date("2010-07-29") - 1 Day)
but failed.
MySQL
SELECT *
FROM TABLE t
WHERE t.date BETWEEN DATE_SUB('2010-07-29', INTERVAL 1 DAY)
AND '2010-07-29'
Change DATE_SUB to DATE_ADD if you want to add a day (and reverse the BETWEEN parameters).
SQL Server
SELECT *
FROM TABLE t
WHERE t.date BETWEEN DATEADD(dd, -1, '2010-07-29')
AND '2010-07-29'
Oracle
SELECT *
FROM TABLE t
WHERE t.date BETWEEN TO_DATE('2010-07-29', 'YYYY-MM-DD') - 1
AND TO_DATE('2010-07-29', 'YYYY-MM-DD')
I used BETWEEN because the date column is likely DATETIME (on MySQL & SQL Server, vs DATE on Oracle), which includes the time portion so equals means the value has to equal exactly. These queries give you the span of a day.
If you're using Oracle, you can use the + and - operators to add a number of days to a date.
http://psoug.org/reference/date_func.html
Example:
SELECT SYSDATE + 1 FROM dual;
Will yield tomorrow's date.
If you're not using Oracle, please tell use what you ARE using so we can give better answers. This sort of thing depends on the database you are using. It will NOT be the same across different databases.
Depends of the DateTime Functions available on the RDBMS
For Mysql you can try:
mysql> SELECT DATE_ADD('1997-12-31',
-> INTERVAL 1 DAY);
mysql> SELECT DATE_SUB('1998-01-02', INTERVAL 31 DAY);
-> '1997-12-02'
If youre using MSSQL, you're looking for DateAdd() I'm a little fuzzy on the syntax, but its something like:
Select * //not really, call out your columns
From [table]
Where date = DateAdd(dd, -1, "2010-07-29",)
Edit: This syntax should be correct: it has been updated in response to a comment.
I may have the specific parameters in the wrong order, but that should get you there.
In PL SQL : select sysdate+1 from dual;

oracle date range

using a Oracle 10g db I have a table something like this:
create table x(
ID NUMBER(10) primary key,
wedding DATE NOT NULL
);
how can I
select * from x where wedding is in june 2008???
I know it is probably an easy one but I couldn't find any satisfying answer so far.
Help is very much appreciated.
Use:
SELECT *
FROM x
WHERE x.wedding BETWEEN TO_DATE('2008-JUN-01', 'YYYY-MON-DD')
AND TO_DATE('2008-JUL-01', 'YYYY-MON-DD')
Use of TO_DATE constructs a date with a time portion of 00:00:00, which requires the end date to be one day ahead unless you want to use logic to correct the current date to be one second before midnight. Untested:
TO_DATE('2008-JUN-30', 'YYYY-MON-DD') + 1 - (1/(24*60*60))
That should add one day to 30-Jun-2008, and then subtract one second in order to return a final date of 30-Jun-2008 23:59.
References:
TO_DATE
This is ANSI SQL, and supported by oracle as of version 9i
SELECT *
FROM x
WHERE EXTRACT(YEAR FROM wedding) = 2008
AND EXTRACT(MONTH FROM wedding) = 06
Classic solution with oracle specific TO_CHAR():
SELECT *
FROM x
WHERE TO_CHAR(wedding, 'YYYY-MMM') = '2008-JUN'
(the latter solutions was supported when dinosaurs still walked the earth)

How to get records after a certain time using SQL datetime field

If I have a datetime field, how do I get just records created later than a certain time, ignoring the date altogether?
It's a logging table, it tells when people are connecting and doing something in our application. I want to find out how often people are on later than 5pm.
(Sorry - it is SQL Server. But this could be useful for other people for other databases)
For SQL Server:
select * from myTable where datepart(hh, myDateField) > 17
See http://msdn.microsoft.com/en-us/library/aa258265(SQL.80).aspx.
What database system are you using? Date/time functions vary widely.
For Oracle, you could say
SELECT * FROM TABLE
WHERE TO_CHAR(THE_DATE, 'HH24:MI:SS') BETWEEN '17:00:00' AND '23:59:59';
Also, you probably need to roll-over into the next day and also select times between midnight and, say, 6am.
In MySQL, this would be
where time(datetimefield) > '17:00:00'
The best thing I can think would be: don't use a DateTime field; well, you could use a lot of DATEADD/DATEPART etc, but it will be slow if you have a lot of data, as it can't really use an index here. Your DB may offer a suitable type natively - such as the TIME type in SQL Server 2008 - but you could just as easily store the time offset in minutes (for example).
For MSSQL use the CONVERT method:
DECLARE #TempDate datetime = '1/2/2016 6:28:03 AM'
SELECT
#TempDate as PassedInDate,
CASE
WHEN CONVERT(nvarchar(30), #TempDate, 108) < '06:30:00' then 'Before 6:30am'
ELSE 'On or after 6:30am'
END,
CASE
WHEN CONVERT(nvarchar(30), #TempDate, 108) >= '10:30:00' then 'On or after 10:30am'
ELSE 'Before 10:30am'
END
Another Oracle method for simple situations:
select ...
from ...
where EXTRACT(HOUR FROM my_date) >= 17
/
http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/functions050.htm#SQLRF00639
Tricky for some questions though, like all records with the time between 15:03:21 and 15:25:45. I'd also use the TO_CHAR method there.
In Informix, assuming that you use a DATETIME YEAR TO SECOND field to hold the full date, you'd write:
WHERE EXTEND(dt_column, HOUR TO SECOND) > DATETIME(17:00:00) HOUR TO SECOND
'EXTEND' can indeed contract the set of fields (as well as extend it, as the name suggests).
As Thilo noted, this is an area of extreme variability between DBMS (and Informix is certainly one of the variant ones).
Ok, I've got it.
select myfield1,
myfield2,
mydatefield
from mytable
where datename(hour, mydatefield) > 17
This will get me records with a mydatefield with a time later than 5pm.