PLSQL Encountered the symbol "YES" when expecting one of the following - plsql

Hi I am trying to create aa table of summary stats using PLSQL. I am new to PLSQL. I keep getting this error.
Encountered the symbol "YES" when expecting one of the following
Below is my code.
DECLARE
AGE NUMBER;
CAMPAIGN NUMBER;
PDAYS NUMBER;
PREVIOUS NUMBER;
POUTCOME NUMBER;
EMP_VAR_RATE NUMBER;
CONS_PRICE_IDX NUMBER;
CONS_CONF_IDX NUMBER;
EURIBOR3M NUMBER;
Y VARCHAR;
BEGIN
EXECUTE IMMEDIATE ('
CREATE TABLE SUMMARY_STAT
AS
SELECT
ROUND(AVG(AGE)) AS AVERAGE_AGE
,ROUND(AVG(CAMPAIGN)) AS AVERAGE_CONTACTS_MADE
,ROUND(AVG(PDAYS)) AS NO_DAYS_PASSED
,ROUND(AVG(PREVIOUS)) AS AVERAGE_PREVIOUS_CONTACTS_MADE
,ROUND(AVG(EMP_VAR_RATE)) AS AVERAGE_EMPLOYEE_VARIATION
,ROUND(AVG(CONS_PRICE_IDX)) AS AVERAGE_CONSUMER_PRICE_INDEX
,ROUND(AVG(CONS_CONF_IDX)) AS AVERAGE_CONSUMER_CONFIDENCE_INDEX
,ROUND(AVG(EURIBOR3M)) AS AVERAGE_EURIBOR
,COUNT(CASE WHEN Y = 'yes' THEN 1 end) AS NUMBER_CUSTOMERS_TOOK_DEPOSIT
,COUNT(CASE WHEN Y = 'no' THEN 1 end) AS NUMBER_CUSTOMERS_DIDNT_TAKE_DEPOSIT
FROM BANK_DATA
');
END;

The problem is you have single quotes within a larger string. Unfortunately from the interpreter's view point your string in 'Create ... when Y = ' is one string and the next token is the word "yes" which the interpreter does not understand.
You have 2 options:
double the quotes needed inside the larger string.
use the alternative quote delimiter.
Using quote delimiter you get:
BEGIN
EXECUTE IMMEDIATE (q'[
CREATE TABLE SUMMARY_STAT
AS
SELECT
ROUND(AVG(AGE)) AS AVERAGE_AGE
,ROUND(AVG(CAMPAIGN)) AS AVERAGE_CONTACTS_MADE
,ROUND(AVG(PDAYS)) AS NO_DAYS_PASSED
,ROUND(AVG(PREVIOUS)) AS AVERAGE_PREVIOUS_CONTACTS_MADE
,ROUND(AVG(EMP_VAR_RATE)) AS AVERAGE_EMPLOYEE_VARIATION
,ROUND(AVG(CONS_PRICE_IDX)) AS AVERAGE_CONSUMER_PRICE_INDEX
,ROUND(AVG(CONS_CONF_IDX)) AS AVERAGE_CONSUMER_CONFIDENCE_INDEX
,ROUND(AVG(EURIBOR3M)) AS AVERAGE_EURIBOR
,COUNT(CASE WHEN Y = 'yes' THEN 1 end) AS NUMBER_CUSTOMERS_TOOK_DEPOSIT
,COUNT(CASE WHEN Y = 'no' THEN 1 end) AS NUMBER_CUSTOMERS_DIDNT_TAKE_DEPOSIT
FROM BANK_DATA
]');
END;

Related

PLSQL SUBSTR function ignore the trailing zero

select TO_NUMBER (SUBSTR(10.31, INSTR (10.31, '.') + 1)) from dual
Above query returns 31 as the output. But below query returns 3 as the output.
select TO_NUMBER (SUBSTR(10.30, INSTR (10.30, '.') + 1)) from dual
How could I get the 30 as the output instead of the 3?
As it seems (from comments) that you are starting with a numeric value that you want to turn into words, you should begin by splitting it into dollars and cents.
If you really need to use substr etc, then you could start with a known format, such as to_char(amount,'fm9990.00'), so it will be a string with exactly two decimal places. However, if you have the numeric value it would be easier to convert it into the desired units using arithmetic functions. Whole dollars are trunc(amount) and cents are 100 * mod(amount,1).
Another issue is that the 'Jsp' date format approach can't handle zeroes. If you are using Oracle 12.2 or later there is a workaround using the default on conversion error clause:
create table demo
( amount number(6,2) );
insert into demo values (10.3);
insert into demo values (.25);
insert into demo values (25);
select amount
, nvl(to_char(to_date(trunc(amount) default null on conversion error,'J'),'Jsp'),'Zero') as dollars
, nvl(to_char(to_date(100 * mod(amount,1) default null on conversion error,'J'),'Jsp'),'Zero') as cents
from demo;
AMOUNT DOLLARS CENTS
-------- ------------ -------------
10.30 Ten Thirty
25.00 Twenty-Five Zero
0.25 Zero Twenty-Five
In 12.1 you could get around it using an inline function (maybe not a bad idea even in later versions, to simplify the rest of the query):
with
function to_words(num number) return varchar2 as
begin
return
case num
when 0 then 'Zero'
else to_char(to_date(num,'J'),'Jsp')
end;
end;
select amount
, to_words(trunc(amount)) as dollars
, to_words(100 * mod(amount,1)) as cents
from demo;
For values greater than 5373484 (the Julian representation of date '9999-12-31'), you can use this from Ask Tom: Spell the number (converted here to a WITH clause, but you can create it as a standalone function):
with function spell_number
( p_number in number )
return varchar2
as
-- Tom Kyte, 2001:
-- https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:1407603857650
l_num varchar2(50) := trunc(p_number);
l_return varchar2(4000);
type myarray is table of varchar2(15);
l_str myarray :=
myarray
( ''
, ' thousand '
, ' million '
, ' billion '
, ' trillion '
, ' quadrillion '
, ' quintillion '
, ' sextillion '
, ' septillion '
, ' octillion '
, ' nonillion '
, ' decillion '
, ' undecillion '
, ' duodecillion ');
begin
for i in 1 .. l_str.count loop
exit when l_num is null;
if substr(l_num, length(l_num) -2, 3) <> 0 then
l_return := to_char(to_date(substr(l_num, length(l_num) - 2, 3), 'J'), 'Jsp') || l_str(i) || l_return;
end if;
l_num := substr(l_num, 1, length(l_num) - 3);
end loop;
return l_return;
end spell_number;
select amount
, spell_number(trunc(amount)) as dollars
, spell_number(100 * mod(amount,1)) as cents
from demo
/
I am actually surprised that your current query is even running without error, given that Oracle's SUBSTR function is supposed to operate on strings, not numbers. That being said, if you properly use your current query with strings, then it works:
SELECT TO_NUMBER(SUBSTR('10.30', INSTR ('10.30', '.') + 1)) FROM dual; -- returns 30
A more compact (though not necessarily more performant) way of doing this might be to use REGEXP_SUBSTR:
SELECT REGEXP_SUBSTR('10.30', '[0-9]+$') FROM dual;
This would retain only digits appearing after the decimal point, in the case that a decimal point be present. Otherwise, it would just return all numbers for inputs which have no decimal component.

When using CAST in plsql case, showing error

I want to put single clob data column value in 2 varchar2 columns by checking the length of CLOB column, but i am getting error in case statement, line is marked in * *, it says syntex error , what am i doing wrong
DECLARE
v_tot_rows NUMBER (3);
rqst_xml_1 ISG.CERT_TEST_CASE_GTWY_TXN.RQST_XML_1_TX%TYPE;
rqst_xml_2 ISG.CERT_TEST_CASE_GTWY_TXN.RQST_XML_2_TX%TYPE;
CURSOR req_res_populate_cur
IS
SELECT scptc.SWR_CERT_PRJCT_TEST_CASE_ID,
orb_txn.MIME_HEAD_TX,
orb_txn.RSPNS_XML_TX,
orb_msg.RQST_GNRL_VLD_JSON_TX,
orb_msg.RQST_TEST_CASE_VLD_JSON_TX,
orb_msg.MRCH_ID
(
CASE
WHEN DBMS_LOB.GETLENGTH (orb_txn.RQST_XML_TX) <= 4000 THEN
rqst_xml_1 := CAST ( orb_txn . RQST_XML_TX AS VARCHAR2 ( 4000 ) ) * ,
rqst_xml_2 := ''
WHEN DBMS_LOB.GETLENGTH(orb_txn.RQST_XML_TX)>4000 THEN
rqst_xml_1:=CAST(substr(orb_txn.RQST_XML_TX,1,4000) AS VARCHAR2(4000)),
rqst_xml_2:=CAST(substr(orb_txn.RQST_XML_TX,4001)
END
)
FROM ISG.online_messages msg
JOIN ISG.SWR_CERT_PRJCT_TEST_CASE scptc
ON msg.online_message_id = scptc.TXN_ID,
ISG.GTWY_PLTFM_TXN_MSG orb_msg
JOIN ISG.GTWY_PLTFM_TXN orb_txn
ON orb_msg.GTWY_PLTFM_TXN_ID = orb_txn.GTWY_PLTFM_TXN_ID
WHERE msg.SPEC_ID = 60;;
BEGIN
FOR req_res IN req_res_populate_cur
LOOP
DBMS_OUTPUT.PUT_LINE (req_res.SWR_CERT_PRJCT_TEST_CASE_ID,
req_res.MIME_HEAD_TX,
req_res.rqst_xml_1,
req_res.rqst_xml_2,
req_res.RSPNS_XML_TX,
req_res.RQST_GNRL_VLD_JSON_TX,
req_res.RQST_TEST_CASE_VLD_JSON_TX,
req_res.MRCH_ID);
END LOOP;
END;
Your problem is your invalid SELECT-statement. You're trying to set variables (of your plsql-block) within a query. That's not intended or allowed.
You need to select the values into columns. Here i added two columns. One for each xml-value.
SELECT scptc.SWR_CERT_PRJCT_TEST_CASE_ID,
orb_txn.MIME_HEAD_TX,
orb_txn.RSPNS_XML_TX,
orb_msg.RQST_GNRL_VLD_JSON_TX,
orb_msg.RQST_TEST_CASE_VLD_JSON_TX,
orb_msg.MRCH_ID,
CASE --Column-Start
WHEN DBMS_LOB.GETLENGTH (orb_txn.RQST_XML_TX) <= 4000
THEN
CAST (orb_txn.RQST_XML_TX AS VARCHAR2 (4000))
WHEN DBMS_LOB.GETLENGTH (orb_txn.RQST_XML_TX) > 4000
THEN
CAST (
SUBSTR (orb_txn.RQST_XML_TX, 1, 4000) AS VARCHAR2 (4000))
END
AS my_rqst_xml_1, -- Column-End. In this column you'll have the value for xml_1
CASE --Column-Start
WHEN DBMS_LOB.GETLENGTH (orb_txn.RQST_XML_TX) <= 4000
THEN
''
WHEN DBMS_LOB.GETLENGTH (orb_txn.RQST_XML_TX) > 4000
THEN
CAST (SUBSTR (orb_txn.RQST_XML_TX, 4001) AS VARCHAR2 (4000))
END
AS my_rqst_xml_2 -- Column-End. In this column you'll have the value for xml_12
FROM ISG.online_messages msg
JOIN ISG.SWR_CERT_PRJCT_TEST_CASE scptc
ON msg.online_message_id = scptc.TXN_ID,
ISG.GTWY_PLTFM_TXN_MSG orb_msg
JOIN ISG.GTWY_PLTFM_TXN orb_txn
ON orb_msg.GTWY_PLTFM_TXN_ID = orb_txn.GTWY_PLTFM_TXN_ID
WHERE msg.SPEC_ID = 60
Afterwards you can work with the result and get the values from it.
BEGIN
FOR req_res IN req_res_populate_cur
LOOP
DBMS_OUTPUT.PUT_LINE (req_res.SWR_CERT_PRJCT_TEST_CASE_ID,
req_res.MIME_HEAD_TX,
req_res.my_rqst_xml_1, -- here we can see the values
req_res.my_rqst_xml_2, -- here too
req_res.RSPNS_XML_TX,
req_res.RQST_GNRL_VLD_JSON_TX,
req_res.RQST_TEST_CASE_VLD_JSON_TX,
req_res.MRCH_ID);
-- And here we could store the values into variables or call some procedures etc.
rqst_xml_1 := req_res.my_rqst_xml_1;
rqst_xml_2 := req_res.my_rqst_xml_2;
END LOOP;
END;
I've to guess, but it seems you didn't want to declare the variables:
rqst_xml_1 ISG.CERT_TEST_CASE_GTWY_TXN.RQST_XML_1_TX%TYPE;
rqst_xml_2 ISG.CERT_TEST_CASE_GTWY_TXN.RQST_XML_2_TX%TYPE;
This would be only needed if you want to work with the values.

Evaluating string/combination of variables as logical expression in oracle pl/sql

In my Pl/Sql code , I have three variables v_var1 , v_operand , v_var2 whose values are populated based on some logic (v_var1 & v_var2 can be date , number , varchar. Associated Operand will be according to data type only). A sample would be
v_var1 = 10 , v_operand = '=' , v_var2 = 20.
Based on these value , I have to evaluate whether the condition "v_var1 -v_operand- v_var2"is true or false.
Ex :- with above values, I have to evaluate whether 10 equals 20 or not.
How can I achieve this ? Can I pass the whole string as '10 = 20' to some function and get the result as false?
One way I can think of is to write CASE statements for evaluating but can there be a better way ?
You could use dynamic SQL to do the evaluation as a filter on the dual table:
declare
v_var1 varchar2(10) := '10';
v_operand varchar2(10) := '=';
v_var2 varchar2(10) := '20';
l_result number;
begin
execute immediate 'select count(*) from dual where :var1 ' || v_operand || ' :var2'
into l_result using v_var1, v_var2;
if l_result = 1 then
dbms_output.put_line('True');
else
dbms_output.put_line('False');
end if;
end;
/
PL/SQL procedure successfully completed.
False
If the condition is true the count will get 1, otherwise it will get 0, and you can then test that via the local variable you select the count into.
Holding dates and numbers as strings isn't ideal, even temporarily, but might be OK as long as you convert to/from the real data types consistently, e.g. always explicitly converting dates with to_date and to_char and specifying the format masks.

PLSQL invalid number error after fetch

sorry if this is an confusing post and the style is not proper but this is my first question but im having an error after i want to fetch c_latlong :
the things he will fetch are:
LAT value : 50,9459744669855
LON value : 5,9704909091251
ORA-01722: invalid number
ORA-06512: at "DOMINOS.GETLATLONG", line 14
ORA-06512: at "DOMINOS.ZOEKWINKELVOORADRES2", line 34
ORA-06512: at line 9
This is the code i use:
create or replace FUNCTION getLatLong(v_postcode varchar2, v_huisnummer varchar2)
RETURN varchar2
IS
v_lat NVARCHAR2(38) ;
v_long NVARCHAR2(38);
v_postcode_id varchar2(200);
v_all varchar2(200);
CURSOR c_latlong IS
SELECT LAT, LON
FROM POSTCODE
WHERE POSTCODE = v_postcode AND v_huisnummer BETWEEN MINNUMBER AND MAXNUMBER;
BEGIN
OPEN c_latlong;
FETCH c_latlong INTO v_lat, v_long;
v_all := v_lat || '-' || v_long;
-- print(v_all);
RETURN v_all;
CLOSE c_latlong;
END getLatLong;
create or replace FUNCTION zoekWinkelVoorAdres2(v_postcode varchar2, v_huisnummer varchar2)
RETURN varchar2
IS
v_array_this apex_application_global.vc_arr2 := apex_util.string_to_table(getLatLong(v_postcode,v_huisnummer), '-');
v_temp_postcode varchar2(200);
v_temp_huisnummer varchar2(200);
v_temp_id varchar2(200);
v_temp_winkel apex_application_global.vc_arr2;
v_array dbms_sql.varchar2_table;
v_max number;
v_closest_winkel varchar2(200);
v_closest_dis varchar2(200);
CURSOR c_all_stores_max IS
SELECT count(*)
FROM WINKEL;
CURSOR c_all_stores IS
SELECT ID, POSTCODE, HUISNR
FROM WINKEL;
CURSOR c_select_dis IS
SELECT WINKEL_ID, DISTANCE
FROM allwinkeldis ORDER BY DISTANCE DESC;
BEGIN
--DELETE FROM allwinkeldis;
OPEN c_all_stores_max;
FETCH c_all_stores_max INTO v_max;
CLOSE c_all_stores_max;
OPEN c_all_stores;
FOR i IN 1..v_max
LOOP
FETCH c_all_stores INTO v_temp_id, v_temp_postcode, v_temp_huisnummer;
-- print('-------------------1---------------------' ||v_temp_postcode);
print(getLatLong(v_temp_postcode,v_temp_huisnummer));
v_temp_winkel :=apex_util.string_to_table(getLatLong(v_temp_postcode,v_temp_huisnummer), '-');
-- print(v_temp_id || ' ' || v_temp_winkel(1) || ' ' || v_temp_winkel(2));
-- print('-------------------2---------------------');
--v_array(i) := v_temp_id ||','||distance(v_array_this(2), v_array_this(3),v_temp_winkel(2), v_temp_winkel(3));
INSERT INTO allwinkeldis(winkel_id, distance) VALUES (v_temp_id, distance(v_array_this(1), v_array_this(2), v_temp_winkel(1), v_temp_winkel(2)));
--print(v_temp_id ||' -------------- '||distance(v_array_this(2), v_array_this(3), v_temp_winkel(2),v_temp_winkel(3)));
END LOOP;
CLOSE c_all_stores;
OPEN c_select_dis;
FETCH c_select_dis INTO v_closest_winkel, v_closest_dis;
CLOSE c_select_dis;
print(v_closest_winkel || '--------------' || v_closest_dis);
END zoekWinkelVoorAdres2;
I'm not sure what is the data type of LAT and LON in your table POSTCODE.
But whatever variables you define to fetch data into, they must match these types.
For examples, assuming that LAT and LON are of the type NVARCHAR2, then I would rewrite your declaration section as follows:
CREATE OR REPLACE FUNCTION getLatLong(v_postcode varchar2, v_huisnummer varchar2)
RETURN varchar2
IS
v_postcode_id varchar2(200);
v_all varchar2(200);
CURSOR c_latlong IS
SELECT LAT, LON
FROM POSTCODE
WHERE POSTCODE = v_postcode AND v_huisnummer BETWEEN MINNUMBER AND MAXNUMBER;
TYPE temp_nvarchar2 IS TABLE OF NVARCHAR2(38);
v_lat temp_nvarchar2;
v_long temp_nvarchar2;
BEGIN
....
The invalid number error occurs when it expects a number and gets something else instead, like a varchar.
v_lat and v_lon are declared as NVARCHAR2. Is this the same datatype that is stored in POSTCODE? If not, this is the cause of the error.
It might also be having trouble with the line v_all := v_lat || '-' || v_long;. If v_lat or v_long are numbers, then you need to cast them to chars instead, like so:
v_all := TO_CHAR(v_lat) || '-' || TO_CHAR(v_long);

Parsing large text in xml format in Pl/Sql

I have a log table and the table has a varchar2 field which holds xml string like below:
In this example ClientName attribute did not change but Clientsurname changed.
I want to capture changed columns and their previous and new values.
The log table contains millions of records.
Which method can you suggest for parsing this data in an efficient way?
<r>
<columntag nameattribute="ClientName">
<new_value>Jeffrey</new_value>
<previous_value>Jeffrey</previous_value>
</columntag>
<columntag nameattribute="ClientSurname">
<new_value>Dijk</new_value>
<previous_value>Disk</previous_value>
</columntag>
</r>
Thank you
not 100% sure the below is what you are after but it should give you some ideas about how to go about it. Hope it is helpfull
CREATE TABLE "RM4SERV"."LOG_TEST" ( "TESTLOG" VARCHAR2(4000 BYTE))
Insert into RM4SERV.LOG_TEST (TESTLOG) values ('<r><columntag nameattribute="ClientName"><new_value>Jeffery</new_value><previous_value>Jeffery</previous_value> </columntag><columntag nameattribute="ClientSurname"><new_value>Dijk</new_value><previous_value>Disk</previous_value></columntag></r>');
Insert into RM4SERV.LOG_TEST (TESTLOG) values ('<r><columntag nameattribute="ClientName"><new_value>Jeffery</new_value><previous_value>Jeffery</previous_value> </columntag><columntag nameattribute="ClientSurname"><new_value>Disk</new_value><previous_value>Disk</previous_value></columntag></r>');
Insert into RM4SERV.LOG_TEST (TESTLOG) values ('<r><columntag nameattribute="ClientName"><new_value>Jeffery</new_value><previous_value>Jim</previous_value> </columntag><columntag nameattribute="ClientSurname"><new_value>Dijks</new_value><previous_value>Diskett</previous_value></columntag></r>');
declare
v_logrec varchar2(4000) := null;
v_recnum number := 0;
cursor c_logs is
select testlog from log_test;
cursor c_records is
select extractValue(x.column_value, '/columntag/#nameattribute') as column_name,
extractValue(x.column_value, '/columntag/new_value') as new_value,
extractValue(x.column_value, '/columntag/previous_value') as previous_value
from TABLE(XMLSequence(extract(xmltype.createxml(v_logrec), '//columntag'))) x
where extractValue(x.column_value, '/columntag/new_value') != extractValue(x.column_value, '/columntag/previous_value');
begin
for v_log in c_logs loop
v_logrec := v_log.testlog;
v_recnum := v_recnum + 1;
dbms_output.put_line(v_recnum);
for v_rec in c_records loop
SYS.dbms_output.put_line(v_rec.column_name || ' : *' || v_rec.new_value || '* : *' || v_rec.previous_value || '*');
end loop;
end loop;
end;
This would give you the below output (so Surname different in first record, nothing different in the second and both different in the third)...
1
ClientSurname : Dijk : Disk
2
3
ClientName : Jeffery : Jim
ClientSurname : Dijks : Diskett

Resources