How to convert a date to a string - sql

I want yo get only the 'date hours:minutes:seconds' from the Date column
Date
10/11/22 12:14:01,807000000
11/12/22 13:15:46,650000000
29/12/22 14:30:46,501000000
and I want to get a string column with date hours:minutes:seconds
Date_string
10/11/22 12:14:01
11/12/22 13:15:46
29/12/22 14:30:46
I tried this code but it doesn't work:
select*, TO_CHAR(extract(hour from (Date)))||':'||TO_CHAR(extract(minute from (Date)))||':'||TO_CHAR(extract(second from (Date))) as Date_string
from table;

If this is a date column, you could use to_char directly:
SELECT m.*, TO_CHAR(my_date_column, 'dd/mm/yy hh24:mi:ss')
FROM mytable m

You can use REGEX SUBSTRING function to get the date string on the left.
SELECT REGEXP_SUBSTR (Date_string, '[^,]+', 1, 1)
AS left_part
FROM Table1;
where ^, means look for chars that are NOT comma on 1st position
and get the first occurrence (on the left)
Result:
LEFT_PART
10/11/22 12:14:01
11/12/22 13:15:46
29/12/22 14:30:46
reference:
https://docs.oracle.com/cd/B12037_01/server.101/b10759/functions116.htm

Just do it with the TO_DATE() and TO_CHAR() function pair, both operating on the Oracle date format strings:
Building the scenario:
-- your input ..
WITH indata(dt) AS (
SELECT '10/11/22 12:14:01,807000000' FROM dual UNION ALL
SELECT '11/12/22 13:15:46,650000000' FROM dual UNION ALL
SELECT '29/12/22 14:30:46,501000000' FROM dual
)
-- end of your input. Real query starts here.
-- Change following comma to "WITH" ..
,
-- Now convert to TIMESTAMP(9) ...
as_ts AS (
SELECT
TO_TIMESTAMP(dt ,'DD/MM/YY HH24:MI:SS,FF9') AS ts
FROM indata
)
SELECT
ts
, CAST(ts AS TIMESTAMP(0)) AS recast -- note: this is rounded
, TO_CHAR(ts,'DD/MM/YY HH24:MI:SS') AS reformatted -- this is truncated
FROM as_ts
Result:
TS
RECAST
REFORMATTED
10-NOV-22 12.14.01.807000000
10-NOV-22 12.14.02
10/11/22 12:14:01
11-DEC-22 13.15.46.650000000
11-DEC-22 13.15.47
11/12/22 13:15:46
29-DEC-22 14.30.46.501000000
29-DEC-22 14.30.47
29/12/22 14:30:46

Going by what you have in your question, it appears that the data in the field Date is a timestamp. This isn't a problem, but the names of the table (TABLE) and field (Date) present some challenges.
In Oracle, TABLE is a reserved word - so to use it as the name of a table it must be quoted by putting it inside double-quotes, as "TABLE". Similarly, Date is a mixed-case identifier and must likewise be quoted (e.g. "Date") every time it's used.
Given the above your query becomes:
SELECT TO_CHAR("Date", 'DD/MM/YY HH24:MI:SS') AS FORMATTED_DATE
FROM "TABLE"
and produces the desired results. db<>fiddle here
Generally, it's best in Oracle to avoid using reserved words as identifiers, and to allow the database to convert all names to upper case - if you do that you don't have to quote the names, and you can refer to them by upper or lower case as the database automatically converts all unquoted names to upper case internally.

Related

Any function in Oracle similar to COALESCE, but instead of NULL works on ERRORs

