Oracle SQL TO_CHAR (num, format) - Trailing zeros - oracle

In the Oracle documentation we read about the formating parameter that:
0999 Returns leading zeros.
9990 Returns trailing zeros.
I understand leading zeros: Display 000123 instead of 123.
But trailing zeros?
How is 9990 different from 9999?
How is 99.90 different from 99.99?

You could use trailing zeros when you are dealing with monetary values
eg
Table and data
create table payments (
payment number
) ;
insert into payments( payment )
select 11 from dual union all
select 11.1 from dual union all
select 11.11 from dual ;
Query
select payment amount from payments
union
select sum( payment ) from payments ;
-- result
AMOUNT
_________
11
11.1
11.11
33.21
If you write a query or view that applies trailing zeros, you'll be able to get a result that is more "usable" eg
create or replace view payments_view ( position_, description, amount )
as
select
1
, 'item'
, to_char( payment, '999,999.00' )
from payments
union
select
2
, 'total'
, to_char( sum( payment ), '999,999.00' )
from payments
;
select description, amount
from payments_view
order by position_
;
-- output
DESCRIPTION AMOUNT
______________ ______________
item 11.00
item 11.10
item 11.11
total 33.21
DBfiddle here.
{1} How is 9990 different from 9999? {2} How is 99.90 different from
99.99?
In order to make this "visible", please have a look at the resultset of the following query, which shows various function calls, the DUMP() (returning a code for the datatype, the length of its input in bytes, and the "internal representation" - UTF8 code points in decimal in this case), and the actual output. The resultset also contains 3 rows with values padded via LPAD() and RPAD(), respectively, in order to make the positions of the digits easier to see.
select
'to_char( 1,''9990'' )' function_
, dump( to_char( 1,'9990' ) ) dump_
, to_char( 1, '9990' ) output_
from dual union all
select 'to_char( 1,''9999'' )', dump( to_char( 1, '9999' ) )
, to_char( 1, '9999' )
from dual union all
select 'to_char( 1 )', dump( to_char( 1 ) )
, to_char( 1 )
from dual union all
select 'to_char( 1,''fm9990'' )', dump( to_char( 1,'fm9990' ) )
, to_char( 1, '9999' )
from dual union all
select 'to_char( 1,''fm9999'' )', dump( to_char( 1,'fm9999' ) )
, to_char( 1, '9999' )
from dual union all
select 'to_char( 0,''99.90'' )', dump( to_char( 0,'99.90' ) )
, to_char( 0, '99.90' )
from dual union all
select 'to_char( 0,''99.99'' )', dump( to_char( 0,'99.99' ) )
, to_char( 0, '99.99' )
from dual union all
select 'to_char( 0,''90.99'' )', dump( to_char( 0,'90.99' ) )
, to_char( 0, '90.99' )
from dual union all
select 'to_char( 0,''fm99.90'' )', dump( to_char( 0,'fm99.90' ) )
, to_char( 0, 'fm99.90' )
from dual union all
select 'to_char( 0,''fm99.99'' )', dump( to_char( 0,'fm99.99' ) )
, to_char( 0, 'fm99.99' )
from dual union all
select 'lpad( ''1'', 5, '' '' )', null, lpad( '1', 5, ' ' ) from dual union all
select 'lpad( ''12345'', 6, ''_'' )', null, lpad( '12345', 6, '_' ) from dual union all
select 'rpad( ''12345'', 6, ''_'' )', null, rpad( '12345', 6, '_' ) from dual
;
Result
FUNCTION_ DUMP_ OUTPUT_
__________________________ _________________________________ __________
to_char( 1,'9990' ) Typ=1 Len=5: 32,32,32,32,49 1
to_char( 1,'9999' ) Typ=1 Len=5: 32,32,32,32,49 1
to_char( 1 ) Typ=1 Len=1: 49 1
to_char( 1,'fm9990' ) Typ=1 Len=1: 49 1
to_char( 1,'fm9999' ) Typ=1 Len=1: 49 1
to_char( 0,'99.90' ) Typ=1 Len=6: 32,32,32,46,48,48 .00
to_char( 0,'99.99' ) Typ=1 Len=6: 32,32,32,46,48,48 .00
to_char( 0,'90.99' ) Typ=1 Len=6: 32,32,48,46,48,48 0.00
to_char( 0,'fm99.90' ) Typ=1 Len=3: 46,48,48 .00
to_char( 0,'fm99.99' ) Typ=1 Len=2: 48,46 0.
lpad( '1', 5, ' ' ) 1
lpad( '12345', 6, '_' ) _12345
rpad( '12345', 6, '_' ) 12345_
Answers to your questions:
{1} No difference. In both cases, the results get padded with blanks (1 extra space for the "sign"). The effect of the formatting is comparable to the output of LPAD() (see third line from the bottom of the result set).
{2} No difference.
As #Ponder Stibbons has mentioned, there will be a difference when the number contains a decimal point and you use the 'fm' format model modifier, as this will remove extraneous characters from the output.
DBfiddle here.

