Get Distinct Value from an Element in a PL/SQL Associative Array - sql

Say I have the table XX_TABLE_SAMPLE with the records below:
TAB_ID BATCH_NAME EMP_NO EMP_NAME STATUS SALARY CATEGORY
------ ---------- ------ -------- -------- ------- ------------
1 BATCH_A 1 Jared Active 1000 (NULL)
2 BATCH_A 2 Siege Active 3000 (NULL)
3 BATCH_A 45 James Suspended 2000 (NULL)
4 BATCH_B 67 Harry Active 100 (NULL)
5 BATCH_B 99 Pikachu Active 10000 (NULL)
6 BATCH_x 100 Eren Suspended 4000 (NULL)
and i have the PL/SQL block like below (please note the comments):
declare
cursor emp_cur is
select *
from XX_TABLE_SAMPLE
where status = 'Active';
type emp_cur_type is table of XX_TABLE_SAMPLE%rowtype index by pls_integer;
emp_rec emp_cur_type;
begin
open emp_cur;
fetch emp_cur
bulk collect
into emp_rec;
close emp_cur;
/* do some pre-processing here via another stored procedure
but the problem is, it has a parameter of p_batch_name, not a type of associative array
for i in emp_rec.first..emp_rec.last loop
pay_pkg.validate_pay (p_batch_name => emp_rec(i).p_batch_name);
end;
-- the problem with this is that it will loop 4 times (twice each for BATCH_A and BATCH_B)
when it should only get the 2 batch names (BATCH_A and BATCH_B)
*/
-- then check the salary of the emp and update the associative array
for i in emp_rec.first..emp_rec.last loop
if emp_rec(i).salary > 200 and emp_rec(i).salary < 3000 then
emp_rec(i).CATEGORY = 'Manager';
end if;
end loop;
forall i in emp_rec.first..emp_rec.last
update XX_TABLE_SAMPLE
set CATEGORY = emp_rec(i).CATEGORY
where TAB_ID = emp_rec(i).TAB_ID;
end;
With that, I would like to get the distinct values of the Element Batch_Name in an Associative Array
and then pass it to the Stored Procedure pay_pkg.validate_pay.
Any thoughts on how i can achive this without declaring another Explicit Cursor?

To me it seems that you are thinking in unnecessary complex solution. I think your example can be simplified to the following solution that requires zero PL/SQL data structures (r is an implicit record type but the compiler makes it for you!).
Btw, there is no need to be afraid of cursors in PL/SQL.
declare
-- a dummy placeholder for the "tricky" subprogram
procedure validate_pay(p_batch_name in varchar2) is begin null; end;
begin
for r in (select distinct batch_name
from xx_sample_data
where status = 'Active')
loop
validate_pay(p_batch_name => r.batch_name);
end loop;
update xx_sample_data set
category = 'Manager'
where status = 'Active'
and salary between 201 and 2999
;
end;
/

Maybe is something else you aren't saying, but if you need {"BATCH_A", "BATCH_B"}
Why dont just use:
SELECT DISTINCT BATCH_NAME
FROM XX_TABLE_SAMPLE
WHERE status = 'Active'
AND salary > 200
AND salary < 3000

if you are on oracle 12 there is another way.
but it involves selecting from your associative array
see
Select from PLSQL Associative array?

Related

incrementing i with condition

I want to increment by i by 1 whenever the value is different for v_id.
i = 1;
for i in 1..10 loop
SELECT subject_details.NEXTVAL
INTO v_id
FROM dual;
INSERT INTO employee (id, subject)
VALUES (i,v_id);
i = i + 1;
end loop;
what this does is
id subject
1 3647
2 3647
3 5678
4 5678
5 5678
but what I want is to increment the value of "i" by 1 whenever there is a change in value for "v_id"
id subject
1 3647
1 3647
2 5678
2 5678
2 5678
I think you can just use dense_rank():
select t.*, dense_rank() over (order by subject)
from t;
If the values can be interleaved, then you can use lag() and a cumulative sum:
select t.*,
sum(case when prev_subject = subject then 0 else 1 end) over (order by id)
from (select t.*,
lag(subject) over (order by id) as prev_subject
from t
) t;
While there are good suggestions offered, I think they mostly miss your biggest mistake. I believe that is a miss understanding of pl/sql scoping rules. Somewhere in tour code (before what you posted) you defined a variable i then created i as the index variable of a FOR loop. The problem you face is that due to scoping rules these are not the same variable. According to documentation: (also see demonstration here)
for index name Name for the implicitly declared integer variable that is
local to the FOR LOOP statement. Statements outside the loop cannot
reference index. Statements inside the loop can reference index, but
cannot change its value. After the FOR LOOP statement runs, index is
undefined.
There are a couple other issues with you process:
In Oracle the assignment operator is := not =; (I assume this is just
a typo).
In each loop iteration you run "subject_details.NEXTVAL", and
subsequently insert that result into your table. Doing so will
increment the value every time. Thus you cannot produce your desired
output - nor your claimed output.
try
j = 1;
v_id_copy = 0;
for i in 1..10 loop
SELECT subject_details.NEXTVAL INTO v_id FROM dual;
if(v_id_copy==0) then
v_id_copy = v_id;
elseif (v_id_copy != v_id) then
j=j+1;
v_copy_id=v_id;
endif
insert into employee (id,subject) values (j,v_id)
end loop;

