Having problems with this select in Oracle PLSQL - sql

I have this code:
create view nombres_lab as
select L.NOMBRE_LAB, R.NOM_REG, count(*) as vacunas_suministradas
from LABORATORIOS L
inner join CREA_VACUNAS CV on l.cod_lab = cv.cod_lab
inner join vacunas V on cv.cod_vac = v.cod_vac
inner join vtorio VT on v.cod_vac = vt.cod_vac
inner join datos_vac_sum DVS on vt.cod_vtorio = dvs.cod_vtorio
inner join frasco_vac FV on dvs.s_unica = fv.s_unica
inner join direccion D on vt.cod_dir = d.cod_dir
inner join region R on d.cod_reg = r.cod_reg
where v.cod_vac = 100202 or v.cod_vac = 100303
group by l.nombre_lab, r.nom_reg;
create or replace procedure ranking is
begin
select nombre_lab, nom_reg, count(*) as vacunas_suministradas, rank() over(order by count(*) desc) as ranking from nombres_lab group by nom_reg, nombre_lab;
end;
I've been trying to convert this select to an select into so that it can work inside the procedure, but nothing that i try works.
Error: PLS-00428: an INTO clause is expected in this SELECT statement
I know its weird to do just a select in the procedure and nothing more, but i want to know if its possible

