Update Oracle table records timezone values - sql

I have a table with an Oracle column of TIMESTAMP (6) WITH TIME ZONE. The table contains records of varying timezones. I'd like to update all of the records of the table to be UTC. Is there a recommended way of doing this in an UPDATE query? I've looked at the Oracle methods to_date(), which is for converting a string to a date, and from_tz() which converts a time to a time with timezone.
It seems like I'd need a way to run a query and pull the timezone from the field, and then somehow update the field to put it in UTC. I don't want to simply change the timezone designation, I want to offset the time of day so
21-JAN-10 03.28.38.713000000 PM -05:00
would become
21-JAN-10 08.28.38.713000000 PM UTC
.
CREATE TABLE "MyDb"."Books"
(
"GUID" RAW(32) DEFAULT SYS_GUID(),
"DATE_CREATED" TIMESTAMP (6) WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
);

You should be able to do something like
UPDATE "Books"
SET date_created = date_created at time zone 'UTC'
which will do something like this
SQL> create table foo( col1 timestamp with time zone );
Table created.
SQL> insert into foo values( current_timestamp );
1 row created.
SQL> select * from foo;
COL1
---------------------------------------------------------------------------
13-FEB-12 01.38.42.372000 PM -05:00
SQL> update foo
2 set col1 = col1 at time zone 'UTC';
1 row updated.
SQL> select * from foo;
COL1
---------------------------------------------------------------------------
13-FEB-12 06.38.42.372000 PM UTC
Now, as a style matter, creating case-sensitive table names and column names is highly discouraged as it will greatly annoy the developers that have to maintain the code.

Related

How to split a datetime column in to two columns of Date and time separately in Oracle SQL?

I have a datetime column.
I want two columns: a date and a time column.
How can I split my column into two?
Use:
a DATE data-type with the time component set to midnight for the date (you can enforce this with a check constraint); and
an INTERVAL DAY(0) TO SECOND data-type for the time component.
CREATE TABLE table_name(
datetime_column DATE,
date_column DATE,
time_column INTERVAL DAY(0) TO SECOND,
CONSTRAINT table_name__date_column__chk CHECK (date_column = TRUNC(date_column))
)
If you want to get the combined date-time then you can easily add the two to get back to a date-time value.
How can I split my column into two?
Assuming you have the columns you can use:
UPDATE table_name
SET date_column = TRUNC(datetime_column),
time_column = (datetime_column - TRUNC(datetime_column)) DAY TO SECOND;
db<>fiddle here
As Gordon commented, there's no time datatype in Oracle.
Though, literally answering what you asked, you can separate date and time and store each of them into their own columns - it's just that these will be VARCHAR2 columns and you can only look at how pretty they are. You can't, for example, do any date arithmetic on them; first you'd have to convert them back to date datatype, so question is what you really want to do with what you get.
Anyway, here you are:
SQL> create table test
2 (datum date,
3 date_only varchar2(10),
4 time_only varchar2(8)
5 );
Table created.
Sample value:
SQL> insert into test (datum) values (sysdate);
1 row created.
Split date to two parts:
SQL> update test set
2 date_only = to_char(datum, 'dd.mm.yyyy'),
3 time_only = to_char(datum, 'hh24:mi:ss');
1 row updated.
What's in there?
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:mi:ss';
Session altered.
SQL> select * from test;
DATUM DATE_ONLY TIME_ONL
------------------- ---------- --------
05.08.2021 21:05:06 05.08.2021 21:05:06
SQL>
Since there is no specific datatype for time, here my suggestion would be to keep the datetime in main column and add two VIRTUAL COLUMN for date value and time value respectively.
Oracle 11g has introduced a new feature that allows you to create a VIRTUAL COLUMN, an empty column that contains a function upon other table columns (the function itself is stored in the data dictionary).
However, it all depends on what you are going to do with it.
Please elaborate your requirement so that you will get a more specific answer.

Comparing with date in Oracle sql