Oracle SQL: How to perform comparison by converting "Varchar" to "Number"

I have only read-only access to Oracle SQL (Can use SELECT command only).
I want to perform the comparison conditions on a Varchar type column by converting it to Number type.
Reference Data:
ID | Price | Currency
-------------------------
548 | 6000 | USD
9784 | 7000 | EUR
254 | 5000 | USD
Query used:
select id, price, currency
from ( select item_id id,
to_number(item_price) price,
item_currency currency
from item
where item_price is not null) A
where A.price <= 6000;
Expected Output:
ID | Price | Currency
-------------------------
548 | 6000 | USD
254 | 5000 | USD
"ORA-01722: invalid number" means what it says: you are attempting to cast a string to a number when the string contains a non-numeric value.
This is the danger of using weakly-typed columns. People always say, "our application will validate the input" . But the one thing you can guarantee is that someone (or something) will stick a non-numeric value into that column.
Okay, so hindsight is a marvellous thing and you probably don't want a lecture from me about data integrity: what, practically, can you do? Basically you need to identify the values which won't cast to numbers and handle them somehow (change the value, filter them from the query, whatever).
There's no Oracle built in to test for numberness but it's easy to write one:
create or replace function is_number (p_str in varchar2)
return varchar2
is
return_value varchar2(5);
n number;
begin
begin
dbms_output.put_line('str='||p_str);
n := to_number(p_str);
return_value := 'TRUE';
exception
when invalid_number or value_error then
dbms_output.put_line('here');
return_value := 'FALSE';
when others then
dbms_output.put_line(sqlerrm);
raise;
end;
return return_value;
end;
/
Here's one way to use it.
with cte as ( select id, price, currency from item
where is_number(price) = 'TRUE')
select id, price, currency
from cte
where to_number(price) <= 6000;
You can use CAST() function.
Example of using it is below
SELECT product_id, CAST(ad_sourcetext AS VARCHAR2(30)) FROM print_media;
For more informations visit THIS.
Hope it helps

content of the package declaration

How can I see the content of the description aria of a package and specific types?(pl sql)
For example in package p1 I have 3 functions f1, f2, f3; 2 procedures p1, p2 and 2 variables v1, v2.
I need a list with 2 columns: one for name (f1, f2 etc) and one for the type (function, procedure, variable etc).
You can find details about the functions and procedures in a package by querying the ALL_ARGUMENTS data dictionary view, or its brethren USER_ARGUMENTS and DBA_ARGUMENTS.
For an example I created the following package:
CREATE OR REPLACE PACKAGE demo AS
PROCEDURE p_none;
PROCEDURE p_two(a INTEGER, b INTEGER);
FUNCTION f_none RETURN INTEGER;
FUNCTION f_three(c INTEGER, q INTEGER, z INTEGER) RETURN INTEGER;
END;
I then ran the following query against it:
SQL> select object_name, argument_name, sequence, in_out
2 from all_arguments
3 where package_name = 'DEMO'
4 order by object_name, sequence;
OBJECT_NAME ARGUMENT_NAME SEQUENCE IN_OUT
------------------------------ ------------------------------ ---------- ---------
F_NONE 1 OUT
F_THREE 1 OUT
F_THREE C 2 IN
F_THREE Q 3 IN
F_THREE Z 4 IN
P_NONE 0 IN
P_TWO A 1 IN
P_TWO B 2 IN
Here you can see all of the arguments to the functions and procedures in our package. Note that there is an extra entry with a null argument name for the return value for each of the two functions. Also, the procedure that has no arguments has a row with a null argument name and a zero SEQUENCE value.
So, to list all functions, you could search for all entries in this view with a null argument name and a SEQUENCE value not equal to 0:
SQL> select distinct object_name
2 from all_arguments
3 where package_name = 'DEMO'
4 and argument_name is null
5 and sequence != 0;
OBJECT_NAME
------------------------------
F_THREE
F_NONE
Listing procedures in a similar way is a little trickier:
SQL> select distinct object_name
2 from all_arguments a1
3 where package_name = 'DEMO'
4 and ( sequence = 0
5 or not exists (select 0
6 from all_arguments a2
7 where a2.package_name = 'DEMO'
8 and a2.object_name = a1.object_name
9 and a2.argument_name is null));
OBJECT_NAME
------------------------------
P_TWO
P_NONE
While this approach appears to work with procedures and functions, I don't know how to list the package-scope variables, types and other things declared within a package header without parsing the package spec, as suggested by #wweicker.
This is no trivial task! You'll have to parse the package specification by looping through the lines, i.e.
select *
from all_source
where owner = '<the package owner>'
and name = '<the package name>'
and type = 'PACKAGE'
order by line;