Related

Convert time from a format to an int in Oracle

I can't seem to figure this out. I have some rows with time in the format 00:00:00 (hh:mm:ss) and i need to calculate the total time it takes for a task.
I am unable to sum this data. Can someone advise on a way to convert this to a format i can sum or a method to calculate the total time for the task.
Thanks for any assistance. This is in an Oracle DB.
Convert your time string to a date and subtract the equivalent date at midnight to give you an number as a fraction of a day. You can then sum this number and convert it to an interval:
Oracle Setup:
CREATE TABLE test_data( value ) AS
SELECT '01:23:45' FROM DUAL UNION ALL
SELECT '12:34:56' FROM DUAL UNION ALL
SELECT '23:45:00' FROM DUAL;
Query:
SELECT NUMTODSINTERVAL(
SUM( TO_DATE( value, 'HH24:MI:SS' ) - TO_DATE( '00:00:00', 'HH24:MI:SS' ) ),
'DAY'
) AS total_time_taken
FROM test_data;
Output:
| TOTAL_TIME_TAKEN |
| :---------------------------- |
| +000000001 13:43:41.000000000 |
db<>fiddle here
Update including durations longer than 23:59:59.
Oracle Setup:
CREATE TABLE test_data( value ) AS
SELECT '1:23:45' FROM DUAL UNION ALL
SELECT '12:34:56' FROM DUAL UNION ALL
SELECT '23:45:00' FROM DUAL UNION ALL
SELECT '48:00:00' FROM DUAL;
Query:
SELECT NUMTODSINTERVAL(
SUM(
DATE '1970-01-01'
+ NUMTODSINTERVAL( SUBSTR( value, 1, HM - 1 ), 'HOUR' )
+ NUMTODSINTERVAL( SUBSTR( value, HM + 1, MS - HM - 1 ), 'MINUTE' )
+ NUMTODSINTERVAL( SUBSTR( value, MS + 1 ), 'SECOND' )
- DATE '1970-01-01'
),
'DAY'
) AS total_time
FROM (
SELECT value,
INSTR( value, ':', 1, 1 ) AS HM,
INSTR( value, ':', 1, 2 ) AS MS
FROM test_data
);
Output:
| TOTAL_TIME |
| :---------------------------- |
| +000000003 13:43:41.000000000 |
db<>fiddle here
Even better would be if you changed your table to hold the durations as intervals rather than as strings then everything becomes much simpler:
Oracle Setup:
CREATE TABLE test_data( value ) AS
SELECT INTERVAL '1:23:45' HOUR TO SECOND FROM DUAL UNION ALL
SELECT INTERVAL '12:34:56' HOUR TO SECOND FROM DUAL UNION ALL
SELECT INTERVAL '23:45:00' HOUR TO SECOND FROM DUAL UNION ALL
SELECT INTERVAL '48:00:00' HOUR TO SECOND FROM DUAL;
Query:
SELECT NUMTODSINTERVAL(
SUM( DATE '1970-01-01' + value - DATE '1970-01-01' ),
'DAY'
) AS total_time
FROM test_data;
Output:
| TOTAL_TIME |
| :---------------------------- |
| +000000003 13:43:41.000000000 |
db<>fiddle here

calculate the running total over the column contain date difference in HH:MI:SS format in oracle

