Select IDs that match all items on table - sql

So I have a table called Mroom which looks something like this:
CREATE Mroom(ID_ROOM NUMBER(3), CHAR_NAME NVARCHAR2(30), CHAR_VALUE NUMBER(20));
The contents of the table are something like:
1; air-conditioner; 1
1; pool; 2
2; pool; 1
Which would mean that the room with the ID 1 has 1 air-conditioner and 2 pools. And the room with the ID 2 has one pool.
I've also created a table of a datatype, that I've called c_chars:
CREATE OR REPLACE TYPE c_chars FORCE IS OBJECT (CHAR VARCHAR2(20), VALUE NUMBER(30));
/
CREATE OR REPLACE TYPE tab_chars FORCE IS TABLE OF c_chars;
/
And I need to know if for example if there are any rooms that have at least 1 air-conditioner and a pool. My current code:
CREATE OR REPLACE PROCEDURE thisRoomOK(v_carateristicas IN tab_chars, lista OUT SYS_REFCURSOR)
IS BEGIN
OPEN lista FOR SELECT MRoom.ID_ROOM
FROM Mroom, TABLE(v_carateristicas) v_carateristicas
WHERE Mroom.CHAR_NAME = v_carateristicas.CHAR
AND Mroom.VALUE >= v_carateristicas.VALUE
GROUP BY Mroom.ID_ROOM
ORDER BY getSalas.Mroom.ID_ROOM;
END;
/
The problem is if I ask for the rooms that have at least one air-conditioner and a pool. It will return me both rooms, because the rrom 2 has a pool (it works like an OR, but I'd like it to work like and AND).
Edit 1:
If possible, I'd like to receive answers that changes the procedure, but not the tables, altough all answers are welcome!
Edit 2:
The code above is a simplification, this is the full code:
Object definition:
CREATE OR REPLACE TYPE c_valor FORCE IS OBJECT (CARATERISTICA VARCHAR2(20), VALOR NUMBER(30));
/
CREATE OR REPLACE TYPE tabc_valor FORCE IS TABLE OF c_valor;
/
Procedure:
CREATE OR REPLACE PROCEDURE getSalas_Carateristicas(v_carateristicas IN tabc_valor, lista OUT SYS_REFCURSOR)
IS BEGIN
OPEN lista FOR SELECT getSalas.ID_SALA
FROM getSalas, TABLE(v_carateristicas) v_carateristicas
WHERE getSalas.NOME_CARATERISTICA = v_carateristicas.CARATERISTICA
AND getSalas.VALOR >= v_carateristicas.VALOR
GROUP BY getSalas.ID_SALA
ORDER BY getSalas.ID_SALA;
END;
/
Procedure altered according to #XING's sugestion:
CREATE OR REPLACE PROCEDURE getSalas_Carateristicas(v_carateristicas IN tabc_valor, lista OUT SYS_REFCURSOR)
IS BEGIN
OPEN lista FOR SELECT getSalas.ID_SALA
FROM getSalas
INNER JOIN TABLE(v_carateristicas) v_carateristicas ON getSalas.NOME_CARATERISTICA = v_carateristicas.CARATERISTICA
AND getSalas.VALOR >= v_carateristicas.VALOR
GROUP BY getSalas.ID_SALA
ORDER BY getSalas.ID_SALA;
END;
/

I guess the issue is with the way you ask for a room. See below demo and explaination inline.
Proc:
CREATE OR REPLACE TYPE T541682.c_chars FORCE IS OBJECT (v_CHAR VARCHAR2(20), v_VALUE NUMBER(30));
/
CREATE OR REPLACE TYPE T541682.tab_chars FORCE IS TABLE OF c_chars;
/
CREATE OR REPLACE PROCEDURE T541682.thisRoomOK (
v_carateristicas IN tab_chars,
lista OUT SYS_REFCURSOR)
IS
BEGIN
OPEN lista FOR
SELECT MRoom.ID_ROOM
FROM Mroom
inner join TABLE (v_carateristicas) v_carateristicas
ON Mroom.CHAR_NAME = v_carateristicas.v_CHAR
AND Mroom.CHAR_VALUE = v_carateristicas.v_VALUE
GROUP BY Mroom.ID_ROOM
ORDER BY Mroom.ID_ROOM;
END;
/
declare
var tab_chars :=tab_chars();
type var1 is table of MRoom.ID_ROOM%type index by pls_integer;
var_out var1;
x sys_refcursor;
begin
var.extend(3);
-- This is the way i populate my object for which i want to match with the table. Am sure you are passing all rows same as your table hence its getting the all the rows.
var(1) := c_chars('air-conditioner',1);
var(2) := c_chars('pool',2);
-- Must be populating the third row as well. And as per your query both rows wil be picked up if you populate the object .
--var(3) := c_chars('pool',1);
-- Calling your procedure
thisRoomOK(v_carateristicas => var,lista => x);
fetch x bulk collect into var_out ;
--- displaying number of room
for i in 1..var_out.count
loop
dbms_output.put_line('Room Number - '||var_out(i));
end loop;
---passing the sys_refcursor result to another procedure
proc1(var_out);
end;
Output:
SQL> /
Room Number - 1
PL/SQL procedure successfully completed.

Related

SQL - Call a proc or function where user enters two table names

I cannot seem to find an answer for my question or even if its possible, I want to create a function or procedure that asks the user for two table names something like Table A and Table B
call myfunction(table_a, table_b)
or
call myprocedure(table_a, table_b)
inside each table contains addresses and i've created a script to tear the address to parts and try to match them together, but i only want to produce the call function above.
each table structure would have the same structure
SELECT * FROM (SELECT KEY_A
,ADDRESS_LINE
,POSTCODE
,ROW_NUMBER() OVER(PARTITION BY KEY_A ORDER BY ADDRESS_LINE) ADDRESS_LINE_RN
FROM TABLE_NAME) A
WHERE ADDRESS_LINE_RN = 1 AND LENGTH(TRIM(ADDRESS_LINE)) > 0 AND LENGTH(TRIM(POSTCODE)) > 0
Is this even possible? I just want to keep the end user experience easy and fast.
Many Thanks
create or replace procedure select_table (name1 varchar2) is
begin
execute immediate 'select * from '||name1;
end;
Here is the code but u wont see any output u need to put the result in a variable or in a collection and then loop through collection to output the result, or u can create a pipelined table function like this:
CREATE OR REPLACE FUNCTION select_table (name1 VARCHAR2)
RETURN YOUR_OBJECT%ROWTYPE PIPELINED IS
TYPE t_sql_result IS
TABLE OF your_object%rowtype INDEX BY PLS_INTEGER;
sql_result t_sql_result;
BEGIN
EXECUTE IMMEDIATE
'SELECT * FROM (SELECT KEY_A ,ADDRESS_LINE ,POSTCODE ,ROW_NUMBER()
OVER(PARTITION BY KEY_A ORDER BY ADDRESS_LINE) ADDRESS_LINE_RN FROM '||name1||')
A WHERE ADDRESS_LINE_RN = 1 AND LENGTH(TRIM(ADDRESS_LINE)) > 0 AND LENGTH(TRIM(POSTCODE)) > 0'
BULK COLLECT INTO sql_result;
FOR I IN sql_result.FIRST..sql_result.LAST LOOP
PIPE ROW (T_sql_result(I.FIRST_COLUMN,I.SECOND_COLUMN...));
END LOOP;
RETURN;
END;
SELECT * FROM TABLE(select_table('table_name'));