SQL function - Returning value for number of records in table

I am putting a bit of SQL into an Oracle script, if I run the Vanilla SQL i get the correct output of one single returned value/record. However in my custom function I get the value I am looking for returned as many times as there are records. Here is an example of what I have.
create function EXAMPLE_FUNCTION (passedID in NUMBER)
return NUMBER
IS
returnValue NUMBER;
BEGIN
SELECT "TABLE1"."ID" INTO returnValue
FROM "TABLE1" WHERE "TABLE1"."ID" = passedID;
RETURN returnValue;
END;
So if TABLE1 has 20 records I will get the record with ID 1 returned 20 times,
I am not sure where its going wrong, but I'm sure its simple!
You are probably calling the function like this:
select EXAMPLE_FUNCTION (1)
from my_table
Call it without the from:
select EXAMPLE_FUNCTION (1)
EDIT:
As pointed by Shannon Oracle requires the from clause so I searched and found examples using the dual table:
select EXAMPLE_FUNCTION (1)
from dual
Just do something like: SELECT EXAMPLE_FUNCTION(1) FROM dual;
Would something like the below work in your function?
Select COUNT(*) into returnValue
from TABLE1
where Table1.ID = passedID;
What value are you using for passedID? If you want to get soemthing different out for each row you need to pass something different in.
Compare these two function calls. First a fixed value:
SQL> select example_function(1) from table1
2 /
EXAMPLE_FUNCTION(1)
-------------------
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
20 rows selected.
SQL>
Then using the table column to supply the parameter:
SQL> select example_function(id) from table1
2 /
EXAMPLE_FUNCTION(ID)
--------------------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
20 rows selected.
SQL>

What is a simple way to combine grouped values in one field?

I mean:
Table PHONE_CODES:
ID CODE_NAME PHONE_CODE
1 USA 8101
2 USA 8102
3 PERU 8103
4 PERU_MOB 81031
5 PERU_MOB 81032
And I want via select to get something like this:
CODE_NAME ZONE_CODES
USA 8101; 8102;
PERU 8103
PERU_MOB 81031; 81032;
I could get it via the function below, but perhaps there is a better way:
select distinct(CODE_NAME) as CODE_NAME, get_code_names_by_ZONE(CODE_NAME) as ZONE_CODES from PHONE_CODES;
Function:
create or replace function get_code_names_by_ZONE
(
ZONE_CODE_NAME in varchar2
)
return varchar2
as
codes_list varchar2(4000);
cursor cur_codes_list is
select p.PHONE_CODE
from PHONE_CODES p
where p.CODE_NAME = ZONE_CODE_NAME;
begin
for codes_list_rec in cur_codes_list
LOOP
-- dbms_output.put_line('PHONE_CODE:[' || codes_list_rec.PHONE_CODE || ']');
codes_list := codes_list || codes_list_rec.PHONE_CODE || '; ';
end loop;
return codes_list;
EXCEPTION
when NO_DATA_FOUND then
return 'notfound';
WHEN others then
dbms_output.put_line('Error code:' || SQLCODE || ' msg:' || SQLERRM);
return null;
end get_code_names_by_ZONE;
/
Tim Hall has an excellent discussion on the various string aggregation techniques that are available in Oracle.
A function would be my preferred method of achieving what you want.
If you're on 11g, take a look at the new PIVOT extension to SQL - the best documentation looks to be in the Data Warehousing Guide section. I believe however that the target of the "... for in ..." clause cannot be a subquery and has to be a hard-coded list of values.
Good link Justin. Tim hall is awesome. I followed his advice and here it is:
1 SELECT CODE_NAME,
2 LTRIM(MAX(SYS_CONNECT_BY_PATH(PHONE_CODES,';'))
3 KEEP (DENSE_RANK LAST ORDER BY curr),';') AS PHONE_CODES
4 FROM (SELECT CODE_NAME,
5 PHONE_CODES,
6 ROW_NUMBER() OVER (PARTITION BY CODE_NAME ORDER BY PHONE_CODES) AS curr,
7 ROW_NUMBER() OVER (PARTITION BY CODE_NAME ORDER BY PHONE_CODES) -1 AS prev
8 FROM a)
9 GROUP BY CODE_NAME
10 CONNECT BY prev = PRIOR curr AND CODE_NAME = PRIOR CODE_NAME
11* START WITH curr = 1
SQL> /
CODE_NAME PHONE_CODES
---------- --------------------------------------------------
PERU 8103
PERU_MOB 81031;81032
USA 8101;8102
dbBradley - I don't think the Pivot extension works here. The Pivot extension requires the use of an aggregate (sum, count, ...).