I am very new to PL/SQL and would like to be able to take advantage of the procedural options of oracle SQL.
I have a query that I would like to transform into a PL/SQL query that takes the SKU you want and inputs whenever you have the word SKU in your query.
Example:
SELECT A.*, CT.CUSTOMER_ID, CT.ORDERS
FROM CUSTOMER_TABLE CT
RIGHT JOIN
(
SELECT OT.COLUMN_ID, OT.SKU, OT.ORDERS
FROM ORDERS_TABLE OT
WHERE OT.SKU = 123
)A
ON CT.ORDERS = OT.ORDERS
AND CT.SKU IS > 0
This is strictly an example so I know that what I am asking is pointless, however, if I had a lengthier query that contains more than 2 or 3 "SKU" inputs then I would like to have a variable/parameter for it.
My ideal code would be:
DECLARE
SKUS INTEGER := 123;
BEGIN
SELECT A.*, CT.CUSTOMER_ID
FROM CUSTOMER_TABLE CT
RIGHT JOIN
(
SELECT OT.COLUMN_ID, OT.SKU, OT.ORDERS
FROM ORDERS_TABLE OT
WHERE #SKUS
)A
ON CT.ORDERS = OT.ORDERS
AND #SKUS IS > 0
END;
/
I am not sure if that is proper syntax but I hope the idea makes sense of what I am asking.
Thank you!
PL/SQL handles such situations very easily. Keep your SQL statement exactly the same and replace literals with your variable, or even a cursor parameter. I moved your SELECT into an explicit cursor to show how you can easily declare a record based on the cursor as well:
DECLARE
c_sku CONSTANT INTEGER := 123;
CURSOR my_cur (sku_in IN INTEGER)
IS
SELECT a.*, ct.customer_id, ct.orders
FROM customer_table ct
RIGHT JOIN (SELECT ot.column_id, ot.sku, ot.orders
FROM orders_table ot
WHERE ot.sku = sku_in) a
ON ct.orders = ot.orders AND sku_in > 0;
l_info my_cur%ROWTYPE;
BEGIN
OPEN my_cur (c_sku);
FETCH my_cur INTO l_info;
CLOSE my_cur;
END;
Related
In my PL/SQL script, how do I declare JUSTIFIC_REC when it represents a join?
SELECT *
INTO JUSTIFIC_REC
FROM TABLE1 A
INNER JOIN TABLE2 B
ON A.ID_JUSTIFIC = B.ID_JUSTIFIC ;
All I want is to insert into a TABLE3 a concatenated row:
INSERT INTO MOF_OUTACCDTL_REQ VALUES(
JUSTIFIC_rec.ENTRY_COMMENTS || ' ' || JUSTIFIC_rec.DESCRIPTION );
How should the declaration of JUSTIFIC_REC be like in the beginning of my script?
If it wasn't for the INNER JOIN , I would write something like:
JUSTIFIC_rec TABLE1%ROWTYPE;
If I understood correctly you can try with cursor rowtype like this (not sure if that is what you meant by declaring your variable type for the select with joins):
set serveroutput on;
declare
cursor cur is
SELECT ENTRY_COMMENTS, DESCRIPTION
FROM TABLE1 A
INNER JOIN TABLE2 B
ON A.ID_JUSTIFIC = B.ID_JUSTIFIC ;
justific_rec cur%ROWTYPE;
begin
open cur;
loop
fetch cur into justific_rec;
exit when cur%notfound;
dbms_output.put_line(justific_rec.entry_comments || ' ' || justific_rec.description);
end loop;
close cur;
end;
Answer to your question is itself in your question. You have to use the %row type
tow type attribute can be any of the below type:
rowtype_attribute :=
{cursor_name | cursor_variable_name | table_name} % ROWTYPE
cursor_name:-
An explicit cursor previously declared within the current scope.
cursor_variable_name:-
A PL/SQL strongly typed cursor variable, previously declared within the current scope.
table_name:-
A database table or view that must be accessible when the declaration is elaborated.
So code will looks like
DECLARE
CURSOR c1 IS
SELECT * FROM TABLE1 A
INNER JOIN TABLE2 B
ON A.ID_JUSTIFIC = B.ID_JUSTIFIC ;
justific_rec c1%ROWTYPE;
BEGIN
open c1;
loop
fetch c1 into justific_rec;
exit when c1%notfound;
INSERT INTO MOF_OUTACCDTL_REQ VALUES(
JUSTIFIC_rec.ENTRY_COMMENTS || ' ' || JUSTIFIC_rec.DESCRIPTION );
end loop;
close c1;
END;
/
You need to use BULK COLLECT INTO
SELECT *
BULK COLLECT INTO JUSTIFIC_REC
FROM TABLE1 A
INNER JOIN TABLE2 B
ON A.ID_JUSTIFIC = B.ID_JUSTIFIC ;
The JUSTIFIC_REC is needed to be TABLE type with appropriate columns
If all you're wanting to do is insert into a table based on a select statement, then there's no need for PL/SQL (by which I mean there's no need to open a cursor, fetch a row, process the row and then move on to the next row) - that's row-by-row aka slow-by-slow processing.
Instead, you can do this all in a single insert statement, e.g.:
INSERT INTO mof_outaccdtl_rec (<column being inserted into>) -- please always list the columns being inserted into; this avoids issues with your code when someone adds a column to the table.
SELECT entry_comments || ' ' || description -- please alias your columns! How do we know which table each column came from?
FROM table1 a
inner join table2 b on a.id_justific = b.id_justific;
If you wanted to embed this insert statement in PL/SQL, all you need to do is add a begin/end around it, like so:
begin
INSERT INTO mof_outaccdtl_rec (<column being inserted into>) -- please always list the columns being inserted into; this avoids issues with your code when someone adds a column to the table.
SELECT entry_comments || ' ' || description -- please alias your columns! How do we know which table each column came from?
FROM table1 a
inner join table2 b on a.id_justific = b.id_justific;
end;
/
This is the preferred solution when working with databases - think in sets (where possible) not procedurally (aka row-by-row processing). It is easier to maintain, the code is simpler to read and write, and it'll be more performant since you're not having to switch between PL/SQL and SQL several times with each row.
Context switching is bad for performance - think in terms of a bath full of water - is it quicker to empty the bath with a spoon (row-by-row processing), with a jug (batched rows - by - batched rows) or by pulling the plug (set-based)?
Here is my code. Please forgive me for not putting variables in declaration section as the editor was giving me tough time in formatting it before I could submit my issue.
I want the result variable (v_Var) to have value printed as
v_ID = :NEW.ID;
v_NAME = :NEW.NAME;
v_ENTITY_ID = :NEW.ENTITY_ID;
BUT, it is getting printed as
v_ID = :NEW.ID;
v_ID = :NEW.ID;
v_NAME = :NEW.NAME;
v_ENTITY_ID = :NEW.ENTITY_ID;
Since the table TEMP_TRG_CONSTRNT has 2 rows, it is working for v_ID also for two times.
I know the issue is with external FOR loop but I am not sure how to handle it.
DECLARE
CURSOR c1 IS
SELECT NAME, OCCUR_COUNT FROM IFMS_SYSTEMCONFIGURATION.TEMP_TRG_CONSTRNT;
BEGIN
v_TableName := 'MyTable';
EXECUTE IMMEDIATE 'TRUNCATE TABLE IFMS_SYSTEMCONFIGURATION.TEMP_TRG_CONSTRNT';
INSERT INTO IFMS_SYSTEMCONFIGURATION.TEMP_TRG_CONSTRNT (NAME, OCCUR_COUNT)
SELECT A.FKN, COUNT(A.FKN) AS OCCUR_COUNT FROM
(
SELECT A.CONSTRAINT_NAME AS FKN FROM ALL_CONSTRAINTS A
INNER JOIN ALL_CONS_COLUMNS B
ON A.CONSTRAINT_NAME = B.CONSTRAINT_NAME
WHERE A.CONSTRAINT_TYPE IN ('P', 'U') AND A.TABLE_NAME = 'MyTable'
)A
GROUP BY A.FKN;
--FOR CONSTR_NAME IN (SELECT NAME FROM IFMS_SYSTEMCONFIGURATION.TEMP_TRG_CONSTRNT)
FOR CONSTR_NAME IN c1
LOOP
--SELECT NAME, OCCUR_COUNT INTO v_Constr_Name, v_Index_Count FROM TEMP_TRG_CONSTRNT WHERE NAME = CONSTR_NAME.NAME;
FOR COL_NAME IN (SELECT COLUMN_NAME FROM ALL_CONS_COLUMNS WHERE CONSTRAINT_NAME = CONSTR_NAME.NAME)
LOOP
v_Var := v_Var || 'v_' || COL_NAME.COLUMN_NAME || ' = :NEW.' || COL_NAME.COLUMN_NAME || ';' || CHR(13);
END LOOP;
DBMS_OUTPUT.PUT_LINE(v_Var);
END LOOP;
END;
There could be a couple of things causing your results:
There are tables in different schemas with the same name (you're missing a join on owner between the two tables)
There could be more than one constraint on the same table.
In addition to those, you've also managed to recreate a Nested Loop join, by doing the nested looping. In general, this is a bad idea - what if a Hash Join was more performant? You will have effectively hobbled Oracle by using your nested cursor for loops. At the very least, you could join your two cursors in a single sql statement before looping through it.
However, it looks like you're trying to generate a list of variables without having to type them. You can do this in a single SQL statement - no need for PL/SQL, like so:
SELECT DISTINCT con.constraint_name,
con.owner,
con.table_name,
col.column_name,
'v_'||col.column_name||' := :NEW.'||col.column_name||';'
FROM all_constraints con
inner JOIN all_cons_columns col ON con.constraint_name = col.constraint_name
AND con.owner = col.owner
AND con.constraint_type IN ('P', 'U')
--AND con.owner = 'SOME_OWNER' -- uncomment out and put in the correct owner name, esp if your table exists in more than one owner.
AND con.table_name = 'YOUR_TABLE'
ORDER BY con.owner, con.table_name;
Note that I've included extra columns, so that you can work out why you're getting the results you're getting, just in case that doesn't match what you're expecting to see. I included the DISTINCT keyword to take care of the case where you have multiple constraints returned for a single owner.table.
If you want to generate a list of variables for multiple tables at once, you might want to use the aggregate listagg function on the above query (meaning you could remove the DISTINCT) with a delimiter of CHR(10).
I've got the following existing procedure:
create or replace
PROCEDURE pmm$AppUser_GetApprover_KFGZ7Q(
ReleaseRequestID INT )
AS
RefCursor SYS_REFCURSOR;
BEGIN
OPEN RefCursor FOR
SELECT
U.*
FROM pmm$PmmReleaseRequest R
INNER JOIN dbo$ManagedEntity ME
ON ME.ManagedEntityID = R.ManagedSystemID
INNER JOIN dbo$SmartRuleAssetCache SRC
ON SRC.AssetID = ME.AssetID
INNER JOIN dbo$UserGroup_SmartRule_Role GSR
ON GSR.SmartRuleId = SRC.SmartRuleId
AND GSR.RoleId IN (2,3)
INNER JOIN dbo$AppUser_UserGroup UG
ON UG.GroupID = GSR.UserGroupId
AND UG.UserID <> R.UserID
INNER JOIN dbo$AppUser U ON UG.UserID = U.UserID
WHERE R.ReleaseRequestID = ReleaseRequestID;
DBMS_SQL.RETURN_RESULT(RefCursor);
END;
I would like to call this from a Trigger and query the Cursor returned using some Where clauses to further refine the results.
Most ideally I would like to put the records in a temporary table.
I can't seem to find any documentation on the best way to do this.
Thanks in advance.
Not directly, no.
A SYS_REFCURSOR is a pointer to a result-- the only thing you can do
with that is to fetch the data. You can't modify the result set. However the best
way workaround for this is to put the data into some king of Temp
table( most preferred is GTT Global temporary table) and then utilize
it to do all the validations in Trigger.
Hope below snippet is useful for you.
CREATE OR REPLACE PROCEDURE test_cur(
p_out OUT sys_refcursor )
AS
BEGIN
OPEN p_out FOR SELECT LEVEL FROM DUAL CONNECT BY LEVEL < 15;
-- Here dummy tab can be any temp table or GTT
INSERT INTO DUMMY_TAB
SELECT LEVEL FROM DUAL CONNECT BY LEVEL < 15;
END;
CREATE OR REPLACE TRIGGER <Trigger_name>
AFTER INSERT OR UPDATE ON <TYABLE_NAME>
FOR EACH ROW
DECLARE
--<variable declarations>
BEGIN
dbms_output.put_line('Your code for data Manipulations');
--SELECT FROM GTT/ Temp table and all the manipulations required
END;
I have a simple query in a cursor
Cursor some_cursor IS
select
sum(some_field)
from some_table table_1
where
table_1.TYPE =1
AND TO_CHAR(table_1.date,'YYYYMMDD') = '20090905'
AND table_1.f2 = 962
AND table_1.f3 = 41813;
Then i do
fetch some_cursor into some_var;--some_var is of type Number, cursor is open
When I run this query, there's the chance that some_var will be NULL. In that case, id like it to take the 0 value;
Something like
--C like pseudocode of what I want
(some_cursor!=Null?(fetch some_cursor into some_var):(some_var:=0))
Is there a way to do this? I was thinking of rewriting the above query to
Cursor some_cursor IS
select
sum(some_field),count(*)
from some_table table_1
where
table_1.TYPE =1
AND TO_CHAR(table_1.date,'YYYYMMDD') = '20090905'
AND table_1.f2 = 962
AND table_1.f3 = 41813;
and then writing
fetch some_cursor into some_var,some_counter;
if (some_counter = 0) then
begin
some_var :=0;
end
but this implies rewriting 10 cursors (yes, not so many). Maybe plsql has a cleaner way.
Thanks in advance
Try:
SELECT NVL(SUM(some_field),0)
from some_table table_1
where table_1.TYPE =1
AND TO_CHAR(table_1.date,'YYYYMMDD') = '20090905'
AND table_1.f2 = 962
AND table_1.f3 = 41813;
You might correct a couple of other issues while you're there:
Use implicit cursors instead of explicit
Don't apply a function to a table column if you can avoid it.
thus:
Declare
some_var Number;
Begin
select Coalesce(sum(some_field),0)
into some_var
from some_table table_1
where table_1.TYPE = 1
AND table_1.date >= date '2009-09-05'
AND table_1.date < date '2009-09-06'
AND table_1.f2 = 962
AND table_1.f3 = 41813;
End;
I have read a book whose title is "Oracle PL SQL Programming" (2nd ed.) by Steven Feuerstein & Bill Pribyl. On page 99, there is a point suggested that
Do not "SELECT COUNT(*)" from a table unless you really need to know the total number of "hits." If you only need to know whether there is more than one match, simply fetch twice with an explicit cursor.
Could you anyone explain this point more to me by providing example? Thank you.
Update:
As Steven Feuerstein & Bill Pribyl recommends us not to use SELECT COUNT() to check whether records in a table exist or not, could anyone help me edit the code below in order to avoid using SELECT COUNT(*) by using explicit cursor instead? This code is written in the Oracle stored procedure.
I have a table emp(emp_id, emp_name, ...), so to check the provided employee ID corret or not:
CREATE OR REPLACE PROCEDURE do_sth ( emp_id_in IN emp.emp_id%TYPE )
IS
v_rows INTEGER;
BEGIN
...
SELECT COUNT(*) INTO v_rows
FROM emp
WHERE emp_id = emp_id_in;
IF v_rows > 0 THEN
/* do sth */
END;
/* more statements */
...
END do_sth;
There are a number of reasons why developers might perform select COUNT(*) from a table in a PL/SQL program:
1) They genuinely need to know how many rows there are in the table.
In this case there is no choice: select COUNT(*) and wait for the result. This will be pretty fast on many tables, but could take a while on a big table.
2) They just need to know whether a row exists or not.
This doesn't warrant counting all the rows in the table. A number of techniques are possible:
a) Explicit cursor method:
DECLARE
CURSOR c IS SELECT '1' dummy FROM mytable WHERE ...;
v VARCHAR2(1);
BEGIN
OPEN c;
FETCH c INTO v;
IF c%FOUND THEN
-- A row exists
...
ELSE
-- No row exists
...
END IF;
END;
b) SELECT INTO method
DECLARE
v VARCHAR2(1);
BEGIN
SELECT '1' INTO v FROM mytable
WHERE ...
AND ROWNUM=1; -- Stop fetching if 1 found
-- At least one row exists
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- No row exists
END;
c) SELECT COUNT(*) with ROWNUM method
DECLARE
cnt INTEGER;
BEGIN
SELECT COUNT(*) INTO cnt FROM mytable
WHERE ...
AND ROWNUM=1; -- Stop counting if 1 found
IF cnt = 0 THEN
-- No row found
ELSE
-- Row found
END IF;
END;
3) They need to know whether more than 1 row exists.
Variations on the techniques for (2) work:
a) Explicit cursor method:
DECLARE
CURSOR c IS SELECT '1' dummy FROM mytable WHERE ...;
v VARCHAR2(1);
BEGIN
OPEN c;
FETCH c INTO v;
FETCH c INTO v;
IF c%FOUND THEN
-- 2 or more rows exists
...
ELSE
-- 1 or 0 rows exist
...
END IF;
END;
b) SELECT INTO method
DECLARE
v VARCHAR2(1);
BEGIN
SELECT '1' INTO v FROM mytable
WHERE ... ;
-- Exactly 1 row exists
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- No row exists
WHEN TOO_MANY_ROWS THEN
-- More than 1 row exists
END;
c) SELECT COUNT(*) with ROWNUM method
DECLARE
cnt INTEGER;
BEGIN
SELECT COUNT(*) INTO cnt FROM mytable
WHERE ...
AND ROWNUM <= 2; -- Stop counting if 2 found
IF cnt = 0 THEN
-- No row found
IF cnt = 1 THEN
-- 1 row found
ELSE
-- More than 1 row found
END IF;
END;
Which method you use is largely a matter of preference (and some religious zealotry!) Steven Feuerstein has always favoured explicit cursors over implicit (SELECT INTO and cursor FOR loops); Tom Kyte favours implicit cursors (and I agree with him).
The important point is that to select COUNT(*) without restricting the ROWCOUNT is expensive and should therefore only be done when a count is trully needed.
As for your supplementary question about how to re-write this with an explicit cursor:
CREATE OR REPLACE PROCEDURE do_sth ( emp_id_in IN emp.emp_id%TYPE )
IS
v_rows INTEGER;
BEGIN
...
SELECT COUNT(*) INTO v_rows
FROM emp
WHERE emp_id = emp_id_in;
IF v_rows > 0 THEN
/* do sth */
END;
/* more statements */
...
END do_sth;
That would be:
CREATE OR REPLACE PROCEDURE do_sth ( emp_id_in IN emp.emp_id%TYPE )
IS
CURSOR c IS SELECT 1
FROM emp
WHERE emp_id = emp_id_in;
v_dummy INTEGER;
BEGIN
...
OPEN c;
FETCH c INTO v_dummy;
IF c%FOUND > 0 THEN
/* do sth */
END;
CLOSE c;
/* more statements */
...
END do_sth;
But really, in your example it is no better or worse, since you are selecting the primary key and Oracle is clever enough to know that it only needs to fetch once.
If two is all you are interested in, try
SELECT 'THERE ARE AT LEAST TWO ROWS IN THE TABLE'
FROM DUAL
WHERE 2 =
(
SELECT COUNT(*)
FROM TABLE
WHERE ROWNUM < 3
)
It will take less code than doing the manual cursor method,
and it is likely to be faster.
The rownum trick means to stop fetching rows once it has two of them.
If you don't put some sort of limit on the count(*), it could take a long while to finish, depending on the number of rows you have. In that case, using a cursor loop, to read 2 rows from the table manually, would be faster.
This comes from programmers writing code similar to the following (this is psuedo code!).
You want to check to see if the customer has more than one order:
if ((select count(*) from orders where customerid = :customerid) > 1)
{
....
}
That is a terribly inefficient way to do things. As Mark Brady would say, if you want to know if a jar contains pennies, would you count all the pennies in the jar, or just make sure there is 1 (or 2 in your example)?
This could be better written as:
if ((select 1 from (select 1 from orders where customerid = :customerid) where rownum = 2) == 1)
{
....
}
This prevents the "counting all of the coins" dilemma since Oracle will fetch 2 rows, then finish. The previous example would cause oracle to scan (an index or table) for ALL rows, then finish.
He means open a cursor and fetch not only the first record but the second, and then you will know there is more than one.
Since I never seem to need to know that SELECT COUNT(*) is >= 2, I have no idea why this is a useful idiom in any SQL variant. Either no records or at least one, sure, but not two or more. And anyway, there's always EXISTS.
That, and the fact that Oracle's optimizer seems to be pretty poor... - I would question the relevance of the technique.
To address TheSoftwareJedi's comments:
WITH CustomersWith2OrMoreOrders AS (
SELECT CustomerID
FROM Orders
GROUP BY CustomerID
HAVING COUNT(*) >= 2
)
SELECT Customer.*
FROM Customer
INNER JOIN CustomersWith2OrMoreOrders
ON Customer.CustomerID = CustomersWith2OrMoreOrders.CustomerID
Appropriately indexed, I've never had performance problems even with whole universe queries like this in SQL Server. However, I have consistently run into comments about Oracle optimizer problems here and on other sites.
My own experience with Oracle has not been good.
The comment from the OP appears to be saying that full COUNT(*) from tables are not well handled by the optimizer. i.e.:
IF EXISTS (SELECT COUNT(*) FROM table_name HAVING COUNT(*) >= 2)
BEGIN
END
(which, when a primary key exists, can be reduced to a simple index scan - in a case of extreme optimization, one can simply query the index metadata in sysindexes.rowcnt - to find the number of entries - all without a cursor) is to be generally avoided in favor of:
DECLARE CURSOR c IS SELECT something FROM table_name;
BEGIN
OPEN c
FETCH c INTO etc. x 2 and count rows and handle exceptions
END;
IF rc >= 2 THEN BEGIN
END
That, to me would result in less readable, less portable, and less maintainable code.
Before you take Steven Feuerstein's suggestions too serious, just do a little benchmark. Is count(*) noticeably slower than the explicit cursor in your case? No? Then better use the construct that allows for simple, readable code. Which, in most cases, would be "select count(*) into v_cnt ... if v_cnt>0 then ..."
PL/SQL allows for very readable programs. Don't waste that just to nano-optimize.
Depending on the DB, there may be a sys table which stores an approximate count and can be queried in constant time. Useful if you want to know whether the table has 20 rows or 20,000 or 20,000,000.
SQL Server:
if 2 = (
select count(*) from (
select top 2 * from (
select T = 1 union
select T = 2 union
select T = 3 ) t) t)
print 'At least two'
Also, don't ever use cursors. If you think you really really need them, beat yourself with a shovel until you change your mind. Let relics from an ancient past remain relics from an ancient past.
If you want to get number of rows in a table, please don't used count(*), I would suggest count(0) that 0 is the column index of your primary key column.