To answer your question (whether it is possible to do something like that): as Justin commented, it is (kind of), but you have to follow some rules.
For example (based on Scott's schema, as I don't have your tables):
SQL> CREATE OR REPLACE VIEW nombres_lab
2 AS
3 SELECT deptno nombre_lab, job nom_reg FROM emp;
View created.
This is a procedure which fixes error you got ("an INTO clause is expected in this SELECT statement"): declare some local variables and select into them.
SQL> CREATE OR REPLACE PROCEDURE ranking
2 IS
3 l_nombre_lab nombres_lab.nombre_lab%TYPE;
4 l_nom_reg nombres_lab.nom_reg%TYPE;
5 l_vacunas_suministradas NUMBER;
6 l_ranking NUMBER;
7 BEGIN
8 SELECT nombre_lab,
9 nom_reg,
10 COUNT (*),
11 RANK () OVER (ORDER BY COUNT (*) DESC)
12 INTO l_nombre_lab,
13 l_nom_reg,
14 l_vacunas_suministradas,
15 l_ranking
16 FROM nombres_lab
17 GROUP BY nom_reg, nombre_lab;
18 END;
19 /
Procedure created.
Will it work? Unfortunately, not. It would if query returned only one row, though:
SQL> EXEC ranking;
BEGIN ranking; END;
*
ERROR at line 1:
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at "SCOTT.RANKING", line 8
ORA-06512: at line 1
So, what to do? You could use a loop (then you don't need any variables nor an into clause) and process values fetched. I don't have any smart ideas so I'm just displaying what cursor returned:
SQL> CREATE OR REPLACE PROCEDURE ranking
2 IS
3 BEGIN
4 FOR cur_r IN ( SELECT nombre_lab,
5 nom_reg,
6 COUNT (*) vacunas_suministradas,
7 RANK () OVER (ORDER BY COUNT (*) DESC) ranking
8 FROM nombres_lab
9 GROUP BY nom_reg, nombre_lab)
10 LOOP
11 DBMS_OUTPUT.put_line (
12 cur_r.nombre_lab
13 || ' - '
14 || cur_r.nom_reg
15 || ' - '
16 || cur_r.vacunas_suministradas
17 || ' - '
18 || cur_r.ranking);
19 END LOOP;
20 END;
21 /
Procedure created.
SQL> SET SERVEROUTPUT ON
SQL> EXEC ranking;
30 - SALESMAN - 4 - 1
20 - ANALYST - 2 - 2
20 - CLERK - 2 - 2
20 - MANAGER - 1 - 4
30 - CLERK - 1 - 4
30 - MANAGER - 1 - 4
10 - MANAGER - 1 - 4
10 - CLERK - 1 - 4
10 - PRESIDENT - 1 - 4
PL/SQL procedure successfully completed.
SQL>
Or, you could switch to a function that returns e.g. ref cursor:
SQL> CREATE OR REPLACE FUNCTION f_ranking
2 RETURN SYS_REFCURSOR
3 IS
4 rc SYS_REFCURSOR;
5 BEGIN
6 OPEN rc FOR SELECT nombre_lab,
7 nom_reg,
8 COUNT (*),
9 RANK () OVER (ORDER BY COUNT (*) DESC)
10 FROM nombres_lab
11 GROUP BY nom_reg, nombre_lab;
12
13 RETURN rc;
14 END;
15 /
Function created.
SQL> var l_rc refcursor
SQL> exec :l_rc := f_ranking
PL/SQL procedure successfully completed.
SQL> print l_rc
NOMBRE_LAB NOM_REG COUNT(*) RANK()OVER(ORDERBYCOUNT(*)DESC)
---------- --------- ---------- -------------------------------
30 SALESMAN 4 1
20 ANALYST 2 2
20 CLERK 2 2
20 MANAGER 1 4
30 CLERK 1 4
30 MANAGER 1 4
10 MANAGER 1 4
10 CLERK 1 4
10 PRESIDENT 1 4
9 rows selected.
SQL>
Therefore, yes - it is possible, but it depends on what you actually want to do.

The count(*) inside a rank function seems like an issue. Try below, I think that should solve the problem.
WITH nombre_lab_summary AS
(
SELECT
nombre_lab,
nom_reg,
COUNT(*) AS vacunas_suministradas,
FROM nombres_lab
GROUP BY
nom_reg,
nombre_lab
)
SELECT
*,
RANK() OVER (ORDER BY vacunas_suministradas DESC) AS ranking
FROM nombre_lab_summary
GROUP BY
nom_reg,
nombre_lab

Related

Counting nulls for each column in table

We want to count how many nulls each column in a table has. There are too many columns to do this one by one, so the following PLSQL procedure was created.
In the first part of the procedure, all column names are obtained. This works, as the dbms_output correctly lists them all.
Secondly, a query inserts the count of null values in the variable 'nullscount'. This part does not work, as the output printed for this variable is always 0, even for columns where we know there are nulls.
Does anyone know how to handle the second part correctly?
Many thanks.
CREATE OR REPLACE PROCEDURE COUNTNULLS AS
nullscount int;
BEGIN
for c in (select column_name from all_tab_columns where table_name = upper('gp'))
loop
select count(*) into nullscount from gp where c.column_name is null;
dbms_output.put_line(c.column_name||' '||nullscount);
end loop;
END COUNTNULLS;
You can get it with just one query like this: this query scans table just once:
DBFiddle: https://dbfiddle.uk/asgrCezT
select *
from xmltable(
'/ROWSET/ROW/*'
passing
dbms_xmlgen.getxmltype(
(
select
'select '
||listagg('count(*)-count("'||column_name||'") as "'||column_name||'"',',')
||' from '||upper('gp')
from user_tab_columns
where table_name = upper('gp')
)
)
columns
column_name varchar2(30) path './name()',
cnt_nulls int path '.'
);
Results:
COLUMN_NAME CNT_NULLS
------------------------------ ----------
A 5
B 4
C 3
Dynamic sql in this query uses (24 chars + column name length) so it should work fine for example for 117 columns with average column name length = 10. If you need more, you can rewrite it a bit, for example:
select *
from xmltable(
'let $cnt := /ROWSET/ROW/CNT
for $r in /ROWSET/ROW/*[name() != "CNT"]
return <R name="{$r/name()}"> {$cnt - $r} </R>'
passing
dbms_xmlgen.getxmltype(
(
select
'select count(*) CNT,'
||listagg('count("'||column_name||'") as "'||column_name||'"',',')
||' from '||upper('gp')
from user_tab_columns
where table_name = upper('gp')
)
)
columns
column_name varchar2(30) path '#name',
cnt_nulls int path '.'
);
create table gp (
id number generated by default on null as identity
constraint gp_pk primary key,
c1 number,
c2 number,
c3 number,
c4 number,
c5 number
)
;
-- add some data with NULLS and numbers
DECLARE
BEGIN
FOR r IN 1 .. 20 LOOP
INSERT INTO gp (c1,c2,c3,c4,c5) VALUES
(CASE WHEN mod(r,2) = 0 THEN NULL ELSE mod(r,2) END
,CASE WHEN mod(r,3) = 0 THEN NULL ELSE mod(r,3) END
,CASE WHEN mod(r,4) = 0 THEN NULL ELSE mod(r,4) END
,CASE WHEN mod(r,5) = 0 THEN NULL ELSE mod(r,5) END
,5);
END LOOP;
END;
/
-- check what is in the table
SELECT * FROM gp;
-- do count of each column
DECLARE
l_colcount NUMBER;
l_statement VARCHAR2(100) := 'SELECT COUNT(*) FROM $TABLE_NAME$ WHERE $COLUMN_NAME$ IS NULL';
BEGIN
FOR r IN (SELECT column_name,table_name FROM user_tab_columns WHERE table_name = 'GP') LOOP
EXECUTE IMMEDIATE REPLACE(REPLACE(l_statement,'$TABLE_NAME$',r.table_name),'$COLUMN_NAME$',r.column_name) INTO l_colcount;
dbms_output.put_line('Table: '||r.table_name||', column'||r.column_name||', COUNT: '||l_colcount);
END LOOP;
END;
/
Table created.
Statement processed.
Result Set 4
ID C1 C2 C3 C4 C5
1 1 1 1 1 5
2 - 2 2 2 5
3 1 - 3 3 5
4 - 1 - 4 5
5 1 2 1 - 5
6 - - 2 1 5
7 1 1 3 2 5
8 - 2 - 3 5
9 1 - 1 4 5
10 - 1 2 - 5
11 1 2 3 1 5
12 - - - 2 5
13 1 1 1 3 5
14 - 2 2 4 5
15 1 - 3 - 5
16 - 1 - 1 5
17 1 2 1 2 5
18 - - 2 3 5
19 1 1 3 4 5
20 - 2 - - 5
20 rows selected.
Statement processed.
Table: GP, columnID, COUNT: 0
Table: GP, columnC1, COUNT: 10
Table: GP, columnC2, COUNT: 6
Table: GP, columnC3, COUNT: 5
Table: GP, columnC4, COUNT: 4
Table: GP, columnC5, COUNT: 0
c.column_name is never null because it's the content of the column "column_name" of the table "all_tab_columns"
not the column of which name is the value of c.column_name, in table gp.
You have to use dynamic query and EXECUTE IMMEDIATE to achieve what you want.

Row wise comparisons between two Oracle table

I'm a beginner & trying to find a solution for the below issue
I have two table with same number of column, same names and same datatype.
I'm sorting both the table by time and trying to do row wise comparison (including data in each row) using not exist operation to identify the mismatch of records. If all the rows in both table are same then I need to return True else False.
Note: No relationship between the tables to do Join.
This is how I understood the question.
Sample tables: they have the same number of columns, with same names, same datatypes - but, you didn't say whether they contain the same number of rows. Mine don't:
SQL> select * from t1 order by id;
ID NAME COUNTRY
---------- ------ -------
1 Little Croatia
2 Foot Hungary
3 Scott UK
SQL> select * from t2 order by id;
ID NAME COUNTRY
---------- ------ -------
1 Little Croatia
2 Michel France
SQL>
The idea is to compare SHA1 hash values computed on concatenated values from both tables for comparative rows (which ones? Those that share the same row number).
SQL> set serveroutput on
SQL> declare
2 l_cnt_1 number;
3 l_cnt_2 number;
4 l_cnt number;
5 --
6 cursor c1 is
7 select id, name, country
8 from (select id, name, country,
9 row_number() over (order by id) rn
10 from t1)
11 order by rn;
12 cursor c2 is
13 select id, name, country
14 from (select id, name, country,
15 row_number() over (order by id) rn
16 from t2)
17 order by rn;
18 c1r c1%rowtype;
19 c2r c2%rowtype;
20 h1 raw(20);
21 h2 raw(20);
22 begin
23 -- which table is smaller (by number of rows)?
24 select count(*) into l_cnt_1 from t1;
25 select count(*) into l_cnt_2 from t2;
26 l_cnt := least(l_cnt_1, l_cnt_2);
27
28 open c1;
29 open c2;
30 -- loop as many times as there are rows in a smaller table; it doesn't make
31 -- sense comparing rows that don't have a "pair"
32 for i in 1 .. l_cnt loop
33 fetch c1 into c1r;
34 fetch c2 into c2r;
35
36 -- SHA1 hash
37 select sys.dbms_crypto.hash(utl_raw.cast_to_raw
38 (c1r.id || c1r.name || c1r.country), sys.dbms_crypto.hash_sh1),
39 sys.dbms_crypto.hash(utl_raw.cast_to_raw
40 (c2r.id || c2r.name || c2r.country), sys.dbms_crypto.hash_sh1)
41 into h1, h2
42 from dual;
43
44 dbms_output.put_line('Comparing');
45 dbms_output.put_line('- T1: ' || c1r.id ||', '|| c1r.name ||', '|| c1r.country);
46 dbms_output.put_line('- T2: ' || c2r.id ||', '|| c2r.name ||', '|| c2r.country);
47
48 if h1 = h2 then
49 dbms_output.put_line('- Result: match');
50 else
51 dbms_output.put_line('- Result: no match');
52 end if;
53 end loop;
54
55 close c1;
56 close c2;
57 end;
58 /
Result is:
Comparing
- T1: 1, Little, Croatia
- T2: 1, Little, Croatia
- Result: match
Comparing
- T1: 2, Foot, Hungary
- T2: 2, Michel, France
- Result: no match
PL/SQL procedure successfully completed.
SQL>

Oracle SQL set sql values to variable

I am trying to compare dates between the same table. I wanted to store each value to a variable and compare the if there is a month difference between the two dates.
Here is my table structure:
TABLE NAME: SYSTEMPARAM
SEQNO - INT primary key
SYSTEMDATE - DATE
Here is what I want to achieve:
var startdate = "SELECT SYSTEMDATE FROM SYSTEMPARAM WHERE SEQNO = '1'";
var enddate = "SELECT SYSTEMDATE FROM SYSTEMPARAM WHERE SEQNO = '2'";
if(MONTHS_BETWEEN(startdate , enddate ){
//Conditions here
}
I want to compare if there is a month difference between two sql statement.
"Procedural" means PL/SQL. So, do it that way.
Just to know what dates represent:
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
Table contents:
SQL> select * From systemparam;
SEQNO SYSTEMDATE
---------- ----------
1 04.04.2021
2 05.04.2021
Anonymous PL/SQL block:
SQL> set serveroutput on
SQL> declare
2 l_startdate systemparam.systemdate%type;
3 l_enddate systemparam.systemdate%type;
4 begin
5 select s.systemdate
6 into l_startdate
7 from systemparam s
8 where s.seqno = 1;
9
10 select s.systemdate
11 into l_enddate
12 from systemparam s
13 where s.seqno = 2;
14
15 if months_between(l_enddate, l_startdate) > 0 then
16 dbms_output.put_line('Dates are not equal');
17 end if;
18 end;
19 /
Dates are not equal
PL/SQL procedure successfully completed.
SQL>
Though, if there are any "months" between these two dates ("0.01" months between is still "months between", although tiny), then they aren't equal so it is maybe more obvious to use if l_enddate <> l_startdate then instead, isn't it? Also, check another option of the SELECT statement:
SQL> declare
2 l_startdate systemparam.systemdate%type;
3 l_enddate systemparam.systemdate%type;
4 begin
5 select max(case when s.seqno = 1 then s.systemdate end),
6 max(case when s.seqno = 2 then s.systemdate end)
7 into l_startdate,
8 l_enddate
9 from systemparam s;
10
11 if l_enddate <> l_startdate then
12 dbms_output.put_line('Dates are not equal');
13 end if;
14 end;
15 /
Dates are not equal
PL/SQL procedure successfully completed.
SQL>

Simple Procedure for Printing the count of rows in a table without using COUNT operation ORACLE

Can anyone help me to create Simple Procedure for Printing the count of rows in a table without using COUNT operation in ORACLE ?
You must be looking for the LOOP in the procedure, considering that aggregate functions are not allowed..
Let's say I have an account table with 3 records in it.
SQL> SELECT * FROM ACCOUNT;
ACC_NR SUM_ CUST_ID
---------- ---------- ----------
500 3400 100
600 5000 101
700 5070 102
SQL>
Now, creating the procedure:
SQL> CREATE OR REPLACE PROCEDURE COUNT_ACCOUNT (
2 P_OUT_COUNT OUT NUMBER
3 ) AS
4 BEGIN
5 P_OUT_COUNT := 0;
6 FOR I IN (
7 SELECT
8 1 AS RW
9 FROM
10 ACCOUNT
11 ) LOOP
12 P_OUT_COUNT := P_OUT_COUNT + 1;
13 END LOOP;
14 END COUNT_ACCOUNT;
15 /
Procedure created.
SQL>
Executing the procedure to see the output:
SQL> SET SERVEROUT ON
SQL>
SQL> DECLARE
2 CNT NUMBER := 0;
3 BEGIN
4 COUNT_ACCOUNT(CNT);
5 DBMS_OUTPUT.PUT_LINE('NUMBER OF RECORDS IN ACCOUNT TABLE: ' || CNT);
6 END;
7 /
NUMBER OF RECORDS IN ACCOUNT TABLE: 3
PL/SQL procedure successfully completed.
SQL>
Cheers!!
Now you can/may wrap the below with either
a procedure or a function with or without a return statement
DECLARE
CNT NUMBER;
BEGIN
SELECT MAX(ROWNUM) INTO CNT FROM TABLE_NAME;
DBMS_OUTPUT.PUT_LINE('NUMBER OF ROWS : '||CNT);
END;

Oracle Forms Secuences Or Autoincrement or an other option

I need to put the number 2 in the company that is CODCIA, the sequence is 1,2,3,4 etc. and when putting the number 4 the sequence is reset to 1.
I only have 1 table with a codcia field.
I have it this way:
CODCIA NRODOCTO
------ --------
2 1
2 2
2 3
4 4
4 5
4 6
WITH THIS CODE:
IF (:E.NRODOCTO in (2)) then
BEGIN
SELECT NVL(MAX(NRODOCTO),1200)+1
INTO :E.NRODOCTO
FROM PS91;
EXCEPTION WHEN OTHERS THEN :E.NRODOCTO := 1200;
END;
END IF;
and I need this:
CODCIA NRODOCTO
------ --------
2 1
2 2
2 3
4 1
4 2
4 3
It seems that quite a lot of information is missing in your question; not that you should post absolutely all information, but oversimplifying it also doesn't help.
Anyway: I'd say that you might be using the POST-INSERT trigger and update NRODOCTO column value, because - doing that in a form would require looping through the whole block, paying attention to CODCIA item value and incrementing the appropriate NRODOCTO value, and doing it for all distinct CODCIA values. That's somewhat dull as nobody prevents user from entering mixed CODCIA values.
Here's a SQL*Plus-based example (as I'm not going to create a form for it). The PS91 table most probably doesn't look as simple as that (re-read my first sentence), but that's what you told us.
SQL> create table ps91 (codcia) as
2 (select 2 from dual union all
3 select 2 from dual union all
4 select 2 from dual union all
5 --
6 select 4 from dual union all
7 select 4 from dual
8 );
Table created.
SQL> alter table ps91 add nrodocto number;
Table altered.
SQL> -- POST-INSERT form trigger
SQL> merge into ps91 p
2 using (select rowid rid, row_number() over (partition by codcia order by null) rn
3 from ps91
4 ) x
5 on (p.rowid = x.rid)
6 when matched then update set p.nrodocto = x.rn;
5 rows merged.
SQL> select * from ps91
2 order by codcia, nrodocto;
CODCIA NRODOCTO
---------- ----------
2 1
2 2
2 3
4 1
4 2
SQL>