Different Regional Setting Different Format Float in Delphi - sql

I want to make Insert SQL Statement in Delphi using BDE Paradox which is
value_a := 0,123;
value_b := 0,234;
value_c := 0,345;
insert into mst_value values (value_a, value_b, value_c);
it shows the error like 'invalid SQL parameter' after debugging, it shows that the sql complete syntax like
insert into mst_value values (0,123, 0,234, 0,345)
which is supposed to be dot but comma in the decimal, so I format it using formatfloat('#.###, value_a), ...` it still using comma, after change the regional setting on Control Panel to English, the SQL parameter is correct, this is because the currency or number format there is just like 123,123,123.00, so, how can I format the decimal number but from another country e.g Indonesia with the format like 123,123,123,123.00 not 123.123.123,00. thanks before...

Try in this way, before calling the formatFloat function, you
can set appropriate value for Delphi's variable ThousandsSeparator and DecimalSeparator :
FormatFloat( "$##.000", value_a );

how do you make string like that ?
"insert into mst_value values (0,123, 0,234, 0,345) "
It is aking for SQL Injection, that wouldallow anyone to break into your program.
Use TQuery.Params instead, with strict datatype checking.
More reasoning on this in comments at http://issuetracker.delphi-jedi.org/view.php?id=5916

I am with Arioch on this. You should use a parameterized query instead. That will let the DB engine handle the formatting for you, eg:
value_a := 0,123;
value_b := 0,234;
value_c := 0,345;
Query.SQL.Text := 'insert into mst_value values (:value_a, :value_b, :value_c)';
Query.ParamByName('value_a').AsFloat := value_a;
Query.ParamByName('value_a').AsFloat := value_b;
Query.ParamByName('value_a').AsFloat := value_c;
Query.ExecSQL;

Related

Why is Oracle not treating '' as NULL in a BETWEEN with NVL comparison

I have this query, not filling in any data as the variables. Expecting a row; I get nothing.
select 1 from dual
where SYSDATE BETWEEN NVL(:START_DATE, SYSDATE) AND NVL(:END_DATE, SYSDATE)
I try this, expecting a row, and I get a row.
select 1 from dual
where SYSDATE BETWEEN SYSDATE AND SYSDATE
Something is not working as expected. An NVL should treat '' as NULL.
I try this, expecting a row, and I get a row.
select 1 from dual
where '' is null
If '' IS NULL, which we have now proven, this should return a row. But it's not.
select 1 from dual
where SYSDATE BETWEEN NVL('', SYSDATE) AND NVL('', SYSDATE)
There are some interesting answers here, but I'm not sure anyone has yet explained quite why your query returns no rows. (Apologies if they have and I missed it - just ignore me then!)
It is because NVL('', SYSDATE) is returning a character string, since the first argument '' is a character strng. So NVL('', SYSDATE) is equivalent to NVL('', TO_CHAR(SYSDATE)). And since we haven't specified the format for the TO_CHAR, Oracle will use the default which usually doesn't include a time component.
So this:
where SYSDATE BETWEEN NVL(:START_DATE, SYSDATE) AND NVL(:END_DATE, SYSDATE)
Is being treated somethinlg like this:
where SYSDATE BETWEEN '25-APR-2020' AND '25-APR-2020'
Oracle will then convert those strings back into dates to perform the BETWEEN, so will assume 00:00:00 for the time of day. Therefore unless you run this at exactly midnight, it will not return a row.
However, if you set your default date format to include the time like this:
alter session set nls_date_format = 'DD-MON-YYYY HH24:MI:SS';
You will find it now works, because the time component of the date no longer gets lost in the implicit conversions.
Probably the best way to deal with this though is to ensure you are dealing with dates and not strings at all:
select 1 from dual
where SYSDATE BETWEEN NVL(TO_DATE(:START_DATE), SYSDATE)
AND NVL(TO_DATE(:END_DATE), SYSDATE)
It ain't what you don't know that'll kill you. It's "what you know" that just ain't so...
The reason that Oracle isn't treating '' as NULL is because '' is not a NULL - it's a zero-length string constant. Now, right here people are going to jump in and tell me I'm wrong, and that I don't know what I'm talking about, and question my parentage, and probably start a petition to deny my Social Security and kick me out of the Old Farts Home, but I'm right. Here's a demonstration:
DECLARE
vStr VARCHAR2(10) := '';
cStr CHAR(10) := '';
vNULL VARCHAR2(10) := NULL;
cNULL CHAR(10) := NULL;
nStr NUMBER := '';
nNULL NUMBER := NULL;
BEGIN
DBMS_OUTPUT.PUT_LINE('LENGTH(vStr) = ' || LENGTH(vStr));
DBMS_OUTPUT.PUT_LINE('LENGTH(cStr) = ' || LENGTH(cStr));
DBMS_OUTPUT.PUT_LINE('LENGTH(vNULL) = ' || LENGTH(vNULL));
DBMS_OUTPUT.PUT_LINE('LENGTH(cNULL) = ' || LENGTH(cNULL));
DBMS_OUTPUT.PUT_LINE('LENGTH(nStr) = ' || LENGTH(nStr));
DBMS_OUTPUT.PUT_LINE('LENGTH(nNULL) = ' || LENGTH(nNULL));
END;
Now without looking down below (yes, I know - it's hard. Try... :-) what output do you expect from the code above? If you're like most people (including me, until I tripped over this a few years ago) you'd expect it to be:
LENGTH(vStr) =
LENGTH(cStr) =
LENGTH(vNULL) =
LENGTH(cNULL) =
LENGTH(nStr) =
LENGTH(nNULL) =
That is, you'd expect the LENGTH function to return NULL when applied to all those variables - because they should all be NULL, right?
But that's not what you get (see this db<>fiddle). What you actually get is:
LENGTH(vStr) =
LENGTH(cStr) = 10
LENGTH(vNULL) =
LENGTH(cNULL) =
LENGTH(nStr) =
LENGTH(nNULL) =
Whoa! Wait!! What's that 10 doing in there?!?
Well, it's pretty simple. Think about the semantics of the CHAR data type in PL/SQL. If you assign a string to a CHAR variable or field, and the length of the string assigned is shorter than the defined length of the variable or field, the value assigned to the variable or field is padded on the right to the full defined width of the variable/field - in this case, 10 characters. So when a '' - that is, a zero-length string constant - is assigned to the variable cStr, the value assigned to the variable is padded on the right to the defined width of the variable, so cStr ends up filled with 10 blanks. But when a NULL is assigned to that same character variable it ends up being set to NULL, as expected, and the LENGTH function returns NULL, as expected.
The semantics of VARCHAR2 (and, for the moment, VARCHAR - at least until Oracle gets around to supporting ANSI semantics for VARCHAR - which they're going to do Real Soon Now (tm)) are different in Oracle. When a variable/field of type VARCHAR2 is assigned a value it doesn't do any padding; instead, it assigns only the significant characters of the source string to the variable or field, and if the resultant length of the string assigned to the variable is zero then the variable or field is set to NULL, in accordance with the Oracle rule that "zero-length string values are the same as NULL". But this happens at the time that the value is assigned to the variable. '' on its own is still a zero-length string constant.
Just remember - it ain't what you don't know that'll kill you. It's "what you know" that just ain't so... :-)
The issue is that some user interfaces (including, annoyingly, SQL*Plus and SQL Developer) don't support the DATE data type for bind variables.
This is a limitation of those user interfaces. Oracle SQL is perfectly capable of working with DATE bind variables, and if you are able to pass to it NULL of data type DATE, it will process that as expected. See an illustration below.
Some answers, posted already, deal with the secondary issue - if you pass in NULL of data type CHAR or VARCHAR2, what happens? I don't think understanding the precise reason is important; what is exceptionally important is to understand this limitation of front-end programs, and - if you are forced to use them to interact with the database - make sure your bind variables are string data type, and the query reflects that (by using TO_DATE with proper date format model).
If you are using PL/SQL, and/or perhaps other calling environments (not sure about ApEx for example, since I don't use it), you can pass in NULL of DATE data type directly, and get the expected behavior. Here is an illustration: I build your query as a dynamic query, with bind variables, and I invoke it and pass to it NULL of data type DATE. Look what happens:
declare
l_sql clob;
l_n number;
begin
l_sql := 'select 1
from dual
where sysdate between nvl(:start_date, sysdate)
and nvl(:end_date , sysdate)';
execute immediate l_sql into l_n using cast(null as date), cast(null as date);
dbms_output.put_line(l_n);
end;
/
1
PL/SQL procedure successfully completed.
You have to use like the below to match null with null to get a row
select 1 from dual
where nvl('',SYSDATE) BETWEEN NVL('', SYSDATE) AND NVL('', SYSDATE)

Number formatting in Oracle using TO_CHAR

Proper way to format the numbers in ORACLE stored procedures.
I need to display currency fields with 2 decimals.
Expected output is as follows:
0 > 0.00
5 > 5.00
1253.6 > 1253.60
1253.689 > 1253.69
Below worked for me:
select to_char(9876.23 , 'fm999990.00') from dual;
But this has the issue of hard coding a bunch of 9s. If I give a larger number it will be displayed as "##############"
Is there any other way I can do this?
I need to display currency fields with 2 decimals.
Ensure you use the number data-type with scale and precision appropriate to the data rather than using NUMBER without scale and precision. If you are going to be storing dollars/euroes/pounds/etc. then the Gross World Product was of the order of $100,000,000,000,000 in 2014. Lets assume that you are not going to be dealing with more than this[citation needed] then your currency column can be:
NUMBER(17,2)
If you get a value that is bigger than that then you need to perform a sanity check on your data and think whether an amount bigger than the world's gross product makes sense. If you are going to store the values as, for example, Yen or Zimbabwe dollars then adjust the scale appropriately.
You could even define a sub-type in a package as:
CREATE PACKAGE currencies_pkg IS
SUBTYPE currency_type IS NUMBER(17,2);
FUNCTION formatCurrency(
amount IN CURRENCY_TYPE
) RETURN VARCHAR2;
END;
/
And your code to format it can be:
CREATE PACKAGE BODY currencies_pkg IS
FUNCTION formatCurrency(
amount IN CURRENCY_TYPE
) RETURN VARCHAR2
IS
BEGIN
RETURN TO_CHAR( currency_value, 'FM999999999999990D00' );
END;
END;
/
Then if you reference that sub-type in your stored procedures/packages you will not be able to exceed the maximum size of the currency data type without an exception being raised. The format model for displaying the value only needs to be defined in a single place and since the input is limited to the currency sub-type, then the formatting function will never exceed the imposed scale/precision and cannot output #s.
CREATE PROCEDURE your_procedure(
in_value1 IN ACCOUNTS_TABLE.ACCOUNT_BALANCE%TYPE,
in_value2 IN ACCOUNTS_TABLE.ACCOUNT_BALANCE%TYPE
)
IS
v_value CURRENCIES_PKG.CURRENCY_TYPE;
BEGIN
-- Do something
v_value := in_value1 + in_value2;
-- Output formatted value
DBMS_OUTPUT.PUT_LINE( CURRENCIES_PKG.formatCurrency( v_value ) );
END;
/
Why is "hardcoding a bunch of 9s" an issue? (It's how you need to do it if you plan to use TO_CHAR)
select to_char(9876.23 , 'fm9999999999999999999990D00') from dual;
ps; you might want to consider using D rather than . (not every country uses . as a decimal separator - D is language sensitive and will use the appropriate symbol)

How to generate XML data in Oracle which has Empty string as an attribute

I want to generate an XML element using Oracle's XML documentation generation support features that looks like this
<Example Attr=""></Example>
Attr is an attribute of element Example and has a value of empty string "".
When I tried to generate an XML Element using Oracle's XML functions, I couldn't generate an XML element which has an attribute whose value is an empty string.
select XMLELEMENT("hello", xmlattributes('' as "Max")) from dual
The result of the above query is
<hello></hello>
Note: there is no space between the single quotes for Max attribute.
However my requirement is
<hello Max=""></hello> -- there is no space between the double quotes.
Is there a way to do this?
As you're aware, for XMLAtttribute "if the value_expr is null, then no attribute is created for that value expression".
You can work around this with InsertChildXML but it isn't terribly pretty:
select insertchildxml(xmlelement("hello"), '/hello', '#Max', null) from dual;
INSERTCHILDXML(XMLELEMENT("HELLO"),'/HELLO','#MAX',NULL)
--------------------------------------------------------------------------------
<hello Max=""/>
... and as you can see it collapses an empty node, but that's only a potentially issue if you want this to look exactly as you showed - it's valid XML still. There is an even uglier way around that if you really need to.
That also suggests an alternative to #smnbbrv's replace:
select updatexml(xmlelement("hello", xmlattributes('$$IMPOSSIBLE-VALUE$$' as "Max")),
'/hello[#Max="$$IMPOSSIBLE-VALUE$$"]/#Max', null) from dual;
UPDATEXML(XMLELEMENT("HELLO",XMLATTRIBUTES('$$IMPOSSIBLE-VALUE$$'AS"MAX")),'/HEL
--------------------------------------------------------------------------------
<hello Max=""/>
which might be easier if your max attribute value is coming from data as you can NVL it to the impossible value. I'm not a fan of using magic values though really.
What about setting the property value to some impossible value and then replace it with the value you need (so, empty string in your case)?
select replace(
XMLELEMENT("hello", xmlattributes('$$IMPOSSIBLE-VALUE$$' as "Max")).getStringVal(),
'$$IMPOSSIBLE-VALUE$$'
)
from dual;
I assume you anyway in the end need the string value, so even if this XMLELEMENT is just an example of the problem and you have a biiiig XML generated, you still can generate it first and then, finally, replace all the values with one command as shown above.
I'm using this approach (you write the source attribute instead of the "null"). This way, you don't have to search for some overall impossible value, it's enough to be impossible for that attribute.
select
replace(XMLELEMENT("hello", xmlattributes(nvl(null,' ') as "Max")).getstringval(),
'Max=" "',
'Max=""')
from dual
I used updatexml() similar to A. Poole's example to create an empty string
set serveroutput on
DECLARE
xa xmltype;
xb xmltype;
BEGIN
xa := xmltype('<surfbreaks>'||
'<break lineNum="0" recordtype="empty">d street </break>'||
'<break lineNum="0" recordtype="empty">recordtype="empty" </break>'||
'</surfbreaks>');
dbms_output.put_line ('Before:');
dbms_output.put_line (xa.getclobval);
select UPDATEXML(xa, '//#recordtype', '') into xb from dual;
dbms_output.put_line ('After:');
dbms_output.put_line (xb.getclobval);
END; /

Trying to use a where statement in cursor creation (PL/SQL)

I'm trying to create a block that accepts input from a prompt and uses that input to filter the result set for the cursor. Keep in mind I'm a novice here so I maybe making a very routine mistake, and thank you for your help. My current code is below.
Set serveroutput on
DECLARE
ACCEPT a PROMPT “Please Enter a Date, eg. Format - 01 or 30"
datev char
datev := &a;
CURSOR cur_day_cursor IS
SELECT Arrival_Date Adate
FROM FLIGHT
WHERE TO_CHAR(Arrival_Date, ‘DD’) = datev;
cur_day_cursor_var cur_day_cursor%ROWTYPE;
BEGIN
OPEN Cur_day_cursor;
LOOP
Fetch Cur_day_cursor
INTO cur_day_cursor_var;
EXIT WHEN cur_day_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE (cur_day_cursor_var.Adate);
END LOOP;
IF cur_day_cursor%ISOPEN THEN
CLOSE cur_day_cursor;
END IF;
END;
The where statement is causing my errors, so I was thinking that I may have to let the cursor collect all the data and then filter it when displaying, but I'm not sure if I can even do that.
The error I keep receiving ERROR at line 9:-
ORA-06550: line 9, column 1:
PLS-00103: Encountered the symbol "WHERE" when expecting one of the following:
begin function pragma procedure subtype type
current cursor delete
exists prior
I don't know exactly why Oracle is reporting the error at the WHERE. Sometimes the parser gets pretty confused by bad syntax and doesn't point to the real problem. You have several syntax errors before the cursor definition.
ACCEPT is a SQLPlus command, not a PL/SQL statement. Move your ACCEPT line above the DECLARE.
Also, your variable declaration and initialization are incorrect. The assignment should be part of the declaration line; you need to provide a length for the CHAR datatype; and the substitution value should be in quotes to be treated as a string. A valid version of your lines would be:
datev char(2) := '&a';
I ran the same query as above, and got the results perfectly fine.
You have few syntax as well as logical error which I corrected in your query. The syntax error(s) are -
datev char
datev := &a;
You can't do such an initialization in PL/SQL. You probably have to complete it in a single line like below -
datev char := &a;
The logical mistake(s) are -
Why use a CHAR variable to store data when you know that the value being returned is NUMBER.
You expect numbers from 1-31; then why do you choose the default size of char which as 1. It will fail if you provide a 2-digit number
Even if you increase the size of CHAR to CHAR(2), you will not get results when the users enters a number like 1 or 01, because for character wise comparison, '1' != '1 '(Mark the extra space at the end, because of char(2)); and also '1' != '01'.
The only solution for above is to use a NUMBER datatype.
Now here I am posting my query which is similar to your query, with a change of column name and table name. Please replace with your required names and try -
(Take care not to execute the ACCEPT....) with the PL/SQL block. It should be done in the SQL prompt first and then the other DECLARE section should be run.
--ACCEPT a NUMBER PROMPT 'Please Enter a Date, eg. Format - 01 or 30 :'
--Run the above line first in SQL Prompt and then execute the rest as whole
DECLARE
datev NUMBER(2) := &a;
CURSOR cur_day_cursor IS
SELECT Ename, HireDate Adate
FROM Emp
WHERE TO_CHAR(HireDate, 'D') = datev;
cur_day_cursor_var cur_day_cursor%ROWTYPE;
BEGIN
OPEN Cur_day_cursor;
LOOP
Fetch Cur_day_cursor
INTO cur_day_cursor_var;
EXIT WHEN cur_day_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE (cur_day_cursor_var.Adate);
END LOOP;
IF cur_day_cursor%ISOPEN THEN
CLOSE cur_day_cursor;
END IF;
END;
/
It appears that the problem is that the single-quotes around ‘DD’ aren't single-quotes. It looks like the code was created in an editor which changes apostrophes into those special "look kind of like single quotes but aren't really" characters. Replace the original version of the WHERE clause with the following:
WHERE TO_CHAR(Arrival_Date, 'DD') = datev;
and I suspect you'll be fine.
And get yourself a good code editor. :-)

oracle stored procedure - local variable behaving mysteriously

This is how I am delcaring the local variables:
team_counter number (38) := 0;
username varchar2(50) := '';
This is how I am trying to use/see their value after using some select into statement:
dbms_output.put_line(team_counter||'.'||username);
if team_counter< 30 AND username <>'' then
begin
dbms_output.put_line('yuhj');
end;
end if;
The second output is not being printed!
The first output is being printed as '1.tuser' which I was expecting.
This is because you're trying to compare a string with a 0 length string using an inequality operator.
Oracle assumes that 0 length strings are equivalent to NULL and will not evaluate comparisons that don't use the NULL specific conditional. To quote:
Oracle Database currently treats a character value with a length of
zero as null. However, this may not continue to be true in future
releases, and Oracle recommends that you do not treat empty strings
the same as nulls.
Simply put this means that your IF statement should be:
if team_counter < 30 and username is not null then
...
As an additional note there's no need for the begin ... end around the dbms_output.put_line. As you're not catching any exceptions explicitly related to this call or declaring additional variables etc there's no real need.