I have a situation where I need to convert a numeric column value into time. It's a 6 digit field, but unfortunately, different processes over the years inserted data in different format, some HHMM, and others HHMMSS. Let's call this column colTime. I use colTime in combination with another 8 digits numeric field which contains a date in YYYYMMDD format, let's call this colDate.
It is being used as below to construct a TIMESTAMP in UTC zone:
select TO_CHAR(SYS_EXTRACT_UTC(TO_TIMESTAMP(CONCAT(NULLIF(colDate,0), LPAD(NULLIF(colTime,0),4,0)), 'YYYY-MM-DD HH24:MI')), 'YYYY-MM-DD"T"HH24:MI:SS.FFTZR') from tab1;
The problem here obviously is that the colTime may contain 4 OR 6 digit data so I cannot know the correct LPAD number in advance. When the above statement encounters a 6 digit field it throws an error.
I was thinking if I have a function similar to COALESCE that can execute the second argument if the first one returns an error then I'd be able to accommodate LPAD 4 and 6 cases.
I can use a CASE statement, but was hoping for something more graceful.
You can still use COALESCE if you also use the DEFAULT NULL ON CONVERSION ERROR syntax in TO_TIMESTAMP:
select
coalesce
(
to_timestamp('2021-01-01' || ' ' ||the_time default null on conversion error, 'YYYY-MM-DD HHMI'),
to_timestamp('2021-01-01' || ' ' ||the_time default null on conversion error, 'YYYY-MM-DD HHMISS')
)
from
(
select '0102' the_time from dual union all
select '010203' the_time from dual
);
You can use regular expressions to find out which format the data comes from and then you can convert it appropriately.
For example:
with
d (col_time) as (
select '0215' from dual
union all select '183207' from dual
union all select '12:34:56' from dual
union all select 'really-bad-data' from dual
)
select d.*,
case when regexp_like(col_time, '^[0-9]{4}$') then 'Short Time Format'
when regexp_like(col_time, '^[0-9]{6}$') then 'Long Time Format'
else 'Other Time Format'
end as guessed_format
from d;
Result:
COL_TIME GUESSED_FORMAT
---------------- -----------------
0215 Short Time Format
183207 Long Time Format
12:34:56 Other Time Format
really-bad-data Other Time Format

DATE FORMAT :DB2 _ SQL

I have date format (TIMESTAMP) like 2018-03-26-08.30.00.000000 and i want to get it as 2018-03-26-08 how i can do it in sql in DB2(just i want year-month-day-hour )'
Read DB2's manual:
https://www.ibm.com/docs/en/db2-for-zos/12?topic=sf-char
Using that docu:
I need to convert the non-standard in-format with a dash between day and hour, and dots as hour/minute and minute/second separators to a timestamp, using TO_TIMESTAMP().
From the obtained timestamp, I can use TO_CHAR() with a format string to give me back a string with the format I desire.
WITH
indata(ts) AS (
SELECT
TO_TIMESTAMP(
'2018-03-26-08.30.00.000000','YYYY-MM-DD-HH24.MI.SS.US'
)
FROM sysibm.sysdummy1
)
SELECT TO_CHAR(ts,'YYYY-MM-DD-HH24') AS new_format FROM indata;
new_format
---------------
2018-03-26-08
VARCHAR_FORMAT(<timestamp>, '<desired format>')
So:
SELECT VARCHAR_FORMAT(CURRENT TIMESTAMP, 'YYYY-MM-DD-hh24')
FROM SYSIBM.SYSDUMMY1

Oracle Regex grabbing value from key value pair in a string