Define, initialise and use variables in SQL developer and SSIS ODBC connection

I am working on a script to be later used in my SSIS ETL, the source DB is oracle and I am using SQL Developer 20.0.2.75 .
I spent so much time declaring 100 variables but it doesn't see to work in SQL developer.
Define & Initialise:
Declare
V1 number;
V2 number;
.
.
.
V100 number;
Begin
Select UDF(params1,param2) into V1 from dual;
Select UDF(params3,param4) into V2 from dual;
...
End;
I was hoping I'd be able to use these variables in my script like :
select columns from table where Col1=:V1 and Col2=:V2
When used "Run Statement" prompts for values, "Run Script" doesn't see to like into Variable statements.
I even tried :
select columns from table where Col1=&&V1 and Col2=&&V2
Now my query doesn't work !
After below responses, I changed my script to :
Variable V1 Number;
Variable V2 Number;
exec select MyFunction(p1,p2) into :V1 from Dual;
/
Select columns from table where col1=:V1 and col2=:V2
It still prompts for value
This is how I defined my function
Create Function MyFunction(m IN Varchar, s IN Number)
Return Number
IS c Number;
select code into c from table where col1=m and col2=s;
Return(c);
End;
Is there anything wrong with the function?
You define variables as per you would in SQL Plus or SQLcl and then run it as a script
Text below
variable x1 number
begin
select 123 into :x1 from dual;
end;
/
print x1
Similar example in SQL Plus (and will work in SQL Dev as well)
SQL> set serverout on
SQL> variable x1 number
SQL> begin
2 select 5 into :x1 from dual;
3 end;
4 /
PL/SQL procedure successfully completed.
SQL> print x1
X1
----------
5
SQL>
SQL> select rownum from dual
2 connect by level <= :x1;
ROWNUM
----------
1
2
3
4
5
SQL>
SQL> begin
2 dbms_output.put_line('X1 is '||:x1);
3 end;
4 /
X1 is 5
PL/SQL procedure successfully completed.
I spent so much time declaring 100 variables
To me, it looks like a wrong approach. OK, declare a few variables, but 100 of them?! Why wouldn't you switch to something easier to maintain. What? A table, for example.
create table params
(var varchar2(20),
value varchar2(20)
);
Pre-populate it with all variables you use (and then just update their values), or just insert rows:
insert into params (var, value) values ('v1', UDF(params1, param2));
insert into params (var, value) values ('v2', UDF(params3, param4));
...
Fetch values through a function:
create or replace function f_params (par_var in varchar2)
return varchar2
is
retval varchar2(20);
begin
select value
into retval
from params
where var = par_var;
return retval;
end;
Use it (in your query) as:
select columns
from table
where Col1 = f_params('v1')
and Col2 = f_params('v2')
If many users use it, consider creating one "master" params table (which contains all the variables) and a global temporary table (which would be populated and used by each of those users).

