How to convert a number with decimal values to a float in PL/SQL? - plsql

The issue is that I need to insert this number into json, and because the number contains a comma, json becomes invalid. A float would work because it contains a period not a comma.
I have tried using replace(v_decimalNumber,',','.') and it kind of works, except that the json property is converted to a string. I need it to remain some type of a numerical value.
How can this be achieved?
I am using Oracle 11g.

You just need to_number() function.
select to_number(replace('1,2', ',', '.')) float_nr from dual;
Result:
FLOAT_NR
1.2
Note that if your number has .0 like 1.0, the function will remove it and leave it only 1

The data type of v_decimalNumber is some type of character format as it can contain commas (,). Your contention is that it contains a number once the commas are removed. However there is NO SUCH THING until that contention has been validated since being character I can put any character(s) I want into it subject to any length restriction. As an example a spreadsheet column that should contain numeric data. However, it that doesn't apply users will often put N/A into telling themselves that it doesn't apply. Oracle will happily load this into your v_decimalNumber. (And that's 1 of many many ways non-numeric data can get into your column.) So before attempting to process as a numeric value you must validate it is in fact valid numeric data. The following demonstrates one such way.
with some_numbers (n) as
( select '123,4456,789.00' from dual union all
select '987654321.00' from dual union all
select '1928374655' from dual union all
select '1.2' from dual union all
select '.1' from dual union all
select '1..1' from dual union all
select 'N/A' from dual
)
, rx as (select '^[0-9]*\.?[0-9]*$' regexp from dual)
select n
, case when regexp_like(replace(n,',',null), regexp)
then to_number(replace(n,',',null))
else null
end Num_value
, case when regexp_like(replace(n,',',null), regexp)
then null
else 'Not valid number'
end msg
from some_numbers,rx ;
Take away: Never trust a character type column to contain specific data requirements except random characters. Always validate then put the data into the appropriately defined columns.

Related

Oracle PL/SQL - Check number formats and masks

After a long time without doing PL/SQL...
I need a suggestion from the community, for something that apparently is trivial, but I am a little bit stuck on this.
I am building a load from an CSV file, and there we have a column with the amount.
The CSVs come from different suppliers, and each one can send the amount in different formats.
So I should reject lines from the CSV with amount that are not in correct number format (999,999,999,999.00), because can be an incorrect amount reported by the supplier and should be fixed.
Coming with the formats 999.999.999,00 or 999999999,99, I can do some treatments through PL/SQL to convert.
But I am having problems with values with different formats as 999,9,9,9 or whatever...
I am trying to use common functions (TO_NUMBER, TO_CHAR). But not having much success...
SELECT TO_NUMBER('999,9,9,9') FROM DUAL; or SELECT TO_NUMBER('999,9,9,9','99G990G990G990G990G990D00') FROM DUAL;
The result is ORA-01722: invalid number, and that is brilliant! However, it would reject other formats that seem correct, like 9,999.99
SELECT TO_NUMBER('999,9,9,9','99G999G999G999G999G999D00') FROM DUAL;
Using a format mask, the value 999,9,9,9 is converted to 999999 - and it is not fine. However using the format mask works fine for 9,999.99, for example.
Do you know any other function provided by Oracle that can help solve my issue?
Or any suggestion on how can I do this?
Thank you very much.
Att.,
Guilherme
You can use a regular expression to determine a valid number or not. Then if valid replace comma with nulls and thee convert that to a number. The queries below use the following regular expression:
'^(\d{1,3})(\,\d{3})*(\.\d{2}|\.?)$'
It breaks down as follows
^(\d{1,3}) --- at beginning of string 1 to 3 digits
(\,\d{3})* --- followed by 0 or more sets of comma followed by 3 digits
(.\d{2}|.?) --- that followed by decimal point followed by 2 digits OR (|) optional decimal point
$ --- end of string
Demo:
with test (num, expected)
as (select '999,999,999,999.00', 'valid' from dual union all
select '999,99,999,999.00', 'invalid' from dual union all
select '999.00', 'valid' from dual union all
select '99', 'valid' from dual union all
select '9,999.', 'valid' from dual union all
select '9,999..0', 'invalid' from dual union all
select '999,99999,999.00', 'invalid' from dual
)
select num
, expected
, case when regexp_like(num,'^(\d{1,3})(\,\d{3})*(\.\d{2}|\.?)$')
then to_char(to_number(replace(num,',',null)))
else 'Not Valid Number'
end converted
from test;
In a live setting you would not want the "to_char(to_number ..." structure). This was used for demonstration/testing as both then and else of the case statement must result in same data type. A live version would appear something like:
with test (num)
as (select '999,999,999,999.00' from dual union all
select '999,99,999,999.00' from dual union all
select '999.00' from dual union all
select '99' from dual union all
select '9,999.' from dual union all
select '9,999..0' from dual union all
select '999,99999,999.00' from dual
)
select to_number(replace(num, ',', null))
from test
where regexp_like(num,'^(\d{1,3})(\,\d{3})*(\.\d{2}|\.?)$');

