Quantcast
Channel: Oracle
Viewing all articles
Browse latest Browse all 1814

Blog Post: “That Ain’t Right”

$
0
0
"Is everyone who lives in Ignorance like you?" asked Milo. "Much worse," he said longingly. "But I don't live here. I'm from a place very far away called Context." -- Norton Juster, The Phantom Tollbooth Dates are, well, dates. They are not strings, they are not numbers, they are dates and to handle them properly the correct data type needs to be used. I can’t count how many times I’ve seen dates stored as VARCHAR2 strings or as numbers, neither of which treat them properly or order them correctly. Recently in a forum I frequent this question was asked: Version Oracle 11 g R2 Hello every body First of All i know the Format is wrong but as it is a old project i need to extract the report as it is there is a table with two fields and data as follow create table Onduty_leave ( Start_time varchar2(100), end_time varchar2(100) ); insert into onduty_leave (start_time,end_time) values('09.00 am','06.00 pm'); Minutes i am handling differently Please just tell me how to calculate hours between 09 am to 06 pm Yes, this person realizes that the data type is incorrect for the data being stored, but the fact remains someone thought this was a good idea and implemented it into a production system. Of course it’s possible to provide the results the person posting this question is after, but it would be so much easier had the correct data type been used. Be that as it may let’s proceed with the current table definition and show what can be done to calculate the desired quantity. Let’s build and populate the table with some simple code: SQL> SQL> -- SQL> -- Create the table with the improper data type SQL> -- SQL> create table Onduty_leave 2 ( 3 Start_time varchar2(100), 4 end_time varchar2(100) 5 ); Table created. SQL> SQL> -- SQL> -- Populate the table SQL> -- SQL> begin 2 for i in 1..10 loop 3 insert into onduty_leave 4 values(sysdate-i, sysdate+i); 5 end loop; 6 7 commit; 8 end; 9 / PL/SQL procedure successfully completed. SQL> SQL> -- SQL> -- Set the display format to see the date and time SQL> -- SQL> alter session set nls_date_format = 'DD-MON-RRRR HH24:MI:SS'; Session altered. SQL> SQL> -- SQL> -- Format the strings for display SQL> -- SQL> column start_time format a25 SQL> column end_time format a25 SQL> We are now ready to compute the hours between the two provided dates in each record. Since these are VARCHAR2 strings it will be necessary to use TO_CHAR and TO_DATE to get proper results. SQL> -- SQL> -- 24 hours per day, find days between and multiply SQL> -- This uses the entire date string to provide correct SQL> -- results SQL> -- SQL> SQL> select (to_date(end_time) - to_date(start_time))*24 hours_between 2 from onduty_leave; HOURS_BETWEEN ------------- 48 96 144 192 240 288 336 384 432 480 10 rows selected. SQL> It isn’t rocket science but it is a bit more work than we should be doing to get these answers. The data in the table was generated simply; let’s generate some new data that may more accurately reflect real-world entries into this table: SQL> -- SQL> -- Purge all current data SQL> -- SQL> truncate table onduty_leave; Table truncated. SQL> SQL> -- SQL> -- Populate table with more realistic records SQL> -- SQL> begin 2 for i in 1..10 loop 3 insert into onduty_leave 4 values(sysdate, sysdate+((25*i)/24)); 5 end loop; 6 7 commit; 8 end; 9 / PL/SQL procedure successfully completed. SQL> Using just the time from each entry let’s compute the new hours difference between them: SQL> -- SQL> -- Hours between entries SQL> -- Use only the time portion of the date SQL> -- SQL> -- This produces wrong results since the date SQL> -- isn't included in the calculation SQL> -- SQL> select start_time, end_time, 2 (to_date(to_char(to_date(end_time), 'HH24:MI:SS'), 'HH24:MI:SS') - to_date(to_char(to_date(start_time), 'HH24:MI:SS'), 'HH24:MI:SS'))*24 hours_between 3 from onduty_leave; START_TIME END_TIME HOURS_BETWEEN ------------------------- ------------------------- ------------- 17-FEB-2016 09:12:28 18-FEB-2016 10:12:28 1 17-FEB-2016 09:12:28 19-FEB-2016 11:12:28 2 17-FEB-2016 09:12:28 20-FEB-2016 12:12:28 3 17-FEB-2016 09:12:28 21-FEB-2016 13:12:28 4 17-FEB-2016 09:12:28 22-FEB-2016 14:12:28 5 17-FEB-2016 09:12:28 23-FEB-2016 15:12:28 6 17-FEB-2016 09:12:28 24-FEB-2016 16:12:28 7 17-FEB-2016 09:12:28 25-FEB-2016 17:12:28 8 17-FEB-2016 09:12:28 26-FEB-2016 18:12:28 9 17-FEB-2016 09:12:28 27-FEB-2016 19:12:28 10 10 rows selected. SQL> The initial request was flawed by assuming each record would reference a single day; even though this may be true for the actual data the solution cannot rely on such assumptions. Obviously the results from that last query were wrong; let’s use the full date string and see what values we get: SQL> -- SQL> -- 24 hours per day, find days between and multiply SQL> -- SQL> -- Use full date string to compute the hours between SQL> -- entries SQL> -- SQL> SQL> select start_time, end_time, 2 (to_date(end_time) - to_date(start_time))*24 hours_between 3 from onduty_leave; START_TIME END_TIME HOURS_BETWEEN ------------------------- ------------------------- ------------- 17-FEB-2016 09:12:28 18-FEB-2016 10:12:28 25 17-FEB-2016 09:12:28 19-FEB-2016 11:12:28 50 17-FEB-2016 09:12:28 20-FEB-2016 12:12:28 75 17-FEB-2016 09:12:28 21-FEB-2016 13:12:28 100 17-FEB-2016 09:12:28 22-FEB-2016 14:12:28 125 17-FEB-2016 09:12:28 23-FEB-2016 15:12:28 150 17-FEB-2016 09:12:28 24-FEB-2016 16:12:28 175 17-FEB-2016 09:12:28 25-FEB-2016 17:12:28 200 17-FEB-2016 09:12:28 26-FEB-2016 18:12:28 225 17-FEB-2016 09:12:28 27-FEB-2016 19:12:28 250 10 rows selected. SQL> This looks much better, but it’s still based on date values stored as strings. With the format specified we can successfully order the data for a single month, but when we add data for other months and sort it ‘strange’ things can happen: SQL> -- SQL> -- Populate table with more records SQL> -- SQL> begin 2 for i in 11..100 loop 3 insert into onduty_leave 4 values(sysdate, sysdate+((25*i)/24)); 5 end loop; 6 7 commit; 8 end; 9 / PL/SQL procedure successfully completed. SQL> SQL> -- SQL> -- Sort the data SQL> -- SQL> select * 2 from onduty_leave 3 order by 2; START_TIME END_TIME ------------------------- ------------------------- 17-FEB-2016 09:29:04 01-APR-2016 03:29:04 17-FEB-2016 09:29:04 01-MAR-2016 22:29:04 17-FEB-2016 09:29:04 01-MAY-2016 08:29:04 17-FEB-2016 09:29:04 02-APR-2016 04:29:04 17-FEB-2016 09:29:04 02-MAR-2016 23:29:04 17-FEB-2016 09:29:04 02-MAY-2016 09:29:04 17-FEB-2016 09:29:04 03-APR-2016 05:29:04 17-FEB-2016 09:29:04 03-MAY-2016 10:29:04 17-FEB-2016 09:29:04 04-APR-2016 06:29:04 17-FEB-2016 09:29:04 04-MAR-2016 00:29:04 17-FEB-2016 09:29:04 04-MAY-2016 11:29:04 17-FEB-2016 09:29:04 05-APR-2016 07:29:04 17-FEB-2016 09:29:04 05-MAR-2016 01:29:04 17-FEB-2016 09:29:04 05-MAY-2016 12:29:04 17-FEB-2016 09:29:04 06-APR-2016 08:29:04 17-FEB-2016 09:29:04 06-MAR-2016 02:29:04 17-FEB-2016 09:29:04 06-MAY-2016 13:29:04 17-FEB-2016 09:29:04 07-APR-2016 09:29:04 17-FEB-2016 09:29:04 07-MAR-2016 03:29:04 17-FEB-2016 09:29:04 07-MAY-2016 14:29:04 17-FEB-2016 09:29:04 08-APR-2016 10:29:04 17-FEB-2016 09:29:04 08-MAR-2016 04:29:04 ... 100 rows selected. SQL> Oracle doesn’t know these are dates, so they are sorted by as ASCII text making the order anything but logical, as far as dates go. Let’s drop the orginal table, recreate with the proper date type and try this again: SQL> -- SQL> -- Drop the original table SQL> -- SQL> drop table onduty_leave purge; Table dropped. SQL> SQL> -- SQL> -- Create the table with the proper data type SQL> -- SQL> create table Onduty_leave 2 ( 3 Start_time date, 4 end_time date 5 ); Table created. SQL> SQL> -- SQL> -- Populate the table SQL> -- SQL> begin 2 for i in 1..10 loop 3 insert into onduty_leave 4 values(sysdate, sysdate+((25*i)/24)); 5 end loop; 6 7 commit; 8 end; 9 / PL/SQL procedure successfully completed. SQL> SQL> -- SQL> -- Compute the hours between entries for a given record SQL> -- SQL> select (end_time - start_time)*24 hours_between 2 from onduty_leave; HOURS_BETWEEN ------------- 25 50 75 100 125 150 175 200 225 250 10 rows selected. SQL> SQL> -- SQL> -- Populate table with more records SQL> -- SQL> begin 2 for i in 11..100 loop 3 insert into onduty_leave 4 values(sysdate, sysdate+((25*i)/24)); 5 end loop; 6 7 commit; 8 end; 9 / PL/SQL procedure successfully completed. SQL> SQL> -- SQL> -- Sort the data SQL> -- SQL> select * 2 from onduty_leave 3 order by 2; START_TIME END_TIME ------------------------- ------------------------- 17-FEB-2016 09:29:05 18-FEB-2016 10:29:05 17-FEB-2016 09:29:05 19-FEB-2016 11:29:05 17-FEB-2016 09:29:05 20-FEB-2016 12:29:05 17-FEB-2016 09:29:05 21-FEB-2016 13:29:05 17-FEB-2016 09:29:05 22-FEB-2016 14:29:05 17-FEB-2016 09:29:05 23-FEB-2016 15:29:05 17-FEB-2016 09:29:05 24-FEB-2016 16:29:05 17-FEB-2016 09:29:05 25-FEB-2016 17:29:05 17-FEB-2016 09:29:05 26-FEB-2016 18:29:05 17-FEB-2016 09:29:05 27-FEB-2016 19:29:05 17-FEB-2016 09:29:05 28-FEB-2016 20:29:05 START_TIME END_TIME ------------------------- ------------------------- 17-FEB-2016 09:29:05 29-FEB-2016 21:29:05 17-FEB-2016 09:29:05 01-MAR-2016 22:29:05 17-FEB-2016 09:29:05 02-MAR-2016 23:29:05 17-FEB-2016 09:29:05 04-MAR-2016 00:29:05 17-FEB-2016 09:29:05 05-MAR-2016 01:29:05 17-FEB-2016 09:29:05 06-MAR-2016 02:29:05 17-FEB-2016 09:29:05 07-MAR-2016 03:29:05 17-FEB-2016 09:29:05 08-MAR-2016 04:29:05 17-FEB-2016 09:29:05 09-MAR-2016 05:29:05 17-FEB-2016 09:29:05 10-MAR-2016 06:29:05 17-FEB-2016 09:29:05 11-MAR-2016 07:29:05 START_TIME END_TIME ------------------------- ------------------------- 17-FEB-2016 09:29:05 12-MAR-2016 08:29:05 17-FEB-2016 09:29:05 13-MAR-2016 09:29:05 17-FEB-2016 09:29:05 14-MAR-2016 10:29:05 17-FEB-2016 09:29:05 15-MAR-2016 11:29:05 17-FEB-2016 09:29:05 16-MAR-2016 12:29:05 17-FEB-2016 09:29:05 17-MAR-2016 13:29:05 17-FEB-2016 09:29:05 18-MAR-2016 14:29:05 17-FEB-2016 09:29:05 19-MAR-2016 15:29:05 17-FEB-2016 09:29:05 20-MAR-2016 16:29:05 17-FEB-2016 09:29:05 21-MAR-2016 17:29:05 17-FEB-2016 09:29:05 22-MAR-2016 18:29:05 START_TIME END_TIME ------------------------- ------------------------- 17-FEB-2016 09:29:05 23-MAR-2016 19:29:05 17-FEB-2016 09:29:05 24-MAR-2016 20:29:05 17-FEB-2016 09:29:05 25-MAR-2016 21:29:05 17-FEB-2016 09:29:05 26-MAR-2016 22:29:05 17-FEB-2016 09:29:05 27-MAR-2016 23:29:05 17-FEB-2016 09:29:05 29-MAR-2016 00:29:05 17-FEB-2016 09:29:05 30-MAR-2016 01:29:05 17-FEB-2016 09:29:05 31-MAR-2016 02:29:05 17-FEB-2016 09:29:05 01-APR-2016 03:29:05 17-FEB-2016 09:29:05 02-APR-2016 04:29:05 17-FEB-2016 09:29:05 03-APR-2016 05:29:05 START_TIME END_TIME ------------------------- ------------------------- 17-FEB-2016 09:29:05 04-APR-2016 06:29:05 17-FEB-2016 09:29:05 05-APR-2016 07:29:05 17-FEB-2016 09:29:05 06-APR-2016 08:29:05 17-FEB-2016 09:29:05 07-APR-2016 09:29:05 17-FEB-2016 09:29:05 08-APR-2016 10:29:05 17-FEB-2016 09:29:05 09-APR-2016 11:29:05 17-FEB-2016 09:29:05 10-APR-2016 12:29:05 17-FEB-2016 09:29:05 11-APR-2016 13:29:05 17-FEB-2016 09:29:05 12-APR-2016 14:29:05 17-FEB-2016 09:29:05 13-APR-2016 15:29:05 17-FEB-2016 09:29:05 14-APR-2016 16:29:05 START_TIME END_TIME ------------------------- ------------------------- 17-FEB-2016 09:29:05 15-APR-2016 17:29:05 17-FEB-2016 09:29:05 16-APR-2016 18:29:05 17-FEB-2016 09:29:05 17-APR-2016 19:29:05 17-FEB-2016 09:29:05 18-APR-2016 20:29:05 17-FEB-2016 09:29:05 19-APR-2016 21:29:05 17-FEB-2016 09:29:05 20-APR-2016 22:29:05 17-FEB-2016 09:29:05 21-APR-2016 23:29:05 17-FEB-2016 09:29:05 23-APR-2016 00:29:05 17-FEB-2016 09:29:05 24-APR-2016 01:29:05 17-FEB-2016 09:29:05 25-APR-2016 02:29:05 17-FEB-2016 09:29:05 26-APR-2016 03:29:05 START_TIME END_TIME ------------------------- ------------------------- 17-FEB-2016 09:29:05 27-APR-2016 04:29:05 17-FEB-2016 09:29:05 28-APR-2016 05:29:05 17-FEB-2016 09:29:05 29-APR-2016 06:29:05 17-FEB-2016 09:29:05 30-APR-2016 07:29:05 17-FEB-2016 09:29:05 01-MAY-2016 08:29:05 17-FEB-2016 09:29:05 02-MAY-2016 09:29:05 17-FEB-2016 09:29:05 03-MAY-2016 10:29:05 17-FEB-2016 09:29:05 04-MAY-2016 11:29:05 17-FEB-2016 09:29:05 05-MAY-2016 12:29:05 17-FEB-2016 09:29:05 06-MAY-2016 13:29:05 17-FEB-2016 09:29:05 07-MAY-2016 14:29:05 START_TIME END_TIME ------------------------- ------------------------- 17-FEB-2016 09:29:05 08-MAY-2016 15:29:05 17-FEB-2016 09:29:05 09-MAY-2016 16:29:05 17-FEB-2016 09:29:05 10-MAY-2016 17:29:05 17-FEB-2016 09:29:05 11-MAY-2016 18:29:05 17-FEB-2016 09:29:05 12-MAY-2016 19:29:05 17-FEB-2016 09:29:05 13-MAY-2016 20:29:05 17-FEB-2016 09:29:05 14-MAY-2016 21:29:05 17-FEB-2016 09:29:05 15-MAY-2016 22:29:05 17-FEB-2016 09:29:05 16-MAY-2016 23:29:05 17-FEB-2016 09:29:05 18-MAY-2016 00:29:05 17-FEB-2016 09:29:05 19-MAY-2016 01:29:05 START_TIME END_TIME ------------------------- ------------------------- 17-FEB-2016 09:29:05 20-MAY-2016 02:29:05 17-FEB-2016 09:29:05 21-MAY-2016 03:29:05 17-FEB-2016 09:29:05 22-MAY-2016 04:29:05 17-FEB-2016 09:29:05 23-MAY-2016 05:29:05 17-FEB-2016 09:29:05 24-MAY-2016 06:29:05 17-FEB-2016 09:29:05 25-MAY-2016 07:29:05 17-FEB-2016 09:29:05 26-MAY-2016 08:29:05 17-FEB-2016 09:29:05 27-MAY-2016 09:29:05 17-FEB-2016 09:29:05 28-MAY-2016 10:29:05 17-FEB-2016 09:29:05 29-MAY-2016 11:29:05 17-FEB-2016 09:29:05 30-MAY-2016 12:29:05 START_TIME END_TIME ------------------------- ------------------------- 17-FEB-2016 09:29:05 31-MAY-2016 13:29:05 100 rows selected. SQL> Proper date arithmetic can be performed and the dates sort correctly since Oracle now knows they are dates. Using the correct data type for the data being used is critical; storing data in a format other than what should be used can create problems with applications and can result in incorrect values. Analytical functions can produce erroneous output when data isn’t using the correct type, especially functions like lead(), lag() and sum() that can partition and order data to provide ‘rolling’ results. And if you can manipulate the data to provide correct answers it usually requires much more work than would be necessary using the proper data type. Keep that in mind when you design tables and applications. Context is everything.

Viewing all articles
Browse latest Browse all 1814

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>