I have to find the running total over the column interval.
SELECT
( ( EXTRACT(DAY FROM intrvl) * 24 ) + ( EXTRACT(HOUR FROM intrvl) ) ) ||':'||
EXTRACT(MINUTE FROM intrvl) ||':'||
EXTRACT(SECOND FROM intrvl) ||':'|| as interval
FROM
(
SELECT
( to_timestamp(TO_CHAR(date_column_name,'dd-mon-rrrr hh:mi:ss') ) ) - ( to_timestamp(TO_CHAR(date_column_name,'dd-mon-rrrr hh:mi:ss') ) ) intrvl
FROM
dual
);
currrently Interval column of table has below data:
Interval(HH:mi:ss)
0:4:23
696:1:36
696:4:51
8760:1:18
The best I can come up with is this. Note that the interval data type does not take a format model for displaying - you can't force an interval of 25 hours to be displayed as 25:00:00 (although you can use that to INPUT an interval). Instead, it will be shown as 01 01:00:00 (meaning, a day and an hour).
with
tbl (interv) as (
select interval '0:4:23' hour(9) to second from dual union all
select interval '696:1:36' hour(9) to second from dual union all
select interval '696:4:51' hour(9) to second from dual union all
select interval '8760:1:18' hour(9) to second from dual
)
select interval '1' day * sum(date '2000-01-01' + interv - date'2000-01-01')
as sum_interv
from tbl;
SUM_INTERV
--------------------
+423 00:12:08.000000
In your original attempt you were trying to get a STRING output. I am not sure that's wise, but if that's what you need you can do it like so:
with
tbl (interv) as (
select interval '0:4:23' hour(9) to second from dual union all
select interval '696:1:36' hour(9) to second from dual union all
select interval '696:4:51' hour(9) to second from dual union all
select interval '8760:1:18' hour(9) to second from dual
)
, prep (sum_interv) as (
select interval '1' day * sum(date '2000-01-01' + interv - date'2000-01-01')
from tbl
)
select to_char( extract(day from sum_interv) * 24
+ extract(hour from sum_interv), 'fm999999999' ) || ':' ||
to_char( extract(minute from sum_interv), 'fm00' ) || ':' ||
to_char( extract(second from sum_interv), 'fm00' ) as sum_interv
from prep
;
SUM_INTERV
------------------
10152:12:08

Oracle - Return a column per date in a range

Let's say I have Table A: assistance
PersonID Date CHECK
123456 2012-01-01 F
213415 2012-01-03 A
PersonID ArrivalDate Jan-01 Jan-02 Jan-03
123456 2012-01-01 F NULL NULL
213415 2012-01-03 NULL NULL A
The system is for checks, between 1 to 15 days but no more than that. Any ideas would be very much appreciated.
you can try this, but I'm not sure if this is what you need,
with inputs_
as (select 123456 person_id, to_date('2012-01-01', 'YYYY-MM-DD') date_, 'F' check_
from dual
union all
select 213415 person_id, to_date('2012-01-03', 'YYYY-MM-DD'), 'A' check_
from dual
UNION ALL
select 123456 person_id, to_date('2012-01-01', 'YYYY-MM-DD') date_, 'F' check_
from dual
union all
select 213415 person_id, to_date('2012-01-03', 'YYYY-MM-DD'), 'A' check_
from dual
union all
select 123456 person_id, to_date('2012-01-04', 'YYYY-MM-DD') date_, 'F' check_
from dual
union all
select 213415 person_id, to_date('2012-01-05', 'YYYY-MM-DD'), 'A' check_
from dual
union all
select 123456 person_id, to_date('2012-01-02', 'YYYY-MM-DD') date_, 'A' check_
from dual
union all
select 213415 person_id, to_date('2012-01-04', 'YYYY-MM-DD'), 'A' check_
from dual
UNION ALL
select 213415 person_id, to_date('2012-01-02', 'YYYY-MM-DD'), 'F' check_
from dual)
select *
from (select person_id, date_ arrival_date, check_, TO_CHAR(date_, 'DD-MON') date_
from inputs_)
pivot (min(check_) for date_ in ('01-JAN', '02-JAN', '03-JAN', '04-JAN', '05-JAN')
)
order by 2;
Output:
Also this is not dynamic, so if you want dynamic pivot, you can see this link, Dynamic pivot in oracle sql
You could construct dynamic date values for PIVOT's IN clause for the range min date to max date using a query. Then open a dynamic cursor with the required arguments in the IN clause of PIVOT.
DBMS_SQL.RETURN_RESULT ( 12c and later ) will display the desired result.
For older versions, you may refer my answer here to easily display the cursor's output: Display result
DECLARE
v_instring VARCHAR2 (1000);
v_cur SYS_REFCURSOR;
BEGIN
WITH dt ( min_t ,max_t ) AS
( SELECT MIN(Date_t) ,MAX(Date_t) FROM TableA
) ,
datevalues (date_ch) AS
(SELECT TO_CHAR(min_t + lvl - 1, 'DD-MON')
FROM dt CROSS APPLY
( SELECT LEVEL lvl FROM DUAL CONNECT BY LEVEL <= max_t - min_t + 1
)
)
SELECT LISTAGG(''''
|| date_ch
|| ''' AS "'
|| date_ch, '",') WITHIN GROUP (
ORDER BY date_ch )||'"'
INTO v_instring
FROM
( SELECT DISTINCT date_ch FROM datevalues
);
OPEN v_cur FOR 'select * from (select PersonID, date_t arrival_date, check_t,
TO_CHAR(date_t, ''DD-MON'') date_t from TableA) pivot ( min(check_t)
for date_t in ('||v_instring||')) ORDER BY arrival_date';
DBMS_SQL.RETURN_RESULT(v_cur);
END;
/
PERSONID ARRIVAL_DATE 01-JAN 02-JAN 03-JAN 04-JAN 05-JAN 06-JAN
------------- --------------- ------ ------ ------ ------ ------ ------
123456 01-01-12 F
213415 03-01-12 A
213416 04-01-12 F
345677 06-01-12 A