I have a column 'creation_date' which is of type 'date', when I am querying my table for distinct records based on 'creation_date' I am getting 6 records:
select distinct creation_date from test_table;
output:
06-APR-11
06-APR-11
28-MAR-11
06-APR-11
06-APR-11
18-MAR-11
In this output 6th April is displayed 4 times even when I used distinct in my query. Also when I am trying to find out all records which are matching with creation_date of 6th April 2011 I am not getting any results. Below is my query:
select * from test_table where creation_date = to_date('06-APR-11','DD-MON-YY');
Please help me where I am doing wrong in these two queries.
The problem is twofold. Firstly the dates almost definitely have time-components. to_date('06-MAR-11','DD-MON-YY') is equivalent to 2011/03/06 00:00:00. If you use the TRUNC() function you will be able to see everything for that day:
select *
from test_table
where trunc(creation_date) = to_date('06-MAR-11','DD-MON-YY');
I would not use the MON datetime format model. As I explain here it depends on your region and settings. It's safer to use a numeric month format model instead. Equally, always specify century as part of the year.
where trunc(creation_date) = to_date('06-03-YY11','DD-MM-YYYY');
Your second problem is almost definitely your NLS_DATE_FORMAT; it appears to not take into account the time, hence why you see 4 identical dates. This only governs the manner in which data is displayed not how it is stored.
You can change this using something like:
ALTER SESSION SET NLS_DATE_FORMAT = "DD/MM/YYYY HH24:MI:SS"
If I set up a test environment using the following:
create table test_table ( creation_date date );
insert into test_table values ( sysdate );
insert into test_table values ( sysdate - 0.01 );
alter session set nls_date_format = "YYYY/MM/DD";
You can see the data returned does not include time (though SYSDATE does):
SQL> select * from test_table;
CREATION_D
----------
2013/04/12
2013/04/12
Altering the NLS_DATE_FORMAT and performing the same SELECT, you now get a time component:
SQL> alter session set nls_date_format = "YYYY/MM/DD HH24:MI:SS";
Session altered.
SQL> select * from test_table;
CREATION_DATE
-------------------
2013/04/12 12:48:41
2013/04/12 12:34:17
Lastly, when trying to select today's date alone no rows will be returned:
SQL> select *
2 from test_table
3 where creation_date = to_date('20130412','yyyymmdd');
no rows selected
But, when using TRUNC() to compare on only the date portion of the field you get all your rows again:
SQL> select *
2 from test_table
3 where trunc(creation_date) = to_date('20130412','yyyymmdd');
CREATION_DATE
-------------------
2013/04/12 12:48:41
2013/04/12 12:34:17
To actually answer your second question, if you want unique dates you can re-use the TRUNC() function:
select distinct trunc(creation_date)
from test_table
The DATE datatype stores the year (including the century), the month, the day, the hours, the minutes, and the seconds (after midnight). You must also consider this.

Oracle — separate time and date

