Perform validation from 2 tables in PL/SQL - stored-procedures

I'm not sure what step should I perform here is it procedure, function, or what? So my goal is to check weather if those data exists in another table. For example in HR schema, I have to validate if the department_name =AD VP from DEPARTMENTS table exists in department_name from employees table.
So how can I perform that validation in plsql?

That's a function.
I don't have HR schema so Scott's will do; this function checks whether department whose deptno column value is equal to function parameter's value exists - if so, it returns 1; otherwise, it returns 0.
I chose to return number. Another option is to return Boolean which is also a good choice - however, as Boolean can't be used in pure SQL (but only in PL/SQL), I'm returning a number in this example.
This is a function:
SQL> create or replace function f_dept_exists_01 (par_deptno in number)
2 return number
3 is
4 l_cnt number;
5 begin
6 select count(*)
7 into l_cnt
8 from dept
9 where deptno = par_deptno;
10
11 return case when l_cnt > 0 then 1
12 else 0
13 end;
14 end;
15 /
Function created.
Contents of the dept table:
SQL> select * from dept;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
Department 10 exists, so function returns 1:
SQL> select f_dept_Exists_01(10) result from dual;
RESULT
----------
1
Department 99 doesn't exist, so function returns 0:
SQL> select f_dept_Exists_01(99) result from dual;
RESULT
----------
0
SQL>
Now, adjust it to your data model.

Related

How to loop through query result set and execute specific code block depending on column recordcount

Once TADOQuery.Open (cte query) I would like to iterate over the result set and depending on whether there is only one record with check_id(50001, 50003) to execute one block of code.
In case when there are two or more records with the same check_id(50002) to execute different block of code.
Result set structure example.
row_num
buyer_id
amount
check_id
1
10001
25
50001
1
10001
30
50002
2
10002
10
50002
1
10003
10
50003
Much appreciated
You can use Group By and Count in the SQL query to get a result like below.
SQL:
SELECT check_id, COUNT(check_id) as check_id_count
FROM my_table
GROUP BY check_id
ORDER BY check_id;
Result:
check_id
check_id_count
50001
1
50002
2
50003
1
Now you can iterate the records like that:
Delphi code
cte.SQL.Text := 'SELECT check_id, COUNT(check_id) as check_id_count FROM my_table GROUP BY check_id ORDER BY check_id';
cte.Open;
while not cte.Eof do
begin
case cte.Fields[1].AsInteger of
1: Foo(cte.Fields[0].AsInteger)
2: Bar(cte.Fields[0].AsInteger)
// add more when necessary
else
FooBar; // every other count of check_id
end;
cte.Next;
end;

Stored procedure that uses cursor

I made a procedure that uses a cursor to generate a report. It is designed to return products with p_qoh > avg(p_qoh).
When i run the cursor on its own outside of the procedure, APEX tells me
PLS-00204: function or pseudo-column 'AVG' may be used inside a SQL
statement only
How is this not inside a SQL statement? Im new to SQL just a required class as a comp sci major.
Heres the whole block. If you run just the cursor you will see what I mean.
CREATE OR REPLACE PROCEDURE prod_rep
IS
CURSOR cur_qoh IS
SELECT p_qoh, p_descript, p_code
FROM xx_product;
TYPE type_prod IS RECORD(
prod_qoh xx_product.p_qoh%TYPE,
prod_code xx_product.p_code%TYPE,
prod_descr xx_product.p_descript%TYPE);
rec_prod type_prod;
BEGIN
OPEN cur_qoh;
LOOP
FETCH cur_qoh INTO rec_prod;
EXIT WHEN cur_qoh%NOTFOUND;
IF rec_prod.prod_qoh > avg(rec_prod.prod_qoh) THEN
DBMS_OUTPUT.PUT_LINE(rec_prod.prod_code||' -> '||rec_prod.prod_desc);
END IF;
END LOOP;
CLOSE cur_qoh;
END;
UPDATE: working block
BEGIN
FOR cur_r IN (SELECT p_qoh, p_descript, p_code FROM xx_product
WHERE p_qoh > (SELECT avg(p_qoh) FROM xx_product))
LOOP
DBMS_OUTPUT.PUT_LINE(cur_r.p_code||' -> '|| cur_r.p_descript);
END LOOP;
END;
Well, yes - this is (PL/)SQL, but you just can't use AVG that way. Have a look: this is an example based on Scott's schema.
Average salary is
SQL> select avg(sal) from emp;
AVG(SAL)
----------
2077,08333
In order to select salaries higher than the average one, you'd use a subquery. Let's call this query the "A" query (for future reference):
SQL> select ename, sal
2 from emp
3 where sal > (select avg(sal) from emp);
ENAME SAL
---------- ----------
JONES 2975
BLAKE 2850
CLARK 2450
KING 5000
FORD 3000
SQL>
Also, no need to declare that much things - a simple cursor FOR loop is easier to maintain:
SQL> create or replace procedure prod_rep as
2 begin
3 for cur_r in (select ename, sal from emp
4 where sal > (select avg(sal) from emp))
5 loop
6 dbms_output.put_line(cur_r.ename ||' '|| to_char(cur_r.sal, '9990'));
7 end loop;
8 end;
9 /
Procedure created.
SQL> begin prod_rep; end;
2 /
JONES 2975
BLAKE 2850
CLARK 2450
KING 5000
FORD 3000
PL/SQL procedure successfully completed.
SQL>
See? No need to declare a cursor (OK, you do use that SELECT in a loop), type & record of that type, open the cursor, worry when to exit a loop, close the cursor.
Your code, however, doesn't make much sense in Apex environment. There's no DBMS_OUTPUT there, and it is rather unusual to create a report (either a classic or interactive one) using a procedure; I've never done that. I've used a function (which is a PL/SQL code) that returns a SQL query and based reports on that.
Your problem is a simple one, so - use the Wizard, create a report and - as its source - use the "A" query (I mentioned earlier). That's all you should do.
At that point you're into PL/SQL, not SQL.
You could use the analytic function AVG into your SQL query to get the average:
CURSOR cur_qoh IS
SELECT p_qoh, p_descript, p_code, AVG(p_goh) OVER () as p_goh_avg
FROM xx_product;
You can add your new field to type_prod:
TYPE type_prod IS RECORD(
prod_qoh xx_product.p_qoh%TYPE,
prod_code xx_product.p_code%TYPE,
prod_descr xx_product.p_descript%TYPE,
prod_avg xx_product.p_qoh%TYPE)
;
And then you can use your average into the LOOP:
IF rec_prod.prod_qoh > rec_prod.prod_avg THEN
It doesn't make sense to use AVG inside a PL/SQL loop. PL/SQL is procedural, at that point you're not dealing with the whole set of data: you manage only one row at a time. Whereas SQL deals with sets of data.
Using the analytic function, you get the average of the column on every row, so you can manage it inside the PL/SQL loop.