Consider the following column row:
col
-------------------------
'{"day":"8","every":"2"}'
I am trying to get 8 from this string using regular expression to figure out the day.
so far I have:
SELECT
regexp_replace(col, '{"day":[^0-9]', '') as "day"
FROM
mytable;
This gives me:
day
---------------
8","every":"2"}
I am having trouble figuring out how to filter out the rest of the string from the first number forward. In my example I just want the number 8 for this row.
When you are lucky enough to use Oracle 12c Release 1 (12.1.0.2) or later, do take a look at JSON_VALUE
WITH t (s)
AS (
SELECT '{"day":"8","every":"2"}'
FROM DUAL
)
SELECT JSON_VALUE(s, '$.day' ) AS day
, JSON_VALUE(s, '$.every') AS every
FROM t;
DAY EVERY
--- -----
8 2
How about this?
SELECT
regexp_replace(col, '{"day":"([0-9]+).*', '\1') as "day"
FROM
mytable;
If you don't have access to JSON_VALUE() then I would recommend the following regex unless you always know the position of the day key in the JSON string:
SELECT REGEXP_REPLACE(col, '^.*"day":"(\d+)".*$', '\1') AS day
FROM mytable;
This will replace the entire string (assuming it matches!) with the contents of the first capturing group (enclosed in parentheses: (\d+)). \d indicates a digit 0-9. If you want to return NULL values as well, you can replace \d+ with \d*. If negative or non-numeric values are possible, then I would recommend the following:
SELECT REGEXP_REPLACE(col, '^.*"day":"([^"]*)".*$', '\1') AS day
FROM mytable;
This will return whatever characters that might be contained in the day key.
FYI, once you have the value, numeric or non-, you can convert it to a number safely by using TO_NUMBER() along with REGEXP_SUBSTR():
SELECT COALESCE( TO_NUMBER( REGEXP_SUBSTR( REGEXP_REPLACE( col, '^.*"day":"[^"]*".*$', '\1' ), '\d+' ) ), 0 ) AS day
FROM mytable;
Hope it helps.

sql pivot function with a defined variable in the IN statement

