Finding dates between two dates (Best practice) - sql
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.
Related
speeding up records fetch time
I have a SQL sentence which takes around 44 Sec to fetch the data. how can I reduce the time ? this is the sql Select AVNR,replace(to_char((CAST(DBTM as timestamp)),'hh24:mi'),'00:00','24:00') as ptime, Wert From e_mw_60min_me Where to_char((CAST ( DBTM as TIMESTAMP)), 'DD-MM-YYYY') = '13-03-1396' and AVNR In (1141,1142,1144,10335,10336,12016,1146,1147,1149,1129,1130,1132,1134,1135,1137,5895,5896,5900,8906,8907,8909,8901,8902,8904,8940,8941,8943,8951,8952,8954,8972,8973,8975,8830,8831,8833,8835,8836,8838,1113,1982,1984,2314,2315,2317,3272,3273,3275,3267,3268,3270,3262,3263,3265,10231,10136,9066,8779,8780,8782,8774,8775,8777,8320,8321,8323,7696,7697,7699,10486,10487,10489,3329,3330,12018,3322,3328,10132,3320,3321,12017,3222,3223,3225,686,687,689,691,692,694,696,697,699,1,2,4,10527,10528,10529,4911,4912,4914,4917,4918,4920,5162,5163,5166,5157,5158,5160,5168,5169,5171,5449,5450,5452,10116,10117,10119,10120,10121,10123,2271,2272,2279,2266,2267,2269,2259,2260,2262,1292,1293,1295,5380,5381,5383,5374,5375,5377,10545,10546,10547,3281,10126,12031,3244,3245,12030,10983,10984,12033,10987,10989,12032,11073,11074,1125,11079,11080,3333,105,106,108,100,101,103,93,94,96,29,30,32,95,102,1197,124,123,126,12085,12086,12087,2520,2521,2523,2525,2526,2528,2515,2516,2518,2510,2511,2513,5444,5445,5447,2201,2202,12025,3336,3337,3339,3002,3003,12029,1643,1644,1646,1609,1610,1612,1596,1597,1599,9717,9718,9720,9722,9723,9725,2146,2147,2149,2141,2142,2144,2136,2137,2139,2131,2132,2134,2121,2122,2124,2126,2127,2129,2635,2636,2638,2641,2642,2644,7499,7500,7502,4610,4611,4613,4605,4606,4608,4600,4601,4603,9074,9075,9077,9079,9080,9082,9235,9236,9238,9240,9241,9243,9245,9246,9248,8468,8469,8471,5785,5786,5788,5790,5791,5793,5691,5692,5694,5685,5686,5688,8455,8456,8458,11312,11313,4588,7654,7655,7657,9376,9377,9379,9371,9372,9374,9382,9383,9385,5918,5919,5922,5934,5935,5938,5912,5913,5916,10963,7860,10964,135,136,138,10658,10659,10664,10660,10661,10662,5173,5458,5460); //these AVNRs are completley Dynamic the Table is like : AVNR (Like ID Number) DBTM( reocrds every time for every points) Wert (the value) I want to know any other sql command which makes it faster to fetch
If DBTM is a date you don't need to cast it to a timestamp; and if there is an index on that then you should not convert it to a string to compare with another string - leave it as a date and convert the fixed value to a date too, and compare with a range that covers the entire day: select AVNR, replace(to_char(DBTM,'hh24:mi'),'00:00','24:00') as ptime, Wert from e_mw_60min_me where DBTM >= date '1396-03-13' and DBTM < date '1396-03-14' and AVNR In (1141, ...); I've used date literals but you can use to_date() if you are starting from a variable string: where DBTM >= to_date('13-03-1396', 'DD-MM-YYYY') and DBTM < to_date('13-03-1396', 'DD-MM-YYYY') + 1 and AVNR In (1141, ...); You should also consider creating a collection for all the AVNR values you are looking for and using member of instead if IN, or explode the collection and join to it. It depends where those values are coming from though.
You can add an index: create index idx_e_mw_60min_me_2 on (to_char((CAST ( DBTM as TIMESTAMP)), 'DD-MM-YYYY'), AVNR) This exactly matches the where clause so it should speed up the query.
SQL. Select Unixtime for whole day
I am looking for a way to select a whole days worth of data from a where statement. Timestamp is in unix time such as (1406045122). I want to select the today's date of unix time range and find all the food that has been added in today. Thank in advance. This is the code I wrote. I'm not sure what I should put in the ( ????? ) part. I know it has to do with 60*60*24=86400 secs per day but I'm not too sure how I can implement this. Select timestamp,food from table1 where timestamp = ( ????? );
Select timestamp,food FROM table1 WHERE timestamp > :ts AND timestamp <= (:ts + 86400); replace :ts with the starting timstamp and you'll filter a whole day's worth of data edit This select query would give you the current timestamp (there may be more efficient ones, i don't work with sqlite often) select strftime("%s", current_timestamp); You can find more info about them here: http://www.tutorialspoint.com/sqlite/sqlite_date_time.htm Using the strftime() function, combined with the date() function we can write this following query which will not need any manual editing. It will return the records filtered on timestamp > start of today & timestamp <= end of today. Select timestamp,food FROM table1 WHERE timestamp > strftime("%s", date(current_timestamp)) AND timestamp <= (strftime("%s", date(current_timestamp)) + 86400);
Your mileage will likely depend on your version of SQL but for example on MySQL you can specify a search as being BETWEEN two dates, which is taken conventionally to mean midnight on each. So SELECT * FROM FOO WHERE T BETWEEN '2014-07-01' AND '2014-07-02'; selects anything with a timestamp anywhere on 1st July 2014. If you want to make it readable you could even use the ADDDATE function. So you could do something like SET #mydate = DATE(T); SELECT * FROM FOO WHERE T BETWEEN #mydate AND ADDDATE(#mydate, 1); The first line should truncate your timestamp to be 00:00:00. The second line should SELECT only records from that date.
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))
Large performance difference: Using sysdate vs using pre-formatted date
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?
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;