find nearest row of different type in oracle

My table looks like
__ Key type timeStamp flag
1 ) 1 B 2015-06-28 22:19:26 Y
2 ) 1 B 2015-06-28 22:20:22 Y
3 ) 1 C 2015-06-28 22:22:06 N
4 ) 1 A 2015-06-28 22:25:11 N
5 ) 1 B 2015-06-28 22:29:44 Y
6 ) 1 A 2015-06-28 22:33:33 N
7 ) 1 B 2015-06-28 22:35:21 N
8 ) 1 B 2015-06-28 22:39:34 Y
9 ) 1 B 2015-06-28 22:43:53 N
10) 1 A 2015-06-28 22:45:53 N
I need to find out all the types of A whose flag='N' with respect to which there exist type B whose timestampOF(B)<timestampOF(A) and Flag(B)='Y' and key(A)=key(B).
note: If there exist two B previous than A than take the B with max timestamp.(ROW[8,9,10] 9 is taken instead of 8)
OUTPUT
__ Key type timeStamp flag
4 ) 1 A 2015-06-28 22:25:11 N
6 ) 1 A 2015-06-28 22:33:33 N
My approach
SELECT *
FROM tab TAB_OUT
WHERE TAB_OUT.TYPE='A'
AND TAB_OUT.FLAG='N'
AND EXISTS(
SELECT *
FROM tab TAB_IN
WHERE TAB_IN.KEY = TAB_OUT.KEY
AND TAB_IN.TYPE='B'
AND TAB_OUT.FLAG='Y'
AND TAB_IN.timestamp<TAB_OUT.timestamp
AND TAB_IN.timestamp = (SELECT MAX(timestamp) from
tab where timestamp< `TAB_OUT.timestamp`)
);
But in this i can not use TAB_OUT.timestamp in third level query. Is there any alternative solution to solve this problem.
In my query note: part is not satisfied as my query as it skips no. 9) and satisfy condition with no. 8)
A solution that only requires a single table scan:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( Key, type, timeStamp, flag ) AS
SELECT 1, 'B', CAST( TIMESTAMP '2015-06-28 22:19:26' AS DATE ), 'Y' FROM DUAL
UNION ALL SELECT 1, 'B', CAST( TIMESTAMP '2015-06-28 22:20:22' AS DATE ), 'Y' FROM DUAL
UNION ALL SELECT 1, 'C', CAST( TIMESTAMP '2015-06-28 22:22:06' AS DATE ), 'N' FROM DUAL
UNION ALL SELECT 1, 'A', CAST( TIMESTAMP '2015-06-28 22:25:11' AS DATE ), 'N' FROM DUAL
UNION ALL SELECT 1, 'B', CAST( TIMESTAMP '2015-06-28 22:29:44' AS DATE ), 'Y' FROM DUAL
UNION ALL SELECT 1, 'A', CAST( TIMESTAMP '2015-06-28 22:33:33' AS DATE ), 'N' FROM DUAL
UNION ALL SELECT 1, 'B', CAST( TIMESTAMP '2015-06-28 22:35:21' AS DATE ), 'N' FROM DUAL
UNION ALL SELECT 1, 'B', CAST( TIMESTAMP '2015-06-28 22:39:34' AS DATE ), 'Y' FROM DUAL
UNION ALL SELECT 1, 'B', CAST( TIMESTAMP '2015-06-28 22:43:53' AS DATE ), 'N' FROM DUAL
UNION ALL SELECT 1, 'A', CAST( TIMESTAMP '2015-06-28 22:45:53' AS DATE ), 'N' FROM DUAL
Query 1:
SELECT Key,
type,
timeStamp,
flag
FROM (
SELECT Key,
type,
timeStamp,
flag,
LAG( CASE WHEN type = 'B' THEN flag END ) IGNORE NULLS OVER ( PARTITION BY Key ORDER BY timeStamp ) AS prev_b_flag
FROM table_name t
WHERE type IN ( 'A', 'B' )
)
WHERE type = 'A'
AND flag = 'N'
AND prev_b_flag = 'Y'
Results:
| KEY | TYPE | TIMESTAMP | FLAG |
|-----|------|------------------------|------|
| 1 | A | June, 28 2015 22:25:11 | N |
| 1 | A | June, 28 2015 22:33:33 | N |
SELECT
*
FROM
tab A
WHERE
flag = 'N' AND type = 'A'
AND EXISTS (
SELECT
NULL
FROM
tab B
WHERE
type = 'B'
AND A.timestamp > timestamp AND A.Key = Key
GROUP BY
Key
HAVING
MAX(flag) KEEP (DENSE_RANK LAST ORDER BY timestamp) = 'Y'
);
There is no need to make correlated query to select flag from the the last record. Using aggregate KEEP clause is more efficient way. In this case it sort the groups by timestamp and keeps only the last value for the aggregation (last timestamp you wanted), so there comes only single record to the MAX function and we just take the FLAG value from it.
Here is simple example:
WITH sample (value1, value2) AS (
SELECT 1, 'Y' FROM DUAL UNION ALL
SELECT 2, 'X' FROM DUAL
)
SELECT
MIN(value2) KEEP (DENSE_RANK LAST ORDER BY value1) value2
FROM
sample
This returns value2 from the record with highest value1.

