How to sort by dynamic column in oracle? - sql

I have some complex oracle query, but I will try to make it simple. I have something like this:
SELECT TBL1.*, TBL2.*
FROM TABLE_1 TBL1
LEFT JOIN (
SELECT *
FROM
(
SELECT TBL2.VERSION_ID, TBL2.CONFIG_ID, TBL2.VALUE
FROM TABLE_2 TBL2
)
PIVOT
(
MAX(VALUE) FOR CONFIG_ID IN (:metadataClassConfigs)
)
) TBL2 ON TBL1.VERSION_ID = TBL2.VERSION_ID
ORDER BY
CASE
WHEN :orderByCustomClass IS NOT NULL THEN
CASE
WHEN :orderByCustomClass = 1 THEN TBL2."1"
WHEN :orderByCustomClass = 21 THEN TBL2."21"
WHEN :orderByCustomClass = 22 THEN TBL2."22"
WHEN :orderByCustomClass = 23 THEN TBL2."23"
WHEN :orderByCustomClass = 24 THEN TBL2."24"
WHEN :orderByCustomClass = 25 THEN TBL2."25"
WHEN :orderByCustomClass = 26 THEN TBL2."26"
WHEN :orderByCustomClass = 27 THEN TBL2."27"
WHEN :orderByCustomClass = 28 THEN TBL2."28"
WHEN :orderByCustomClass = 29 THEN TBL2."29"
WHEN :orderByCustomClass = 30 THEN TBL2."30"
WHEN :orderByCustomClass = 31 THEN TBL2."31"
WHEN :orderByCustomClass = 32 THEN TBL2."32"
WHEN :orderByCustomClass = 34 THEN TBL2."34"
WHEN :orderByCustomClass = 35 THEN TBL2."35"
WHEN :orderByCustomClass = 36 THEN TBL2."36"
WHEN :orderByCustomClass = 41 THEN TBL2."41"
WHEN :orderByCustomClass = 52 THEN TBL2."42"
END
END;
and this is working fine. This input parameters are: :metadataClassConfigs is the list of numbers (1,21,22,23,24,25,26,27,28,29,30,31,32,34,35,36,41,42) and :orderByCustomClass can be any of this number.
I have much more numbers then this list, more than 1000, so I am wondering how can I order by dynamic column something like:
WHEN :orderByCustomClass IS NOT NULL THEN TBL2."{:orderByCustomClass}"
?

There is a multiple way for dynamic SQL in Oracle PL/SQL. I'm assuming that you are talking about PL/SQL, because in other kind of clients (like python-oracle, jdbc) the only way to send a "query" is to create cursor from string. So you've always forced to build query kind of dynamically...
Native Dynamic SQL - execute immediate
Good for simple cases (look how to get the result - works best for one row - its more complicated for arrays).
The query is a string - so you can "build" it. If you need - you can use parameters with USING clause (each parameter in query must have colon as a prefix). Be aware that they are mapped by position in query - not by name.
declare
type t_rec is record (
<describe returned columns>
);
type t_result_array is table of t_rec index by pls_integer;
v_result_array t_result_array;
v_sort_column varchar2(4000);
begin
-- do some logic to determine name of column for order by:
v_sort_column := <some_logic determining column name for sorting>;
-- if logic is based on raw user input, then you should sanitize it:
v_sort_column := DBMS_ASSERT.QUALIFIED_SQL_NAME(v_sort_column);
-- build query based on v_sort_column value
execute immediate 'select ... from ...
order by '||v_sort_column
bulk collect into v_result_array;
<do something with result stored in v_result_array>
end;
/
Native Dynamic SQL - OPEN FOR
Very similar to execute immediate but based on cursor variable and OPEN FOR statement. To accomplish it you have to do 3 steps: open cursor, fetch rows and close cursor.
declare
type t_rec is record (
<describe returned columns>
);
type t_result_array is table of t_rec index by pls_integer;
v_result_array t_result_array;
v_sort_column varchar2(4000);
type t_ret_cursor is ref cursor return t_rec;
v_cursor t_ret_cursor;
begin
-- do some logic to determine name of column for order by:
v_sort_column := <some_logic determining column name for sorting>;
-- if logic is based on raw user input, then you should sanitize it:
v_sort_column := DBMS_ASSERT.QUALIFIED_SQL_NAME(v_sort_column);
open v_cursor for 'select ... from ...
order by '||v_sort_column;
fetch v_cursor bulk collect into v_cursor;
close v_cursor;
<do something with result stored in v_result_array>
end;
/
Dynamic SQL - DBMS_SQL package
This is the most flexible way of doing it - you can even conditionally change selected columns or dynamically check what kind of row is in result (number of columns, data types etc.). Furthermore, it is also one of the best in terms of performance.
I'm just putting information about this option here so you can see for yourself if you need these features and capabilities.
There are many more steps here and they are more complex: open cursor, parse, bind every parameter (optional), define columns, execute, fetch, access data, so I will not post any example. Probably it's an overkill for your purposes.