How to deploy a procedure automatically which is present in a clob column

I have a table which has a clob column. And in the clob column I have a package which I would like to be compiled without manually intervention.Can anyone tell me if this is possible or not in PLSQL.
Note: The package in the clob column is not already present in the database. This is something new which I want to be compiled.
Not sure what you mean by "without manual intervention", but yes you can create an object e.g. package from data stored in a CLOB column. for example:
insert into mytable (id, clob_col) values
(1, 'create or replace package p1 as procedure proc1; end;');
commit;
Then
declare
clob_val clob;
begin
select clob_col into clob_val
from mytable
where id = 1;
execute immediate clob_val;
end;
Of course, a package has two parts, the specification and the body. So you ideally need to store these separately (e.g. 1 row for the spec and another for the body). If they were within the same CLOB value then you would need to parse the CLOB value and extract them singly before running the execute immediate.
If there were several PL/SQL procedures in a table, you'd use a loop. Here's an example:
SQL> create table test (id number, col clob);
Table created.
SQL> insert into test values
2 (1, 'create or replace package pkg_test as ' ||
3 ' function today return date; ' ||
4 'end;'
5 );
1 row created.
SQL> insert into test values
2 (2, 'create or replace package body pkg_test as ' ||
3 ' function today return date is ' ||
4 ' begin ' ||
5 ' return sysdate; ' ||
6 ' end; ' ||
7 ' end;'
8 );
1 row created.
SQL> declare
2 cursor c1 is
3 select id, col
4 from test
5 order by id;
6 c1r c1%rowtype;
7 begin
8 open c1;
9 loop
10 fetch c1 into c1r;
11 exit when c1%notfound;
12
13 execute immediate(c1r.col);
14 end loop;
15 close c1;
16 end;
17 /
PL/SQL procedure successfully completed.
SQL> select pkg_test.today from dual;
TODAY
----------------
30.01.2018 12:50
SQL>
WooHoo! It works!

Delphi Table sorting

I have a simple problem which gave em a headache
I need to sort integers in a Database table TDBGrid ( its ABS database from component ace ) with the following order
0
1
11
111
121
2
21
211
22
221
and so on
which means every number starting with 1 should be under 1
1
11
111
5
55
can anyone help me?
thanks
This should work to get stuff in the right order:
Convert the original number to a string;
Right-pad with zeroes until you have a string of 3 characters wide;
(optional) Convert back to integer.
Then sorting should always work the way you want. Probably it's best to let the database do that for you. In MySQL you'd do something like this:
select RPAD(orderid,3,'0') as TheOrder
from MyTable
order by 1
I just ran this in SQL Server Management Studio - note I mixed up the rows in the input so they were not in sorted order:
create table #temp( ID Char(3));
insert into #temp (ID)
select '111' union
select '221';
select '0' union
select '21' union
select '1' union
select '11' union
select '211' union
select '121' union
select '2' union
select '22' union
select * from #temp order by ID;
I got the following output:
ID
----
0
1
11
111
121
2
21
211
22
221
(10 row(s) affected)
If you're getting different results, you're doing something wrong. However, it's hard to say what because you didn't post anything about how you're retrieving the data from the database.
Edit: Some clarification by the poster indicates that the display is in a TDBGrid attached to a table using Component Ace ABS Database. If that indeed is the case, then the answer is to create an index on the indicated column, and then set the table's IndexName property to use that index.
select cast(INT_FIELD as varchar(9)) as I
from TABxxx
order by 1

