Parse time strings with different formats and compare them - sql

I am running a query between several tables and I am running into an issue between comparing two time columns on separate tables: "rc1_time" is in a string format and "osemplog_time" is in a time format. both are time only with no date
rc1_time's contents look like this '10560684' which corresponds to HH24MISSMS
osemplog_time's contents look like 07:57:02.917455
how do I format the rc1_time into a "time format" with no date?
what are some options for comparing the two times?
I am newbie at this exposition on your answers would be welcome
below is my query
SELECT
"public".payroll_master.prm1_name,
"public".payroll_master.prm1_oe_init,
"public".receipt.rc1_init,
"public".employee_log.osemplog_ipaddress,
"public".employee_log.osemplog_event,
"public".receipt.rc1_date,
"public".employee_log.osemplog_logdate,
"public".receipt.rc1_code,
"public".employee_log.osemplog_logname,
"public".oslogname.lognm_empname,
"public".receipt.rc1_arname,
"public".receipt.rc1_arnum,
"public".receipt.rc1_time,
"public".employee_log.osemplog_logtime
FROM
"public".receipt
INNER JOIN "public".employee_log ON "public".receipt.rc1_date = "public".employee_log.osemplog_logdate
INNER JOIN "public".payroll_master ON "public".payroll_master.prm1_oe_init = "public".receipt.rc1_init
INNER JOIN "public".oslogname ON "public".oslogname.lognm_empname = "public".payroll_master.prm1_name AND "public".oslogname.lognm_name = "public".employee_log.osemplog_logname
WHERE
"public".receipt.rc1_code = 'CA'
AND
"public".employee_log.osemplog_logdate = "public".receipt.rc1_date
ORDER BY
"public".receipt.rc1_init ASC

Question as stated
You can represent a time without a date using the time data type. To convert a string from a given format into one, you can go through the to_timestamp function and then cast to time:
SELECT to_timestamp('10560684', 'HH24MISSUS')::time;
SELECT to_timestamp('07:57:02.917455', 'HH24:MI:SS.US')::time;
The basic idea is that you parse the time string using to_timestamp. The resulting timestamp will have a default date, and casting to time will remove the date, leaving only the parsed out time portion.
Assumptions:
Your hours are in 24-hour clock format (13-23 for 1 PM to 11 PM and 00 for midnight). If they are not 24 hour times, then you are missing the AM/PM designation and will need to sort that out.
The second "SS" you mention in your first pattern is actually a fractional part of seconds. If not, you'll need to adjust the pattern. If you don't care about the fractional seconds, you might consider just leaving the US and the .US off entirely and working only at the seconds level. Note that US interprets 84 to be 0.84 seconds, not actually 84 microseconds (0.000084 seconds).
Ultimately, you will need to either provide much more precise details about the format or figure out the correct format string yourself. Rather than worry about those details, I've tried to exemplify the general mechanism and leave those to you.
Comparison is then trivial. You just use PostgreSQL's operators (<, >, =, etc.):
SELECT to_timestamp('07:57:02.917455', 'HH24:MI:SS.US')::time < to_timestamp('10560684', 'HH24MISSUS')::time;
Other considerations
Be aware of time zone issues if you are working across them. You'll want to look at timetz (short form of time with time zone) or timestamptz (short form of timestamp with time zone) if you need to deal with time zones. Generally, I would recommend including time zone handling up front in case it becomes a problem later.
In this case, why not build a complete timestamp? You already have the dates: "public".receipt.rc1_date and "public".employee_log.osemplog_logdate.
You don't specify the data types, but whatever the forms of those are, it should be possible. For example, if they are actual date objects, then:
SELECT to_timestamp(to_char("public".receipt.rc1_date, 'YYYY-MM-DD')||' '||"public".receipt.rc1_time, 'YYYY-MM-DD HH24MISSMS');
If they are strings of the form 'YYYY-MM-DD', then:
SELECT to_timestamp("public".receipt.rc1_date||' '||"public".receipt.rc1_time, 'YYYY-MM-DD HH24MISSMS');
And so on. Now you have a real timestamp, which makes simple great/less than comparison much, much easier.
In my experience, it's extremely rare that you actually want to test time stamps with fractional second precision for equality. You might want a more tolerant equality check, something like SELECT t1 - t2 < interval '5 seconds', but this is really up to the application.