Related

Cursor returning no data inside a loop, but the data is definitely there

i have a very strange issue going on with nested cursor loops.
i've pasted the code below. The overall function of the code is to fetch each row from cursor cur1 and use some info from cur1 to run a loop on another cursor cur2. fairly straight forward - just like a nested 2-tier for loop. The issue I run into is that when the first (any?) iteration through cur2 produces NOTFOUND, the subsequent/other iterations also produce NOTFOUND even though i explicitly run the cursor select statement manually and see data. I've also modified the data so that the first iteration DOES have data as a test, and everything works fine...
create or replace PROCEDURE EvalEqu
(prim_indg_ref_in NUMBER,
stock_ref_in NUMBER)
IS
SD_REF STOCK_DETAILS.STOCK_DETAIL_REF%TYPE; -- Variable used to hold stock_details_ref Stock_details
CAL_REF CALCULATIONS.CALC_REF%TYPE; -- Variable used to hold calc_ref value
CALCSTRG CALCULATIONS.CALCULATION_STATEMENT%TYPE; -- Variable used to hold calculation string from Calculations
CALCSTRGTEMP CALCULATIONS.CALCULATION_STATEMENT%TYPE; -- Variable used to hold calculation as parm values are substitued in
CALCVALTEMP STOCK_DETAILS.DATA%TYPE; -- Variable used hold andcheck calculated value sig figs
CALCVAL STOCK_DETAILS.DATA%TYPE; -- Variable used to populate calc value on Stock_details once calcualted
PARM CALC_COA_VALS.PARM_NAME%TYPE; -- Variable used to hold parm names used for substitution
PARMVAL STOCK_COA_DATA.DATA%TYPE; -- Variable used to hold CoA value from form that replaces PARM value
--Define cursor to hold all calculations associated to a stock record
cursor stock_calc is select SD.STOCK_DETAIL_REF, CALC.CALC_REF, CALC.CALCULATION_STATEMENT
from stock_details sd,
calculations calc
where SD.LABEL_REF = calc.label_ref
and CALC.Prim_Ing_Ref = prim_indg_ref_in
and SD.STOCK_REF = stock_ref_in;
--Define cursor to hold all parm names and their respective CoA values from form
cursor calc_parms is select CCV.PARM_NAME, SCD.DATA
from calc_coa_vals ccv, stock_coa_data scd
where CCV.COA_VAL_REF=SCD.COA_VAL_REF
and SCD.STOCK_REF=stock_ref_in
and CCV.CALC_REF=CAL_REF;
BEGIN
-- Open calculation cursor
IF NOT stock_calc%ISOPEN then
OPEN stock_calc;
END IF;
-- Loop through each calcualtion for Stock to calculate values
LOOP
FETCH stock_calc INTO SD_REF, CAL_REF, CALCSTRG;
EXIT WHEN stock_calc%NOTFOUND;
-- Open parm cursor to loop through each parm used in calculation
IF NOT calc_parms%ISOPEN then
OPEN calc_parms;
END IF;
FETCH calc_parms into PARM, PARMVAL;
IF calc_parms%NOTFOUND
THEN
-- No parameters are associated with this calculation
-- It is a simple integer and can be returned as such
CALCVAL := CALCSTRG;
ELSE
WHILE calc_parms%FOUND
LOOP
CALCSTRGTEMP := REPLACE(CALCSTRG, PARM, PARMVAL);
CALCSTRG := CALCSTRGTEMP;
FETCH calc_parms into PARM, PARMVAL;
END LOOP;
CLOSE calc_parms;
-- evaluate the equation
EXECUTE IMMEDIATE 'SELECT to_char(('||CALCSTRG||'),''999999999990.099999999999999'') from dual ' INTO CALCVALTEMP;
IF instr(to_char(to_number(CALCVALTEMP)), '.') = 0 -- whole number or 0, no padding
THEN
CALCVAL := to_char(to_number(CALCVALTEMP));
ELSE
IF ABS(to_number(CALCVALTEMP)) < 1
THEN
IF instr(CALCVALTEMP, '-') = 0 -- Indicates positive number
THEN
EXECUTE IMMEDIATE 'SELECT LPAD(RPAD(to_CHAR(trunc(to_number('||CALCVALTEMP||'),12-(instr('||CALCVALTEMP||',''.'')-1))), 12,''0''), 13, ''0'') from dual ' INTO CALCVAL;
ELSE -- Negative number
EXECUTE IMMEDIATE 'SELECT LPAD(RPAD(to_CHAR(trunc(ABS(to_number('||CALCVALTEMP||')),12-(instr(ABS(to_number('||CALCVALTEMP||')),''.'')-2))), 12,''0''), 14, ''-0'') from dual ' INTO CALCVAL;
END IF;
ELSE
IF instr(CALCVALTEMP, '-') = 0 -- Indicates positive number
THEN
EXECUTE IMMEDIATE 'SELECT RPAD(to_CHAR(trunc(to_number('||CALCVALTEMP||'),12-(instr('||CALCVALTEMP||',''.'')-1))), 13,''0'') from dual ' INTO CALCVAL;
ELSE -- Negative number
EXECUTE IMMEDIATE 'SELECT RPAD(to_CHAR(trunc(to_number('||CALCVALTEMP||'),12-(instr('||CALCVALTEMP||',''.'')-2))), 14,''0'') from dual ' INTO CALCVAL;
END IF;
END IF;
END IF;
END IF;
-- Update stock_details record with newly calculated value
UPDATE stock_details
SET DATA = CALCVAL
WHERE STOCK_DETAIL_REF = SD_REF;
END LOOP;
CLOSE stock_calc;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
raise_application_error(-20001,'An error was encountered - '||SQLCODE||' -ERROR- '||SQLERRM);
END;
Data for cursor 1: stock_ref=124000
STOCK_DETAIL_REF STOCK_REF LABEL_REF DATA
--------------------------------------- --------------------------------------- --------------------------------------- ------------------------------------------------------------
217924 124000 1000 1.0
217925 124000 2602 (A1*2)
217926 124000 2603 (A1*3)
217927 124000 2604 (A1*4)
Prim_Ing_Ref = 1234
CALC_REF Prim_Ing_Ref LABEL_REF CALCULATION_STATEMENT
--------------------------------------- --------------------------------------- --------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
846 1234 666 1.0
847 1234 667 1.0
848 1234 690 1.0
849 1234 1000 1.0
850 1234 2602 (A1*2)
851 1234 2603 (A1*3)
852 1234 2604 (A1*4)
Result from cursor1:
STOCK_DETAIL_REF CALC_REF CALCULATION_STATEMENT
--------------------------------------- --------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
217924 849 1.0
217925 850 (A1*2)
217926 851 (A1*3)
217927 852 (A1*4)
Cursor 2 looks for values for the parameters in the equations and substitutes them for the requested values and evaluates the equations. Cursor 2 seems to returned nothing for ALL entries in Cursor 1 if ANY NOTFOUND occurs. so for the 1.0 value, there are no parameters., but this causes the rest of the cursor loops to fail
Cursor 2 data:
STOCK_REF CALC_REF PARM_NAME DATA
--------------------------------------- --------------------------------------- --------- ------------------------------------------------------------
124000 850 A1 25
124000 851 A1 25
124000 852 A1 25
these are for the 3 equations shown above in CALCULATION_STATEMENT. however, as said, the routine fails, just for this specific instance where static numeric values are intermixed with equations (i.e. the cursor2 returns NOTFOUND for at least one item in cursor1). manually running the cursor's select statement retrieves the data just fine. I can only assume that the implementation of the cursor is incorrect somehow.
you need to pass the value of c1 into c2 as a parameter. Have a look at the example here:
PL/SQL referencing another cursor in a cursor
BTW, I assume this line:
FETCH stock_calc INTO ...
should be:
FETCH cur1 INTO ...
Update Following Comments
(Please tick this if you felt I have helped with the answer)
To re-iterate what was said in the comments:
you need to CLOSE calc_parms within IF calc_parms%NOTFOUND THEN... at the moment you only close it in the ELSE statement.
Also, if performance is an issue, you should look at looping through calc_parms before you loop through stock_calc. As calc_parms is not being changed by anything happening in stock_calc, PARM and PARMVAL have exactly the same values in every loop through stock_calc.