Use column name as key for PL/SQL associative array when updating another column

I have an Excel table with two columns of data. Column A are codes, column B are the corresponding country names. I turned it into an associative array, ueln_country.
Now the task is to update HORSE table's column COUNTRY_OF_RESIDENCE. Horses have a column UELN where first three letters correspond to the codes in the Excel table.
I have to check if the code exists in the Excel table. If it does, I have to update HORSE.country_of_residence with a CLASSIFICATOR.code where CLASSIFICATOR.name = **the corresponding country in column B** andCLASSIFICATOR.dom_code = 'ISOCODE'`.
First try gets the error
PLS-00201: identifier 'UELN' must be declared
As I understood, it's because I can only use declared variables in PL/SQL statement.
declare
type TBL_UELN_COUNTRY is table of varchar2(50) index by varchar2 (3);
test TBL_UELN_COUNTRY;
ueln_country TBL_UELN_COUNTRY;
begin
ueln_country('008') := 'ALBAANIA';
ueln_country('010') := 'ANTARKTIS';
ueln_country('011') := 'ANTARKTIS';
....
update HORSE
set COUNTRY_OF_RESIDENCE=
when (...dummy_case...) then
(select code from meta.classifcator
where dom_code = 'ISOCODE'
and name = ueln_country(substr(UELN, 1, 3)))
where UELN is not null;
end;
/
Second try.
So because of the first error I tried to somehow declare the variables.
I knew it wouldn't work (ORA-01422: exact fetch returns more than requested number of rows) but made it to show where my idea is going:
declare
type TBL_UELN_COUNTRY is table of varchar2(50) index by varchar2 (3);
test TBL_UELN_COUNTRY;
ueln_country TBL_UELN_COUNTRY;
v_ueln horse.UELN%TYPE;
begin
select UELN into v_ueln from HORSE;
ueln_country('008') := 'ALBAANIA';
ueln_country('010') := 'ANTARKTIS';
ueln_country('011') := 'ANTARKTIS';
....
update HORSE
set COUNTRY_OF_RESIDENCE=
when (...dummy_case...) then
(select code from meta.classifcator
where dom_code = 'ISOCODE'
and name = ueln_country(substr(v_ueln, 1, 3)))
where UELN is not null;
end;
/
So I want pick a value from associative array where the key = substr(specific_horse.UELN, 1, 3).
Searched through Google and Stack for hours and didn't find an answer.
The ugly and very slow working solution was just where I didn't make the associate array and made 400+ cases for every Excel table row in the form like when -key- then select code from meta.classificator where dom_code = 'ISOKOOD' and name = -value-
associative array can not be used in SQL.
If you use expression like array(index) in SQL then, in fact, PL/SQL engine gets value by index and then result is bound into SQL engine before execution of the SQL statement.
More specifically
declare
type TBL_UELN_COUNTRY is table of varchar2(50) index by varchar2 (3);
test TBL_UELN_COUNTRY;
dummy varchar2(30);
begin
test('GBP') := 'UK';
test('USD') := 'USA';
select /*+ qwerty */ test('GBP')
into dummy
from dual;
end;
/
If we check binds for a cursor we see that actual bind value has a type VARCHAR(128) - :B1. test('GBP') in PL/SQL code is passed as bind variable B1.
SQL> column sql_text format a50
SQL> select sbc.datatype_string, sql_text
2 from v$sql s join v$sql_bind_capture sbc
3 on s.sql_id = sbc.sql_id
4 where lower(sql_text) not like '%v$sql%'
5 and lower(sql_fulltext) like 'select %qwerty%';
DATATYPE_STRING SQL_TEXT
--------------- --------------------------------------------------
VARCHAR2(128) SELECT /*+ qwerty */ :B1 FROM DUAL
SQL engine knows nothing about associative array and apparently it cannot pass and index value to array and get an element of the array back.
If you still want to use associative array to look-up some values you can declare package variable and a getter function (you may also want to implement the logic to handle a case when there is no element in array for a given index - otherwise you'll get run-time exception in such case).
create or replace package pkg as
function GetCountry(idx in varchar2) return varchar2;
end pkg;
/
sho err
create or replace package body pkg as
type TBL_UELN_COUNTRY is table of varchar2(50) index by varchar2 (3);
test pkg.TBL_UELN_COUNTRY;
function GetCountry(idx in varchar2) return varchar2 as
begin return test(idx); end;
-- initializing
begin
test('GBP') := 'UK';
test('USD') := 'USA';
end pkg;
/
sho err
And finally
SQL> set serveroutput on
SQL> declare
2 dummy varchar2(30);
3 begin
4 with t(idx) as (select 'GBP' from dual)
5 select pkg.GetCountry(t.idx)
6 into dummy
7 from t;
8 dbms_output.put_line(dummy);
9 end;
10 /
UK
PL/SQL procedure successfully completed.

Oracle procedure, placement of multiple records in variables

I'm trying to create my first oracle procedure. The select will return multiple records; I need to be able to place each record in the variables and use the record in later actions in the procedure. Any help please?
key number;
keyCount number;
rub varchar2(50);
srub varchar2(100);
type varchar2(200);
date varchar2(14);
note varchar2(500);
BEGIN
SELECT KEY,COUNT(KEY),RUB,
SRUB,TYPE ,DATE,NOTE FROM Student
WHERE S_KEY = {key};
END;
In PL/SQL we need to select results into matching variables. One way is separate variables for each column (as shown). The alternative is to use a row variable which matches the project of the query; find out more.
You've got an aggregating function - COUNT() so you need a GROUP BY clause which defines the non-aggregating columns. You say you have more than one record so you need to populate a collection not scalar variables. Find out more.
Your procedure should look something like this
create or replace procedure my_first_proc
( p_key in student.s_key%type )
as
type my_rec is record (
key number ,
keyCount number ,
rub varchar2(50); ,
srub varchar2(100) ,
type varchar2(200) ,
date varchar2(14),
note varchar2(500)
);
type my_rec_coll is table of my_rec;
l_student_recs my_rec_coll;
BEGIN
SELECT KEY,COUNT(KEY),RUB,SRUB,TYPE ,DATE,NOTE
bulk collect into l_student_recs
FROM Student
WHERE S_KEY = p_key
group by KEY,RUB,SRUB,TYPE ,DATE,NOTE
;
for idx in l_student_recs.first() .. l_student_recs.last()
loop
-- do some processing here
dbms_output.put_line('RUB = '||l_student_recs(idx).rub);
end loop;
EXCEPTION
when no_data_found then
raise_application_error(-01403, 'no student records for key='||p_key);
END;
Get into good habits:
use sensible variable names
distinguish parameter names from local variables
handle predictable exceptions

How to exceute a query stored in dictionary table into a CURSOR?

I’ve a table named QUERIES_DICTIONNARY with 2 columns (ID, SQL_QUERY) the first column is NUMBER and the second column is a Varchar represent an SQL query.
CREATE TABLE "QUERIES_DICTIONNARY"
(
"ID" NUMBER(10,0) NOT NULL ENABLE,
"SQL_QUERY" NVARCHAR2(2000) NOT NULL ENABLE
)
I want to create a stored procedure with 2 parameters (P_KEY, P_RESULTS_CURSOR) where the P_KEY is the ID of my table, and the P_RESULT_CURSOR where i execute my SQL query of the record having ID = P_KEY.
CREATE OR REPLACE PROCEDURE GET_STATISTICS_RESULTS
(
P_KEY IN NUMBER,
P_RESULTS_CURSOR OUT SYS_REFCURSOR
)
In my stored procedure I get the record where the ID = P_KEY
Once I’ve the record I want to execute the the associated query using EXECUTE IMMEDIATE into the P_RESULTS_CURSOR.
CREATE OR REPLACE PROCEDURE GET_STATISTICS_RESULTS
(
P_KEY IN NUMBER
P_RESULTS_CURSOR OUT SYS_REFCURSOR
) AS
BEGIN
---- Get the record
-- SELECT SQL_QUERY FROM QUERIES_DICTIONNARY WHERE ID = P_KEY;
---- Then Execute the query
-- OPEN P_RESULTS_CURSOR FOR ?
END GET_STATISTICS_RESULTS;
How can I do this approach in one Stored procedure please?
There is two ways: simple and not so simple. Simple way is:
CREATE OR REPLACE PROCEDURE GET_STATISTICS_RESULTS
(
P_KEY IN NUMBER;
P_RESULTS_CURSOR OUT SYS_REFCURSOR;
) AS
cursor_text QUERIES_DICTIONNARY.SQL_QUERY%TYPE;
BEGIN
---- Get the record
SELECT SQL_QUERY
INTO cursor_text
FROM QUERIES_DICTIONNARY WHERE ID = P_KEY;
---- Then Execute the query
OPEN P_RESULTS_CURSOR FOR cursor_text;
END GET_STATISTICS_RESULTS;
Not so simple way - using DBMS_SQL package. You can see example in documentation. It will be quite complicated decision.
CREATE OR REPLACE PROCEDURE GET_STATISTICS_RESULTS
(
P_KEY IN NUMBER
P_RESULTS_CURSOR OUT SYS_REFCURSOR
) AS
l_s "QUERIES_DICTIONNARY"."SQL_QUERY"%TYPE;
BEGIN
---- Get the record
SELECT SQL_QUERY into l_s FROM QUERIES_DICTIONNARY WHERE ID = P_KEY;
---- Then Execute the query
OPEN P_RESULTS_CURSOR FOR l_s;
END GET_STATISTICS_RESULTS;
PLS-00382 appears because of NVARCHAR2 column. You may try to cast nvarchar2 to varchar2:
l_s VARCHAR2(4000);
...
SELECT CAST(SQL_QUERY AS VARCHAR2(4000)) into l_s FROM QUERIES_DICTIONNARY WHERE ID = P_KEY;
...
But you may lose some data.