Related

Find entries in sql that were done milliseconds against each other

I need to debug an issue I found with someone else's code, where it reads both the id and the timestamp for a key. But, I noticed that it is not reading the millisecond information. While this is a potential cause, this not confirmed, and I need to find out if this is the cause.
The problem could occur if two entries in the table happened within the same second, 10:20:05.0500 pm and 10:20:05.5000 pm, but not 10:20:05.5000 and 10:20:06.0500.
How do I write such a query to look for it?
I am using Oracle pl/sql.
In Oracle to use fractional seconds a column must be of data type "TIMESTAMP".
Maybe someone else's code is using a variable of type DATE, which is year-month-day hour-minute-second without fractional seconds.
Database SQL Language Reference: Data Types
Can you give the table description and someone else's code?
To find records with the same date/time up to second precision but with different milliseconds, you can compare different records by joining the table to itself
SELECT A.ts, B.ts
FROM
Test A
INNER JOIN Test B
ON TO_CHAR(A.ts, 'YYYY-MM-DD HH24:MI:SS') = TO_CHAR(B.ts, 'YYYY-MM-DD HH24:MI:SS')
WHERE
A.ts < B.ts
ORDER BY
A.ts, B.ts;
TO_CHAR truncates the milliseconds. This is important, because functions that round could yield different seconds. E.g., CAST(ts as timestamp(0)) rounds, which is not what wee need.
The example from the link below has a record with 999 milliseconds to test this.
See example on SQL Fiddle.

Convert TIMESTAMPL (timestamp long) to TIMESTAMP ends with 60

I am having an OData Service returning some DateTime values. They are saved in a table in the back end as TIMESTAMPL (with some other data).
Now there is the value 20160630084459.5000. With MOVE-CORRESPONDING into the et_entityset, where it is a TIMESTAMP. Because of the rounding, it gets 20160630084460, Since a the seconds must be between 00 and 59, this is not a valid value to return.
My main problem is, that my table has extremely much entries, so I need a performant way to fix this error.
Here is a way to convert it to what you want.
REPORT zzy NO STANDARD PAGE HEADING.
FORM convert_timestamp.
DATA(l_t1) = CONV timestampl('20160630084459.5000').
DATA: l_t2 TYPE timestamp.
l_t2 = l_t1.
WRITE / : l_t1, l_t2.
CONVERT TIME STAMP l_t1 TIME ZONE sy-zonlo INTO DATE DATA(l_date) TIME DATA(l_time).
CONVERT DATE l_date TIME l_time INTO TIME STAMP l_t2 TIME ZONE sy-zonlo.
WRITE / l_t2.
ENDFORM.
START-OF-SELECTION.
PERFORM convert_timestamp.
Here is the output.
20.160.630.084.459,5000000
20.160.630.084.460
20.160.630.084.459
You mention floor in your question but that is not what is happening. The value is rounded. If you simple do use FLOOR in your assignment from TIMESTAMPL to TIMESTAMP you will get the answer you want. If you have to use MOVE-CORRESPONDING, just do that first and then do a seperate assignment for the timestamp.
However, this means that 0:59.9 will get translated to 0:59 and not 1:00. If that missing second is OK for your application then just use the FLOOR command. If not it becomes more complicated and you will take a performance hit.

Unknown SQL coding issue in Oracle SQL Developer