Oracle: elegant way to parse a number to 9,99 format

With Oracle 11g I want to parse a number to strip decimals if their value is 0 and keep two decimal figure after the decimal separator ',' if the value of decimals is different from 0
Example:
1,00 -> 1
1,001 -> 1
0,203 -> 0,20
And so on.
I've obtained something like that in a very unelegant way
select replace(trim(to_char (trunc ('0,2345',2),'9999999990.99')), '.', ',')
from dual
Do you know more elegant way? The output should be a char (not number).
Not sure it's much more elegant, but assuming your replace is to deal with different locales, this might work for you:
with t as (
select 1.00 as n from dual
union all select 1.001 from dual
union all select 0.203 from dual
union all select 0.2345 from dual
union all select 112.999 from dual
)
select n, regexp_replace(to_char(trunc(n, 2), '9999999990D00',
'NLS_NUMERIC_CHARACTERS='',.'''), '[,.]00$', null) as new_n
from t;
N NEW_N
---------- --------------
1 1
1.001 1
0.203 0,20
0.2345 0,23
112.999 112,99
The nls_param argument to to_char let's you dictate whether it used a comma or a period as the decimal separator. If you can set that at session level then the query looks a bit simpler. The regexp_replace strips ,00 (or .00, which come to think of it is overkill) from the end of th string.
As ThinkJet noted the regexp_replace is a bit excessive, and since the decimal seperator is defined in the column clase (and the format has no group separators anyway) it can be done with a plan replace:
with t as (
select 1.00 as n from dual
union all select 1.001 from dual
union all select 0.203 from dual
union all select 0.2345 from dual
union all select 112.999 from dual
union all select 13.08 from dual
)
select n, replace(trim(
to_char(trunc(n, 2), '9999999990D00', 'NLS_NUMERIC_CHARACTERS='',.''')),
',00', null) as new_n
from t;
N NEW_N
---------- --------------
1 1
1.001 1
0.203 0,20
0.2345 0,23
112.999 112,99
13.08 13,08
Still not sure this can be described as 'elegant' though.
To achieve correct results you must deal with numbers, not strings:
with t as (
select 1.00 as n from dual
union all select 1.001 from dual
union all select 0.203 from dual
union all select 0.2345 from dual
union all select 112.999 from dual
union all select 112.105 from dual
union all select 0 from dual
union all select -12.307 from dual
)
select
n,
decode( trunc(n,2) - trunc(n) ,
0, to_char(trunc(n), 'TM9', 'NLS_NUMERIC_CHARACTERS = '', '''),
to_char(trunc(n,2),'9999999990D00', 'NLS_NUMERIC_CHARACTERS = '', ''')
)
string_val
from t
SQLFiddle
P.S. Updated to get incorrect truncation instead of round, as in OP request.
Another variant
SELECT n,
to_char( trunc( n, 2 ),
case when mod( trunc( n, 2 ), 1 ) = 0
then '9990'
else '9990D00'
end, 'NLS_NUMERIC_CHARACTERS='',.''' ) val
from t
;
SQLFiddle demo

Resources