sqlite2: Joining max values per column from another table (subquery reference)?

I'm using the following database:
CREATE TABLE datas (d_id INTEGER PRIMARY KEY, name_id numeric, countdata numeric);
INSERT INTO datas VALUES(1,1,20); //(NULL,1,20);
INSERT INTO datas VALUES(2,1,47); //(NULL,1,47);
INSERT INTO datas VALUES(3,2,36); //(NULL,2,36);
INSERT INTO datas VALUES(4,2,58); //(NULL,2,58);
INSERT INTO datas VALUES(5,2,87); //(NULL,2,87);
CREATE TABLE names (n_id INTEGER PRIMARY KEY, name text);
INSERT INTO names VALUES(1,'nameA'); //(NULL,'nameA');
INSERT INTO names VALUES(2,'nameB'); //(NULL,'nameB');
What I would like to do, is to select all values (rows) of names - to which all columns of datas will be appended, for the row where datas.countdata is maximum for n_id (and of course, where name_id = n_id).
I can somewhat get there with the following query:
sqlite> .header ON
sqlite> SELECT * FROM names AS n1
LEFT OUTER JOIN (
SELECT d_id, name_id, countdata FROM datas AS d1
WHERE d1.countdata IN (
SELECT MAX(countdata) FROM datas
WHERE name_id=1
)
) AS p1 ON n_id=name_id;
n1.n_id|n1.name|p1.d_id|p1.name_id|p1.countdata
1|nameA|2|1|47
2|nameB|||
... however - obviously - it only works for a single row (the one explicitly set by name_id=1).
The problem is, the SQL query fails whenever I try to somehow reference the "current" n_id:
sqlite> SELECT * FROM names AS n1
LEFT OUTER JOIN (
SELECT d_id, name_id, countdata FROM datas AS d1
WHERE d1.countdata IN (
SELECT MAX(countdata) FROM datas
WHERE name_id=n1.n_id
)
) AS p1 ON n_id=name_id;
SQL error: no such column: n1.n_id
Is there any way of achieving what I want in Sqlite2??
Thanks in advance,
Cheers!
Oh, well - that wasn't trivial at all, but here is a solution:
sqlite> SELECT * FROM names AS n1
LEFT OUTER JOIN (
SELECT d1.*
FROM datas AS d1, (
SELECT max(countdata) as countdata,name_id
FROM datas
GROUP BY name_id
) AS ttemp
WHERE d1.name_id = ttemp.name_id AND d1.countdata = ttemp.countdata
) AS p1 ON n1.n_id=p1.name_id;
n1.n n1.name p1.d_id p1.name_id p1.countdata
---- ------------ ---------- ---------- -----------------------------------
1 nameA 2 1 47
2 nameB 5 2 87
Well, hope this ends up helping someone, :)
Cheers!
Notes: note that just calling max(countdata) screws up competely d_id:
sqlite> select d_id,name_id,max(countdata) as countdata from datas group by name_id;
d_id name_id countdata
---- ------------ ----------
3 2 87
1 1 47
so to get correct corresponding d_id, we must do max() on datas separately - and then perform sort of an intersect with the full datas (except that intersect in sqlite requires that there are equal number of columns in both datasets, which is not the case here - and even if we made it that way, as seen above d_id will be wrong, so intersect will not work).
One way to do that is in using a sort of a temporary table, and then utilize a multiple table SELECT query so as to set conditions between full datas and the subset returned via max(countdata), as shown below:
sqlite> CREATE TABLE ttemp AS SELECT max(countdata) as countdata,name_id FROM datas GROUP BY name_id;
sqlite> SELECT d1.*, ttemp.* FROM datas AS d1, ttemp WHERE d1.name_id = ttemp.name_id AND d1.countdata = ttemp.countdata;
d1.d d1.name_id d1.countda ttemp.coun ttemp.name_id
---- ------------ ---------- ---------- -----------------------------------
2 1 47 47 1
5 2 87 87 2
sqlite> DROP TABLE ttemp;
or, we can rewrite the above so a SELECT subquery (sub-select?) is used, like this:
sqlite> SELECT d1.* FROM datas AS d1, (SELECT max(countdata) as countdata,name_id FROM datas GROUP BY name_id) AS ttemp WHERE d1.name_id = ttemp.name_id AND d1.countdata = ttemp.countdata;
d1.d d1.name_id d1.countda
---- ------------ ----------
2 1 47
5 2 87

Resources