Prevent SQLite query from stripping leading zeros from numeric strings?

In my database, a table contains two columns each containing an 8 digit ASCII code, usually it's just alphanumeric. For example, a row might contain A123B45C in col1 and PQ2R4680 in col2.
I need to have a query/view that outputs a 4 character string calculated as the 2nd+3rd chars of these, concatenated. So in this example the extra column value would be 12Q2.
This is a cut-down version of the SQL I'd like to use, although it won't work as written because of zero stripping / conversion:
select
*,
(substr(col1, 2, 2) || substr(col2, 2, 2)) AS mode
from (nested SQL source query)
where (conditions)
This fails because if a row contains A00B23B4 in col1 and P32R4680 in col2, it will evaluate as 0032 and the query output will contain numeric 32 not 0032. (It's worse if col1 contains P1-2345 or "1.23456" or something like that)
Other questions on preventing zero stripping and string to integer conversion in Sqlite, all relate to data in tables where you can define a column text affinity, or static (quotable) data. In this case I can't do these things. I also can only create queries, not tables, so I can't write to a temp table.
What is the best way to ensure I get a 4 character output in all cases?
I believe you issue is not with substr stripping characters as this works as expected e.g. :-
Then running query SELECT substr(col1,2,2) || substr(col2,2,2) as mode FROM stripping
results in (as expected):-
Rather, your issue is likely how you subsequently utilise mode in which case you may need to use a CAST expression CAST expressions
For example the following does what is possibly happening :-
`SELECT substr(col1,2,2) || substr(col2,2,2) as mode, CAST(substr(col1,2,2) || substr(col2,2,2) AS INTEGER) AS oops FROM stripping`
resulting in :-

How to replace occurrence only on the start of the string in Oracle SQL?

I have a source column and I want to search for string values starting with 05, 5 971971 and 97105 to be replaced by 9715. As showin in output table.
SOURCE OUTPUT
0514377920 971514377920
544233920 971544233920
971971511233920 971511233920
9710511233920 971511233920
I tried following which works for first case.
SELECT REGEXP_REPLACE ('0544377905', '^(\05*)', '9715')FROM dual;
But following is not working, for second case:
SELECT REGEXP_REPLACE ('544377905', '^(\5*)', '9715')FROM dual;
Something is wrong with my regular expression. As I am getting: ORA-12727: invalid back reference in regular expression.
You can provide your four patterns using alternation; that is, in parentheses with a vertical bar between them:
with t(source) as (
select '0514377920' from dual
union all select '544233920' from dual
union all select '971971511233920' from dual
union all select '9710511233920' from dual
)
SELECT source, REGEXP_REPLACE (source, '^(05|5|9719715|97105)', '9715') as output
FROM t;
SOURCE OUTPUT
--------------- --------------------
0514377920 971514377920
544233920 971544233920
971971511233920 971511233920
9710511233920 971511233920
Depending on your data and any other restrictions you have, you may be able to make it as simple as replacing the first part of any string that has a 5 in it, which works for your small sample:
SELECT source, REGEXP_REPLACE (source, '^.[^5]?5', '9715') as output
FROM t;
That matches zero or more characters that are not 5, followed by a 5. That may be too simplistic for your real situation though.

SUM totals by FOR ALL ENTRIES itab keys

I want to execute a SELECT query on a database table that has 6 key fields, let's assume they are keyA, keyB, ..., keyF.
As input parameters to my ABAP function module I do receive an internal table with exactly that structure of the key fields, each entry in that internal table therefore corresponds to one tuple in the database table.
Thus I simply need to select all tuples from the database table that correspond to the entries in my internal table.
Furthermore, I want to aggregate an amount column in that database table in exactly the same query.
In pseudo SQL the query would look as follows:
SELECT SUM(amount) FROM table WHERE (keyA, keyB, keyC, keyD, keyE, keyF) IN {internal table}.
However, this representation is not possible in ABAP OpenSQL.
Only one column (such as keyA) is allowed to state, not a composite key. Furthermore I can only use 'selection tables' (those with SIGN, OPTIOn, LOW, HIGH) after they keyword IN.
Using FOR ALL ENTRIES seems feasible, however in this case I cannot use SUM since aggregation is not allowed in the same query.
Any suggestions?
For selecting records for each entry of an internal table, normally the for all entries idiom in ABAP Open SQL is your friend. In your case, you have the additional requirement to aggregate a sum. Unfortunately, the result set of a SELECT statement that works with for all entries is not allowed to use aggregate functions. In my eyes, the best way in this case is to compute the sum from the result set in the ABAP layer. The following example works in my system (note in passing: using the new ABAP language features that came with 7.40, you could considerably shorten the whole code).
report zz_ztmp_test.
start-of-selection.
perform test.
* Database table ZTMP_TEST :
* ID - key field - type CHAR10
* VALUE - no key field - type INT4
* Content: 'A' 10, 'B' 20, 'C' 30, 'D' 40, 'E' 50
types: ty_entries type standard table of ztmp_test.
* ---
form test.
data: lv_sum type i,
lt_result type ty_entries,
lt_keys type ty_entries.
perform fill_keys changing lt_keys.
if lt_keys is not initial.
select * into table lt_result
from ztmp_test
for all entries in lt_keys
where id = lt_keys-id.
endif.
perform get_sum using lt_result
changing lv_sum.
write: / lv_sum.
endform.
form fill_keys changing ct_keys type ty_entries.
append :
'A' to ct_keys,
'C' to ct_keys,
'E' to ct_keys.
endform.
form get_sum using it_entries type ty_entries
changing value(ev_sum) type i.
field-symbols: <ls_test> type ztmp_test.
clear ev_sum.
loop at it_entries assigning <ls_test>.
add <ls_test>-value to ev_sum.
endloop.
endform.
I would use FOR ALL ENTRIES to fetch all the related rows, then LOOP round the resulting table and add up the relevant field into a total. If you have ABAP 740 or later, you can use REDUCE operator to avoid having to loop round the table manually:
DATA(total) = REDUCE i( INIT sum = 0
FOR wa IN itab NEXT sum = sum + wa-field ).
One possible approach is simultaneous summarizing inside SELECT loop using statement SELECT...ENDSELECT statement.
Sample with calculating all order lines/quantities for the plant:
TYPES: BEGIN OF ls_collect,
werks TYPE t001w-werks,
menge TYPE ekpo-menge,
END OF ls_collect.
DATA: lt_collect TYPE TABLE OF ls_collect.
SELECT werks UP TO 100 ROWS
FROM t001w
INTO TABLE #DATA(lt_werks).
SELECT werks, menge
FROM ekpo
INTO #DATA(order)
FOR ALL ENTRIES IN #lt_werks
WHERE werks = #lt_werks-werks.
COLLECT order INTO lt_collect.
ENDSELECT.
The sample has no business sense and placed here just for educational purpose.
Another more robust and modern approach is CTE (Common Table Expressions) available since ABAP 751 version. This technique is specially intended among others for total/subtotal tasks:
WITH
+plants AS (
SELECT werks UP TO 100 ROWS
FROM t011w ),
+orders_by_plant AS (
SELECT SUM( menge )
FROM ekpo AS e
INNER JOIN +plants AS m
ON e~werks = m~werks
GROUP BY werks )
SELECT werks, menge
FROM +orders_by_plant
INTO TABLE #DATA(lt_sums)
ORDER BY werks.
cl_demo_output=>display( lt_sums ).
The first table expression +material is your internal table, the second +orders_by_mat quantities totals selected by the above materials and the last query is the final output query.

Query a manual list of data items

I would like to run a query involving joining a table to a manually generated list but am stuck trying to generate the manual list. There is an example of what I am attempting to do below:
SELECT
*
FROM
('29/12/2014', '30/12/2014', '30/12/2014') dates
;
Ideally I would want my output to look like:
29/12/2014
30/12/2014
31/12/2014
What's your Teradata release?
In TD14 there's STRTOK_SPLIT_TO_TABLE:
SELECT *
FROM TABLE (STRTOK_SPLIT_TO_TABLE(1 -- any dummy value
,'29/12/2014,30/12/2014,30/12/2014' -- any delimited string
,',' -- delimiter
)
RETURNS (outkey INTEGER
,tokennum INTEGER
,token VARCHAR(20) CHARACTER SET UNICODE) -- modify to match the actual size
) AS d
You can easily put this in a Derived Table and then join to it.
inkey (here the dummy value 1) is a numeric or string column, usually a key. Can be used for joining back to the original row.
outkey is the same as inkey.
tokennum is the ordinal position of the token in the input string.
token is the extracted substring.
Try this:
select '29/12/2014'
union
select '30/12/2014'
union
...
It should work in Teradata as well as in MySql.

Resources