Skip to Main Content
  • Questions
  • Table with date column datatype (Storing datetime) causing issue in fetching result and when use with to_date and to_char causing performance issue.

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Deepesh.

Asked: November 13, 2017 - 5:57 am UTC

Last updated: November 14, 2017 - 12:42 pm UTC

Version: 12.1.0.2.0

Viewed 10K+ times! This question is

You Asked

Hello Tom,

Good Morning.

This is my first time posting question, I always got helped for my most of problems from your post.

Need your help in understanding what will be good way with respect of performance.

I have one table with date datatype column(register_date) but its storing datetimestamp as well so
1. when I am passing date directly from function in parameter data is not coming due to hour:minute:second is different.
where register_date = ai_registerdate -- ( variable format DD-MON-YYYY as nls_parameter ).
or
where register_date = TO_DATE(ai_registerdate) -- Not giving data as register_date having timestamp which is different for entire day.

2. We can use below options but not able to get which one is better or correct one.
where to_Date(register_date) = to_date(ai_registerdate)
or
where to_char(register_date,'YYYYMMDD') = to_char(ai_date, 'YYYYMMDD')

I got few post for similar question on this query but I did not get any answer what will be impact on performance using function in where clause and how we can improve performance of it as this is main column in my query, want index to be hit for this column.Consider this table will have 10 Million records and each day will have 1 to 3 Million records.

Thanks for you assistance in advance.

and Chris said...

First up, a couple of things:

- Oracle Database has no "day" data type. Dates always include the time too. You can effectively strip the time from a date using:

trunc(dt)


But this is really a date with the time of midnight.

- The NLS settings only affect display and implicit conversions. As long as you're comparing dates to dates, it doesn't matter what these are.

Notice how the code below selects a row from the table, regardless of the NLS date format:

create table t (
  d date
);
declare
  dt date;
  procedure count_matching as
    ct pls_integer;
  begin
    select count(*) into ct 
    from   t where d = dt;
  
    dbms_output.put_line('Found ' || ct || ' for date ' || dt);
  end count_matching;
begin
  dt := sysdate;
  insert into t values (dt);
  execute immediate q'|alter session set nls_date_format = 'yyyy-mm-dd'|';
  
  count_matching();  

  execute immediate q'|alter session set nls_date_format = 'dd-MON-yyyy hh24:mi:ss'|';
  
  count_matching();
end;
/

Found 1 for date 2017-11-13
Found 1 for date 13-NOV-2017 03:12:09


So:

1. If you have columns with specific times and you pass a "day" (a date with time of midnight), to find these rows you should:

- Check the column is greater than or equal to the parameter AND
- it's strictly less than the parameter plus one day

e.g.:

where register_date >= ai_registerdate and register_date < ai_registerdate + 1


You could trunc() both the parameter and column. But this affects your indexes. More on that below.

2. Both are "wrong"!

The first will have implicit conversions. The second does an unnecessary conversion from a date to a string.

Assuming ai_registerdate has the data type date, all you need is:

where register_date = ai_registerdate


If it's a string instead, you should to_date the parameter as appropriate.

where register_date = to_date(ai_registerdate, 'fmt mask')


Applying a function to the column stops the optimizer using an index on that column. So if you have

create index i on t (dt);


And a query:

where to_date(dt) = ...


You'll get a full table scan instead of an index range scan. So generally you want to avoid functions on your columns in the where clause. If you do apply a function in your index, you'll want to use the exact same function in your SQL.

You can read more about how indexes work in this article:

https://blogs.oracle.com/sql/how-to-create-and-use-indexes-in-oracle-database#choose

Search for "Function-based Indexes" for more discussion on this.

Rating

  (2 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Deepesh Chothani, November 14, 2017 - 6:13 am UTC

Thanks TOM for explaining in such great details.I ran below block its working fine but similar thing when checking with my code or with data at my database its not working so got confused as block given by you running fine even on my database.

You mentioned it check date less then mid-night and output which i have shown its less then midnight in below query (its hh24). Please apology in advance if any understanding issue.

Please find detail as below:
1. NSL detail for reference.
select * from V$nls_Parameters where parameter = 'NLS_DATE_FORMAT';
PARAMETER VALUE CON_ID
---------------- ----------- -------
NLS_DATE_FORMAT MM/DD/YYYY 0

2. Query to show how date is stored in database with and without trnc.
Runnign query on my table.
select to_char(to_date(register_Date), 'dd/mm/yyyy hh24:mi:ss') trunc,to_char(register_date, 'dd/mm/yyyy hh24:mi:ss') not_trunc from t1;

TRUNC NOT_TRUNC
------------------- -------------------
12/10/2017 00:00:00 12/10/2017 11:42:18
12/10/2017 00:00:00 12/10/2017 11:42:18
12/10/2017 00:00:00 12/10/2017 11:45:38
12/10/2017 00:00:00 12/10/2017 11:45:39
12/10/2017 00:00:00 12/10/2017 11:45:39

Now when i am running similar block its giving no record.
declare
ai_date date := '10/12/2017';
cnt number := 0;
begin
dbms_output.put_line('ai_Date:' || ai_date || 'cnt:' || cnt);
select count(*) into cnt from cpps_input_file where register_date = ai_date;
dbms_output.put_line('ai_Date:' || ai_date || 'cnt:' || cnt);
exception when others then
dbms_output.put_line('SQLERRM:' || SQLERRM || 'ai_Date:' || ai_date || 'cnt:' || cnt);
end;

-----output
ai_Date:10/12/2017cnt:0
ai_Date:10/12/2017cnt:0

why its not selecting data even though i have recod for 10/12/2017 but when i am using same block "trunc(register_date) = ai_date;" in above code its giving result as
ai_Date:10/12/2017cnt:0
ai_Date:10/12/2017cnt:13772

also if i used trunc() will have any issue and its better to create index with trunc or any specific care needs to be taken.




Chris Saxon
November 14, 2017 - 11:43 am UTC

So, you're setting:

ai_date date := '10/12/2017';


i.e. 10 Dec 2017 at midnight. Yet the columns in your table all have times on them!

So really, register_date = ai_date is comparing:

'12/10/2017 11:42:18' = '12/10/2017 00:00:00'


Do you see now why this returns nothing?

also if i used trunc() will have any issue and its better to create index with trunc or any specific care needs to be taken.

You can find full discussion of this in the linked blog post above. Look for the "Function-based Indexes" section.

date solution

Ajit, November 14, 2017 - 12:07 pm UTC

please try

Where register_date between ai_registerdate and ai_registerdate+0.99999
Chris Saxon
November 14, 2017 - 12:42 pm UTC

Please don't.

Check if the date is strictly less than the next ( < ai_dt + 1 ). Avoids any potential rounding issues you get by checking the upper bound equals the variable.

More to Explore

SQL

The Oracle documentation contains a complete SQL reference.