I'm writing an SQL statment that is supposed to do a count based on a date range. But, for some reason no data is being returned. Before I try and filter the count with my date range, everything works fine. Here is that code.
SELECT
CR.GCR_RFP_ID
,S.RFP_RECEIVED_DT
,CR.GCR_RECEIVED_DT
,CT.GCT_LOB_IND
FROM ADM.GROUP_CHANGE_TASK_FACT CT
JOIN ADM.B_GROUP_CHANGE_REQUEST_DIM CR
ON CR.GROUP_CHANGE_REQUEST_KEY = CT.GROUP_CHANGE_REQUEST_KEY
JOIN ADM.B_RFP_WC_COVERAGE_DIM S
ON S. RFP_ID = CR.GCR_RFP_ID
WHERE CT.GCT_LOB_IND = 'WC'
AND CR.GCR_CHANGE_TYPE_ID IN ('10','20','30','50','60','70','80','90','100','110',
'120','130','140', '150','160','170','180','190','200',
'210','220','230','240','260','270','280','300','310',
'320','330','340','350','360','370','371','372')
AND S.RFP_AUDIT_IND = 'N'
AND S.RFP_TYPE_IND = 'A'
The date field I'm using is called CR.GCR_RECIEVED_DT. This is a new field a in the db and all the records are 01-JAN-00. But I'm still doing the count just to make sure I can grab the data. Now, I added this line:
AND CR.GCR_RECEIVED_DT LIKE '01-JAN-00'
just as a random test thing. I know all the dates are the same. And it works fine, no issues. So I remove that line and replace it with this:
AND CR.GCR_RECEIVED_DT BETWEEN '31-DEC-99' AND '02-JAN-00'
I used this small range to keep it simple. But even though 01-JAN-00 deffinetly falls between those two dates, no data is returned. I have no idea why this is happening. I even tried this line to:
AND CR.GCR_RECEIVED_DT = '01-JAN-00'
and I still don't get data returned. It only seems to work with LIKE. I have checked and the field is a date type. Any help wold be much appreciated.
If your NLS_DATE_FORMAT is set to DD-MON-YY then the apparent discrepancy between the first two results can be explained.
When you use LIKE it implicitly converts the date value on the left-hand side to a string for the comparison, using the default format model, and then compares that to the fixed string; and '01-JAN-00' is like '01-JAN-00'. You're effectively doing:
AND TO_CHAR(CR.GCR_RECEIVED_DT, 'DD-MON-YY') LIKE '01-JAN-00'
Using LIKE to compare dates doesn't really make any sense though. When you use BETWEEN, though, the left-hand side is being left as a date, so you're effectively doing:
AND CR.GCR_RECEIVED_DT BETWEEN TO_DATE('31-DEC-99', 'DD-MON-YY')
AND TO_DATE('02-JAN-00', 'DD-MON-YY')
... and TO_DATE('31-DEC-99', 'DD-MON-YY') is December 31st 2099, not 1999. BETWEEN only works when the first value is lower than the second (from the docs, 'If expr3 < expr2, then the interval is empty'). So you're looking for values bwteen 2099 and 2000, and that will always be empty. If your date model was DD-MON-RR, from the NLS parameter or explicitly via TO_DATE, then it would be looking for values between 1999 and 2000, and would find your records.
Your third result is a little more speculative but suggests that the values in your GCR_RECEIVED_DT field have a time component, or are not in the century you think. This is similar to the LIKE version, except this time the fixed string is being converted to a date, rather than the date being converted to a string; effectively:
AND CR.GCR_RECEIVED_DT = TO_DATE('01-JAN-00', 'DD-MON-YY')
If they were at midnight on 2000-01-01 this would work. Because it doesn't that suggests they are either some time after midnight, or maybe more likely - since you're using a 'magic' date in your existing records - they are another date entirely, quite possibly 1900-01-01.
Here are SQL Fiddles for just past midnight and 1900.
If the field will eventually have a time component for new records you might want to structure the condition like this, and use date literals to be a bit clearer (IMO):
AND CR.GCR_RECEIVED_DT >= DATE '2000-01-01'
AND CR.GCR_RECEIVED_DT < DATE '2000-01-02'
That will find any records at any time on 2000-01-01, and can use an index on that column if one is available. BETWEEN is inclusive, so using BETWEEN DATE '2000-01-01' AND '2000-01-02' would include any records that are exactly at midnight on the later date, which you probably don't want.
Whatever you end up doing, avoid relying on implicit conversions using NLS_DATE_FORMAT as one day it might not be set to what you expect, causing potentially data-corrupting or hard to find bugs; and specify the full four-digit year in the model if you can to avoid ambiguity.
try something like this:
WHERE TRUNC(CR.GCR_RECEIVED_DT) = TO_DATE('01-JAN-00','DD-Mon-YY')
TRUNC without parameter removes hours, minutes and seconds from a DATE.