SAS - Proc SQL or Merge - Trying to optimise an INNER join that includes a string search (index)

I've a rudimentary SAS skillset, most of which involves "proc sql", so feel free to challenge the fundamental approach of using this.
I'm attempting to match one set of personal details against another set, the first having some ~400k rows and the other 22 million. The complexity is that the 400k rows feature previous names and postcodes as well as current ones (all on the same row), so my approach (code below) was to concatenate all of the surnames together and all of the postcodes together and search for the string from the second table (single name and postcode) within the concatenated strings using the index(source, excerpt) function.
proc sql;
CREATE TABLE R4 AS
SELECT DISTINCT
BS.CUST_ID,
ED.MATCH_ID
FROM T_RECS_WITH_CONCATS BS
INNER JOIN T_RECS_TO_MATCH ED
ON LENGTH(ED.SinglePostcode) > 4
AND index(BS.AllSurnames,ED.SingleSurname) > 0
AND index(BS.AllPostcodes,ED.SinglePostcode) > 0
;
QUIT;
In the above, AllSurnames can contain up to 9 surnames (delimited by |), and AllPostcodes up to 9 concatenated postcodes (again, delimited by |).
The downside of this is of course that it takes forever to run. Is there are more efficient way of doing this, either within a proc sql step or a real data step?
Here is a way using HASH component object
Presume the data sets are named SHORT_MANY and TALL_ONE. Use the data in SHORT_MANY to populate a multidata hash table that can operate as a lookup for values being checked in TALL_ONE.
Using just surname and postal code as the lookup key could result in many false matches.
Example (with numeric surname & postcode)
data SHORT_MANY;
do cust_id = 1 to 400;
array Surnames surname1-surname9;
array Postcodes postcode1-postcode9;
call missing (of surnames(*));
do index = 1 to dim(surnames);
surnames(index) = ceil (100000 * ranuni(123));
postcodes(index) = ceil ( 99999 * ranuni(123));
if ranuni(123) < 0.15 then leave;
end;
output;
end;
run;
data TALL_ONE(keep=match_id surname postcode forcemark);
do match_id = 1 to 22000;
surname = ceil(100000 * ranuni(1234));
postcode = ceil( 99999 * ranuni(1234));
forcemark = .;
if ranuni(123) < 0.15 then do; * randomly ensure some match will occur;
point = ceil(400*ranuni(123));
set SHORT_MANY point=point;
array surnames surname1-surname9;
array postcodes postcode1-postcode9;
do until (surname ne .);
index = ceil(9 * ranuni(123));
surname = surnames(index);
postcode = postcodes(index);
end;
forcemark = point;
end;
output;
end;
stop;
run;
data WHEN_TALL_MEETS_SHORT(keep=cust_id match_id index);
if 0 then set TALL_ONE SHORT_MANY ; * prep pdv (for hash host variables);
if _n_ = 1 then do;
length index 8;
declare hash lookup(multidata: 'yes');
lookup.defineKey('surname', 'postcode');
lookup.defineData('cust_id', 'index');
lookup.defineDone();
do while (not lookup_filled);
SET SHORT_MANY end=lookup_filled;
array Surnames surname1-surname9;
array Postcodes postcode1-postcode9;
do index = 1 to dim(surnames) while (surnames(index) ne .);
surname = surnames(index);
postcode = postcodes(index);
lookup.add();
end;
end;
end;
call missing (surname, postcode, cust_id, index);
set TALL_ONE;
rc = lookup.find(); * grab just first match -- has_next/find_next to retrieve other lookup matches;
run;