I have a question with Oracle (I've installed Oracle 11g Express Edition).
I want to insert values for 'date' and 'time', but I cannot separate them.
create table Match
(
numMatch number(2) constraint PKMatch primary key,
dateM date,
heureM date,
numE_Eq number(2),
numE_Eq2 number(2),
nomTerrain varchar2(30)
);
--"tools"=>"preferences"=>"format de date:DD/MM/YYYY HH24:MI:SS"
insert into Match values (1,to_date
('10/12/2010','DD/MM/YYYY'),to_date('15:00:00','HH24:MI:SS'),1,3,'Stade Argentina'
);
result:
dateM: 10/12/2010 00:00:00
heureM: 01/11/2012 15:00:00
PS: I've tried to_char instead of to_date, but it didn't work at all.
Yes, I'm aware of that 'DATE datatype contains both date and time', but it's the prof who insists showing date and time separately in the table,
and I've seen your solutions before, but for me, it's a query, not to 'insert values' in the table.
So I'd like to know how I can have a table directly presenting date and time.
Oracle doesn't have a TIME datatype. You can store a DATE with a time component, and just query based on time, and display based on time.
select to_char(my_date_field, 'HH24:MI:SS')
from my_table
where to_date(my_date_field, 'HH24:MI') = '18:51';
Alternatively, you can store seconds from midnight as an integer, and calculate the time of day from that. It will also make querying for range times easier I think.
Also, within a session, execute the following to have all dates formatted the way you wish:
alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'
Another way to represent a TIME equivalent type in Oracle is with the INTERVAL type, such as:
SQL> CREATE TABLE foo (
bar INTERVAL DAY(0) TO SECOND(3)
);
This would allow the storage of a time period with 0 precision of the DAY component, and 3 decimal points for the SECOND component. An INSERT example is:
SQL> INSERT INTO foo VALUES ('0 01:01:01.333');
What's great about this approach is that it automatically presents the results of a SELECT in an intuitive format without the need for conversion:
SQL> SELECT * FROM foo;
BAR
---------------------------------------------------------------------------
+0 01:01:01.333
but it's the prof who insists showing date and time separately in the table
While this sounds like a pretty stupid requirement, one thing you could do is to create two computed columns that show the date and time as varchar columns:
create table match
(
nummatch number(2) constraint pkmatch primary key,
the_date date,
datem generated always as to_char(the_date, 'yyyy-mm-dd'),
heurem generated always as to_char(the_date, 'hh24:mi')
nume_eq number(2),
nume_eq2 number(2),
nomterrain varchar2(30)
);
insert into Match (nummatch, the_date, nume_eq, nume_eq2, nomterrain)
values
(1,to_date('10/12/2010 15:00:00','DD/MM/YYYY hh24:mi:ss'),1,3,'Stade Argentina');
Then a
select *
from match;
will return:
NUMMATCH | THE_DATE | DATEM | HEUREM | NUME_EQ | NUME_EQ2 | NOMTERRAIN
---------+---------------------+------------+--------+---------+----------+----------------
1 | 2010-12-10 15:00:00 | 2010-12-10 | 15:00 | 1 | 3 | Stade Argentina
Alternatively you could just create a view on the table that separates the date and time using to_char()
Oracle DATE type includes both DATE and TIME information (because it pre-dates the SQL-92 standard when the standard DATE, TIME and TIMESTAMP types were added). So, you can't separate them in the table; there's no reason to do so, either. You can, if you so desire, create a view which presents the DATE field as separate date-only and time-only display fields.

Migrating Oracle DATE columns to TIMESTAMP with timezone

Bakground:
I've got a legacy app I'm working on that uses DATE types for most time storage in the database. I'd like to try update some of these tables so that they can utilize time zones since this is causing problems with users in different areas from where the db is(see A below). This is for Oracle 10g.
Quetions:
1) Can I migrate this "in place." That is can I convert like so
DATE_COL = type:DATE => DATE_COL = type:TIMESTAMP
...or will I have to use a different column name?
Keep in mind that data needs to be retained. If this can be done semi-easily in a migration script it will work for my purposes.
2) Will this type of conversion be backwards compatible? We likely have some scripts or reports that will hit this table that we may not know about. We can probably deal with it but I'd like to know what sort of hornet's nest I'm walking into.
3) What pitfalls should I be on the lookout for?
Thanks,
EDIT:
(partly in response to Gary)
I'm fine with a multi-step process.
1) move data to a new Timestamp column (caled TEMP) with some sort of conversion
2) drop old column (we'll call it MY_DATE)
3) create new timestamp column with the old date column name (MY_DATE)
4) move data to the MY_DATE column
5) drop TEMP column
A Gary also wanted clarification on the specific timezone issue. I copied my answer from below to keep it more readable.
Basically the data will be accessed from several different areas. We need to be able to convert to/from the local time zone as needed. We also have triggers that use sysdate further complicating things. timestamp with time zone alleviates a lot of this pain.
Oh and thanks for the answers so far.
You could just run:
ALTER TABLE your_table MODIFY your_date_column TIMESTAMP WITH TIME ZONE;
But I would recommend adding a TIMESTAMP column to the table, using an UPDATE statement to populate, and drop the original date column if you so choose:
ALTER TABLE your_table ADD date_as_timestamp TIMESTAMP WITH TIME ZONE;
UPDATE your_table
SET date_as_timestamp = CAST(date_column AS TIMESTAMP WITH TIME ZONE);
The conversion is backwards compatible - you can switch back & forth as you like.
Simple enough to demonstrate
SQL> create table x (y date);
Table created.
SQL> insert into x select sysdate from dual;
1 row created.
SQL> commit;
Commit complete.
SQL> alter table x modify y timestamp;
Table altered.
SQL> select * from x;
Y
---------------------------------------------------------------------------
03/NOV/09 12:49:03.000000 PM
SQL> alter table x modify y date;
Table altered.
SQL> select * from x;
Y
---------
03/NOV/09
SQL> alter table x modify y timestamp with time zone;
alter table x modify y timestamp with time zone
ERROR at line 1:
ORA-01439: column to be modified must be empty to change datatype
SQL> alter table x modify y timestamp with local time zone;
Table altered.
SQL> alter table x modify y date;
Table altered.
So you can go from date to timestamp (or timestamp with local timezone) and back again, but not for timestamp with time zone (ie where the offset is persisted).
You'd have to add another column, and copy the existing data over (with a default for the appropriate time zone).
"causing problems with users in different areas from where the db is".
Might help to be a bit more specific. Is it sufficient to convert the dates (or timestamps) from the database timezone to the user's timezone when inserted/changed/queried, or do you actually need to persist the fact that the record was created at 3:00pm in a specific timezone.

Sqlite: CURRENT_TIMESTAMP is in GMT, not the timezone of the machine