Storing "Time" in database- What to use DateTime/Interger/VarChar

I want to save Date and Time of the user on various activities performed. For date I have decided to use DateTime Column in Database and for Time I am in dilemma what datatype to go for.
I know in sql server 2008 Time datatype has been introduced but I am using older version i.e. Sql Server 2005 so I need your suggest to prove my understanding true or false.
I have seen people using varchar or DateTime for storing time into database. But I am looking towards usage of Integer datatype.
Reason for my selection is performance.
Following is the justification that I am giving to myself.
Assumptions
Any data saved into database must agree following rules
Date will be stored in format mm/dd/yyyy hh:MM:ss where hh:MM:ss will always be 00:00:00
Time will be stored in valid format (from hh:MM:ss as hhMMss)
if hh is 00
then MMss
and if MM is 00
then ss
and if ss is 00
then 0
hh will range in between 0-23
MM will range in between 0-59
ss will range in between 0-59
i.e. few examples
00:00:00 = 0
00:01:00 = 100
01:00:00 = 10000
13:00:00 = 130000
Personal thought why it will perform better.
SELECT * FROM Log WHERE loginDate = '05/23/2011'
AND loginTime BETWEEN 0 AND 235959 --Integer Comparison
When using JOINS on the basis of DateTime considering join for Date part only.
JOIN two tables on the basis of Common Dates irrespective of Time.I think Type Conversion would heavily impact in such cases if using DateTime as the storage datatype.
Since Sql will have to do an integer comparison and no typecasting would be required hence it should perform better.
EDIT
One drawback I just identified is when I want to get the difference between two times that how much time has been spent between 3 days, hopefully then it would become a nightmare to manage throughout the application.
So why do you need 2 columns. If the DateTime column (loginDate) has an empty time 00:00:00 why not just use that empty space for loginTime and have one column.
WHERE loginDate >= '05/23/2011' AND loginDate < '05/24/2011'
If you're intent on using an integer, there's nothing wrong with it.
Bearing your edit in mind, your ideal solution is to put both date and time in the same column, a DATETIME:
You can then trivially figure the difference between start and end times with DATEDIFF
You can easily establish just the date portion with CONVERT(varchar(10), loginDate, 101)
You can easily establish just the time portion with CONVERT(varchar(10), loginDate, 108)
Storage issues might be resolved by using SMALLDATETIME, if precision < 1minute isn't required. SMALLDATETIME requires four bytes per column, which is the same as INTEGER, so you're making a significant net gain over using two columns.

SQL Timstamp Function

Is there any difference between these two queries?
select * from tbl where ts < '9999-12-31-24.00.00.000000';
and
select * from tbl where ts < timestamp('9999-12-31-24.00.00.000000');
When is the timestamp function required?
Is there a difference in performance?
If ts is a string type:
1st one is comparing like for like as strings
2nd one will cause ts to be converted to date/time
If ts is a date/time type,
1st one will convert the constant to the same date time type as the ts column
2nd one is comparing like for like as date/time
If ts is string type, the 2nd one is worst to use because ts will be converted thus invalidating any indexes.
If ts is date/time, there is no difference
Data type precedence applies to most DB engines
At first glance, the first statement will make a string comparison while the second should make date related comparisons.
It depends on your SQL implementation, although assuming the column "ts" is some date/time type, there's practically no difference.
Most SQL implementations have a cast mapping that instructs the engine how to automatically convert data types that fit some pattern, especially if a literally comparison makes no sense. For example, literally comparing a timestamp to a string doesn't make much sense. However, most engines have a mapping that lets it know a string formatted like a date can be automatically converted to a timestamp, in order to be compared to another timestamps.