How to Insert data using cursor into new table having single column containing XML type data in Oracle?

I'm able to insert values into table 2 from table 1 and execute the PL/SQL procedure successfully but somehow the output is clunky. I don't know why?
Below is the code :
create table airports_2_xml
(
airport xmltype
);
declare
cursor insert_xml_cr is select * from airports_1_orcl;
begin
for i in insert_xml_cr
loop
insert into airports_2_xml values
(
xmlelement("OneAirport",
xmlelement("Rank", i.Rank) ||
xmlelement("airport",i.airport) ||
xmlelement("Location",i.Location) ||
xmlelement("Country", i.Country) ||
xmlelement("Code_iata",i.code_iata) ||
xmlelement("Code_icao", i.code_icao) ||
xmlelement("Total_Passenger",i.Total_Passenger) ||
xmlelement("Rank_change", i.Rank_change) ||
xmlelement("Percent_Change", i.Percent_change)
));
end loop;
end;
/
select * from airports_2_xml;
Output:
Why it is showing &lt ,&gt in the output ? And why am I unable to see the output fully?
Expected output:
<OneAirport>
<Rank>3</Rank>
<Airport>Dubai International</Airport>
<Location>Garhoud</Location>
<Country>United Arab Emirates</Country>
<Code_IATA>DXB</Code_IATA>
<Code_ICAO>OMDB</Code_ICAO>
<Total_passenger>88242099</Total_passenger>
<Rank_change>0</Rank_change>
<Percent_Change>5.5</Percent_Change>
</OneAirport>
The main issue is how you are constructnig the XML. You have an outer XMLElement for OneAirport, and the content of that element is a single string.
You are generating individual XMLElements from the cursor fields, but then you are concenating those together, which gives you a single string which still has the angle brackets you're expecting. So you're trying to do something like, simplified a bit:
select
xmlelement("OneAirport", '<Rank>1</Rank><airport>Hartsfield-Jackson</airport>')
from dual;
XMLELEMENT("ONEAIRPORT",'<RANK>1</RANK><AIRPORT>HARTSFIELD-JACKSON</AIRPORT>')
--------------------------------------------------------------------------------
<OneAirport><Rank>1</Rank><airport>Hartsfield-Jackson</airp
and by default XMLElement() escapes entities in the passed-in values, so the angle-brackets are being converted to 'safe' equivalents like <. If it didn't do that, or you told it not to with noentityescaping:
select xmlelement(noentityescaping "OneAirport", '<Rank>1</Rank><airport>Hartsfield-Jackson</airport>')
from dual;
XMLELEMENT(NOENTITYESCAPING"ONEAIRPORT",'<RANK>1</RANK><AIRPORT>HARTSFIELD-JACKS
--------------------------------------------------------------------------------
<OneAirport><Rank>1</Rank><airport>Hartsfield-Jackson</airport></OneAirport>
then that would appear to be better, but you still actually have a single element with a single string (with characters that are likely to cause problems down the line), rather than the XML structure you almost certainly intended.
A simple way to get an zctual structure is with XMLForest():
xmlelement("OneAirport",
xmlforest(i.Rank, i.airport, i.Location, i.Country, i.code_iata,
i.code_icao, i.Total_Passenger, i.Rank_change, i.Percent_change)
)
You don't need the cursor loop, or any PL/SQL; you can just do:
insert into airports_2_xml (airport)
select xmlelement("OneAirport",
xmlforest(i.Rank, i.airport, i.Location, i.Country, i.code_iata,
i.code_icao, i.Total_Passenger, i.Rank_change, i.Percent_change)
)
from airports_1_orcl i;
The secondary issue is the display. You'll see more data if you issue some formatting commands, such as:
set lines 120
set long 32767
set longchunk 32767
Those will tell your client to retrieve and show more of the long (XMLType here) data, rather the default 80 characters it's giving you now.
Once you are generating a nested XML structure you can use XMLSerialize() to display that more readable when you query your second table.
Try this below block :
declare
cursor insert_xml_cr is select * from airports_1_orcl;
v_airport_xml SYS.XMLTYPE;
begin
for i in insert_xml_cr
loop
SELECT XMLELEMENT ( "OneAirport",
XMLFOREST(i.Rank as "Rank"
,i.airport as "Airport"
,i.Location as "Location"
,i.Country as "Country"
,i.code_iata as "Code_iata"
,i.code_icao as "code_icao"
,i.Total_Passenger as "Total_Passenger"
, i.Rank_change as "Rank_change"
,i.Percent_change as "Percent_Change"
))
into v_airport_xml
FROM DUAL;
insert into airports_2_xml values (v_airport_xml);
end loop;
end;

Returning multiple values using function causing multiple query runs

We have kiosks for customers to check their purchase volume for two different categories of items. They will input their mobile number, which will send an OTP to their mobile numbers and they will input it back to authenticate, the system has to check the data and display for them. As a developer, the kiosk supplier has provided us with a limited functionality development kit by which we can execute select statement on the database and display the returned values on the kiosk.
I have created an object type as follows:
CREATE OR REPLACE TYPE rebate_values
AS
OBJECT (ASales_total number,
ACurrent_Rebate_Percent number,
ANeeded_Sales number,
ANext_Rebate_Percent number,
BSales_total number,
BCurrent_Rebate_Percent number,
BNeeded_Sales number,
BNext_Rebate_Percent number);
A function to which I will pass customers' mobile to get their sales and rebate information:
CREATE OR REPLACE FUNCTION AA_rebate_function (P_phone IN NUMBER)
RETURN rebate_values
IS
A_P_Sales_total NUMBER;
A_P_Current_Rebate_Percent NUMBER;
A_P_Needed_Sales NUMBER;
A_P_Next_Rebate_Percent NUMBER;
B_P_Sales_total NUMBER;
B_P_Current_Rebate_Percent NUMBER;
B_P_Needed_Sales NUMBER;
B_P_Next_Rebate_Percent NUMBER;
P_CODE VARCHAR (10);
BEGIN
SELECT CC_CODE
INTO P_CODE
FROM CUSTOMERS
WHERE C_MOBILE = P_phone;
FOR OUTDATA
IN (
--My Query to retrieve the data
Select ................
)
LOOP
IF OUTDATA.CLASS = 'X'
THEN
A_P_Sales_total := OUTDATA.SALES_TOTAL;
A_P_Current_Rebate_Percent := OUTDATA.CURRENT_REBATE_PERCENT;
A_P_Needed_Sales := OUTDATA.NEEDED_SALES_FOR_HIGHER_REBATE;
A_P_Next_Rebate_Percent := OUTDATA.NEXT_HIGHER_REBATE_PERCENT;
END IF;
IF OUTDATA.CLASS = 'Y'
THEN
B_P_Sales_total := OUTDATA.SALES_TOTAL;
B_P_Current_Rebate_Percent := OUTDATA.CURRENT_REBATE_PERCENT;
B_P_Needed_Sales := OUTDATA.NEEDED_SALES_FOR_HIGHER_REBATE;
B_P_Next_Rebate_Percent := OUTDATA.NEXT_HIGHER_REBATE_PERCENT;
END IF;
END LOOP;
RETURN rebate_values (A_P_Sales_total,
A_P_Current_Rebate_Percent,
A_P_Needed_Sales,
A_P_Next_Rebate_Percent,
B_P_Sales_total,
B_P_Current_Rebate_Percent,
B_P_Needed_Sales,
B_P_Next_Rebate_Percent);
END;
/
The query takes 27 seconds to retrieve the values for each customer. Each customer will have 2 rows, so that's why I have used LOOP to collect the values.
When I execute the function:
SELECT AA_rebate_function (XXXXXXXXXX) FROM DUAL;
I get data as follows in a single column within 27 seconds:
(XXXX, X, XXXX, X, XXXX, X, XXXX, X)
But when I execute the function to get the values in different columns, it takes 27 x 8 seconds = 216 seconds, i.e., approximately 3.6 minutes which is a big issue as the customer cannot wait for 3.6 minutes on the kiosk to view the data.
SELECT x.c.ASales_total,
x.c.ACurrent_Rebate_Percent,
x.c.ANeeded_Sales,
x.c.ANext_Rebate_Percent,
x.c.BSales_total,
x.c.BCurrent_Rebate_Percent,
x.c.BNeeded_Sales,
x.c.BNext_Rebate_Percent
FROM (SELECT AA_rebate_function (XXXXXXXXXX) c FROM DUAL) x;
I have tried using stored procedure with OUT values but it doesn't fit in my environment as I cannot program to execute stored procedures from the kiosk development toolkit because it only supports select statements, checked with the supplier and they don't have any plan to add that support in near future.
I tried converting the single field into multiple columns using REGEXP_SUBSTR but I get a type conversion error as it is an array.
The query is very complex and has to calculate data for the last 10 years and has millions of rows, 27 seconds is actually the optimum time to get the desired results.
Interesting! I didn't realize that when you query a function that returns an object, it runs the function once for each column you reference the object in. That's awkward.
The easiest solution I could find for this is to switch your function to be PIPELINED. You'll need to create a nested table type to do this.
create type rebate_values_t is table of rebate_values;
/
CREATE OR REPLACE FUNCTION AA_rebate_function (P_phone IN NUMBER)
RETURN rebate_values_t PIPELINED
IS
... your code here ...
PIPE ROW (rebate_values (A_P_Sales_total,
A_P_Current_Rebate_Percent,
A_P_Needed_Sales,
A_P_Next_Rebate_Percent,
B_P_Sales_total,
B_P_Current_Rebate_Percent,
B_P_Needed_Sales,
B_P_Next_Rebate_Percent));
RETURN;
END;
/
SELECT x.ASales_total,
x.ACurrent_Rebate_Percent,
x.ANeeded_Sales,
x.ANext_Rebate_Percent,
x.BSales_total,
x.BCurrent_Rebate_Percent,
x.BNeeded_Sales,
x.BNext_Rebate_Percent
FROM TABLE(AA_rebate_function (XXXXXXXXXX)) x;
For some reason, this should only execute the function once, and take 27 seconds.

Oracle UPDATE has no effect

I am working on Sql Developper an I created the following procedure in a package:
PROCEDURE VALIDER(a_session IN NUMBER) AS
i NUMBER;
TYPE type_tab IS TABLE OF PANIER%ROWTYPE;
tabSeances type_tab;
BEGIN
SELECT * BULK COLLECT INTO tabSeances
FROM PANIER
WHERE a_session = sessionweb;
i:=0;
FOR i IN 1 .. tabSeances.count LOOP
-- UPADTE DU NOMBRE DE PLACES LIBRES
BEGIN
UPDATE PROJECTION
SET remaining_seats = (remaining_seats - tabseances(i).nbrplaces)
WHERE num_copy = tabseances(i).num_copy
AND day = tabseances(i).dateseance
AND time_slot = tabseances(i).time_slot
AND movie = tabseances(i).movie;
COMMIT;
--UPDATE ON PANIER
UPDATE PANIER
SET valide = 1
WHERE sessionweb = a_session
AND num_copy = tabseances(i).num_copy
AND dateseance = tabseances(i).dateseance
AND time_slot = tabseances(i).time_slot
AND movie = tabseances(i).movie;
COMMIT;
EXCEPTION
WHEN NO_DATA_FOUND THEN raise_application_error(-20035, 'Pas de données');
WHEN OTHERS THEN raise_application_error(-20006,'Autres Erreurs');
END;
END LOOP;
END VALIDER;
The procedure executes normaly and I don't get an error.
I have a kind of product cart: "PANIER". I loop all the entries in thsi cart for one person (session) to validate them in the database and decrement the total number of seats.
But the field "remaining-seats" (from PROJECTIONS) in the first update don't work. The field isn't updated. I have already tried with other values but nothing.
I am sure that the procedure is executetd because the second update still works. It marks the cart entry as "CONFIRMED".
I don't have any trigger on this field.
My tables contains valid data (<>NULL).
I execute this procedure like this (in a BEGIN END; block):
CMDPLACES.VALIDER(1);
Thank for your reply.
Is it day or dateseance in your first update?
UPDATE PROJECTION
SET remaining_seats = (remaining_seats - tabseances(i).nbrplaces)
WHERE num_copy = tabseances(i).num_copy
AND dateseance = tabseances(i).dateseance
AND time_slot = tabseances(i).time_slot
AND movie = tabseances(i).movie;
Also as #ThorstenKettner was mentioning, the timestamp data in the date , may fail while comparing, so we have TRUNCATE the timestamp data using TRUNC() [if needed]!
If the date column is indexed, beware the index will not be used by the database .
To handle NO Data in UPDATE, you can check (SQL%ROWCOUNT > 0) to identify the number of rows updated!
Your first update compares days. What data type are these? In case you deal with DATETIME, make sure to compare without the time part if any. Use TRUNC to achieve this.
AND TRUNC(day) = TRUNC(tabseances(i).dateseance)