Can anyone tell me how I can make this work? I either get "ORA-56901: non-constant expression is not allowed for pivot|unpivot values" or I get "ORA-00917: missing comma" depending on whether I put &mb1 in quotes or not in the IN statement. Thank you!
define mb1= ADD_MONTHS(TRUNC(SYSDATE,'MM'),-1);
select *
from
( select COLLECTOR
,Month
,low_activity_days
from dwh_prod.low_activity_days_collect_t) src
pivot
(
sum(low_activity_days)
for month in ('&mb1')
) piv;
You could set your substitution to a fixed value instead, so it isn't trying to to a calculation inside the pivot clause. As you're working with dates you can generate a date literal:
set termout off
column x_mb1 new_value mb1
select 'date ''' || TO_CHAR(ADD_MONTHS(TRUNC(SYSDATE,'MM'),-1),
'YYYY-MM-DD') || '''' as x_mb1
from dual;
set termout on
select * from (
select COLLECTOR ,Month ,low_activity_days
from dwh_prod.low_activity_days_collect_t
) src
pivot ( sum(low_activity_days) for month in (&mb1) ) piv;
The first query - with the output hidden by termout generates a string:
select 'date ''' || TO_CHAR(ADD_MONTHS(TRUNC(SYSDATE,'MM'),-1),
'YYYY-MM-DD') || '''' as x_mb1
from dual;
X_MB1
-----------------
date '2017-03-01'
The column ... new_value command then sets &mb to that. The substitution in your really query then becomes:
select * from (
select COLLECTOR ,Month ,low_activity_days
from dwh_prod.low_activity_days_collect_t
) src
pivot ( sum(low_activity_days) for month in (date '2017-03-01') ) piv
The problem with your original code is that you're ending up with a reference to sysdate inside the substituted pivot clause, which throws the ORA-56901 exception (since sysdate isn't constant). You can use any fixed value in the pivot, so you could do:
define mb1=''01-APR-17''
... for month in (&mb1) ) piv;
with the qoutes around the string as part of the definition, or
define mb1='01-APR-17'
... for month in ('&mb1') ) piv;
without, or other variations. But because those end up as string you're relying on implicit date conversion, and relying on your NLS settings; with the date literal I'm generating that isn't an issue. If you really wanted to use that date format you could still do:
define mb1='01-APR-17'
... for month in (to_date('&mb1', 'DD-MON-RR')) ) piv;
or select the date in that specific format with the new_values method:
column x_mb1 new_value mb1
select to_char(add_months(trunc(sysdate, 'MM'), -1), 'DD-MON-RR') as x_mb1 from dual;
... for month in (to_date('&mb1', 'DD-MON-RR')) ) piv;
Using the date literal format still seems simpler to me though.
I'm not sure why pivoting a single value is particularly useful though. Maybe your real query is doing more, but simple aggregation and a filter look more appropriate here:
select collector, sum(low_activity_days)
from low_activity_days_collect_t
where month = add_months(trunc(sysdate, 'MM'), -1)
group by collector;
If you want a rolling 12-month summary you don't need to build the pivot quite that dynamically; you can generate a month-offset number based on the current date, which will give you a fixed range of numeric values you can pivot on (rather than dates), something like:
select * from (
select collector, low_activity_days,
months_between(trunc(sysdate, 'MM'), month) as month_offset
from low_activity_days_collect_t
where month >= add_months(trunc(sysdate, 'MM'), -13)
and month < trunc(sysdate, 'MM')
) src
pivot (sum(low_activity_days) as months_ago
for month_offset in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12));
which will give columns collector, 1_months_ago, 2_months_ago etc.
What you can't easily do is make the column headings represent the actual month (e.g. 2017-04, 2017-03, ...), but you don't seem to have been trying to do that anyway; and unless you alias the pivoted column terms the ones generated in this example have to be treated as quoted identifiers (because they start with numbers).
If you do want month headers you could generate those with some more substitution variable shenanigans, with something like this before the query:
set termout off
column x_m1 new_value m1
column x_m2 new_value m2
...
column x_m12 new_value m12
select to_char(add_months(trunc(sysdate, 'MM'), -1), 'YYYY-MM') as x_m1,
to_char(add_months(trunc(sysdate, 'MM'), -2), 'YYYY-MM') as x_m2,
...
to_char(add_months(trunc(sysdate, 'MM'), -12), 'YYYY-MM') as x_m12
from dual;
column 1_months_ago heading &m1
column 2_months_ago heading &m2
...
column 12_months_ago heading &m12
set termout on
You might find it easier to use a proper reporting tool to query and format/display the results for you though.

PLSQL 'IN' Operator

I'm trying to select rows based on a range of 'dates' determined by the following PLSQL query, which currently delivers the results I need - being the 'date' object of the last 10 weeks of the day of the week when the script is run. Eg. running it on the 22th of May would yield, 15th May, 8th May and so on.
SELECT SYSDATE-(level*7) as DateRange
FROM DUAL
CONNECT BY LEVEL <=10
This generates a list of dates. Then I try and combine this with a parent select statement to get rows with the dates outputted by the above that are in the 'DAY' (of Oracle type DATE) column.
SELECT * FROM NEM_RM16
WHERE NEM_RM16.DAY IN (
SELECT SYSDATE-(level*7) as DateRange
FROM DUAL
CONNECT BY LEVEL <=10);
Which gives no results, despite knowing that there are rows that have the dates generated by the above.
I've read that when using the 'IN' operator, values must be enclosed in single quotes, but I'm not sure about how to do this with the query above.
Am I going about this the right way by using the IN operator, or should I be doing a different type of nested query?
use trunc for truncate time component from sysdate
SELECT * FROM NEM_RM16
WHERE NEM_RM16.DAY IN (
SELECT trunc(SYSDATE)-(level*7) as DateRange
FROM DUAL
CONNECT BY LEVEL <=10);
Maybe the format of the date returned by nested query does not match with the date format of the column NEM_RM16.DAY
Probably, if the dates are compared after making them of the same format, they will match properly
Like this
SELECT *
FROM NEM_RM16
WHERE TO_DATE(TO_CHAR(NEM_RM16.DAY, 'DD/MM/YYYY'), 'DD/MM/YYYY') IN
(SELECT TO_DATE(TO_CHAR(SYSDATE - (level * 7), 'DD/MM/YYYY'),
'DD/MM/YYYY') as DateRange
FROM DUAL
CONNECT BY LEVEL <= 10);
Hope it helps