I have a sqlite (v3) table with this column definition:
"timestamp" DATETIME DEFAULT CURRENT_TIMESTAMP
The server that this database lives on is in the CST time zone. When I insert into my table without including the timestamp column, sqlite automatically populates that field with the current timestamp in GMT, not CST.
Is there a way to modify my insert statement to force the stored timestamp to be in CST? On the other hand, it is probably better to store it in GMT (in case the database gets moved to a different timezone, for example), so is there a way I can modify my select SQL to convert the stored timestamp to CST when I extract it from the table?
I found on the sqlite documentation (https://www.sqlite.org/lang_datefunc.html) this text:
Compute the date and time given a unix
timestamp 1092941466, and compensate
for your local timezone.
SELECT datetime(1092941466, 'unixepoch', 'localtime');
That didn't look like it fit my needs, so I tried changing the "datetime" function around a bit, and wound up with this:
select datetime(timestamp, 'localtime')
That seems to work - is that the correct way to convert for your timezone, or is there a better way to do this?
simply use local time as the default:
CREATE TABLE whatever(
....
timestamp DATE DEFAULT (datetime('now','localtime')),
...
);
You should, as a rule, leave timestamps in the database in GMT, and only convert them to/from local time on input/output, when you can convert them to the user's (not server's) local timestamp.
It would be nice if you could do the following:
SELECT DATETIME(col, 'PDT')
...to output the timestamp for a user on Pacific Daylight Time. Unfortunately, that doesn't work. According to this SQLite tutorial, however (scroll down to "Other Date and Time Commands"), you can ask for the time, and then apply an offset (in hours) at the same time. So, if you do know the user's timezone offset, you're good.
Doesn't deal with daylight saving rules, though...
In the (admitted rare) case that a local datatime is wanted (I, for example, store local time in one of my database since all I care is what time in the day is was and I don't keep track of where I was in term of time zones...), you can define the column as
"timestamp" TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M','now', 'localtime'))
The %Y-%m-%dT%H:%M part is of course optional; it is just how I like my time to be stored. [Also, if my impression is correct, there is no "DATETIME" datatype in sqlite, so it does not really matter whether TEXT or DATETIME is used as data type in column declaration.]
When having a column defined with "NOT NULL DEFAULT CURRENT_TIMESTAMP," inserted records will always get set with UTC/GMT time.
Here's what I did to avoid having to include the time in my INSERT/UPDATE statements:
--Create a table having a CURRENT_TIMESTAMP:
CREATE TABLE FOOBAR (
RECORD_NO INTEGER NOT NULL,
TO_STORE INTEGER,
UPC CHAR(30),
QTY DECIMAL(15,4),
EID CHAR(16),
RECORD_TIME NOT NULL DEFAULT CURRENT_TIMESTAMP)
--Create before update and after insert triggers:
CREATE TRIGGER UPDATE_FOOBAR BEFORE UPDATE ON FOOBAR
BEGIN
UPDATE FOOBAR SET record_time = datetime('now', 'localtime')
WHERE rowid = new.rowid;
END
CREATE TRIGGER INSERT_FOOBAR AFTER INSERT ON FOOBAR
BEGIN
UPDATE FOOBAR SET record_time = datetime('now', 'localtime')
WHERE rowid = new.rowid;
END
Test to see if it works...
--INSERT a couple records into the table:
INSERT INTO foobar (RECORD_NO, TO_STORE, UPC, PRICE, EID)
VALUES (0, 1, 'xyz1', 31, '777')
INSERT INTO foobar (RECORD_NO, TO_STORE, UPC, PRICE, EID)
VALUES (1, 1, 'xyz2', 32, '777')
--UPDATE one of the records:
UPDATE foobar SET price = 29 WHERE upc = 'xyz2'
--Check the results:
SELECT * FROM foobar
Hope that helps.
SELECT datetime(CURRENT_TIMESTAMP, 'localtime')
SELECT datetime('now', 'localtime');
Time ( 'now', 'localtime' ) and Date ( 'now', 'localtime' ) works.
You can also just convert the time column to a timestamp by using strftime():
SELECT strftime('%s', timestamp) as timestamp FROM ... ;
Gives you:
1454521888
'timestamp' table column can be a text field even, using the current_timestamp as DEFAULT.
Without strftime:
SELECT timestamp FROM ... ;
Gives you:
2016-02-03 17:51:28
I think this might help.
SELECT datetime(strftime('%s','now'), 'unixepoch', 'localtime');
The current time, in your machine's timezone:
select time(time(), 'localtime');
As per http://www.sqlite.org/lang_datefunc.html