I have a SQL sentence where I make a minus of two tables to search the differences. As I use frequently I would like to create a function or procedure to make these and get output by screen. Someone could explain me how is the best way to make these, could you put me some example?
If you frequently use the MINUS query, then it is better to create a view on the query. To fetch the resultset, you just need to select from the view.
For example,
CREATE OR REPLACE VIEW my_view AS
SELECT column_list FROM table1
MINUS
SELECT column_list FROM table2
And to fetch the result,
SELECT * FROM my_view;
Read the documentation for more details on CREATE VIEW
Maybe this is the what you're looking for, if you're using Oracle 11g Release 2:
create or replace procedure prnt_my_view(my_view in varchar2, separator in varchar2 default ',') is
type myrefcur is ref cursor;
type rowtext is table of varchar2(256);
rowdef varchar2(256);
rows_cv myrefcur;
text rowtext;
begin
select listagg(column_name,'||'''||separator||'''||') within group (order by column_id) into rowdef from user_tab_columns where lower(table_name) = lower(my_view);
open rows_cv for 'select '||rowdef||' from '||my_view;
fetch rows_cv bulk collect into text;
for i in text.first..text.last loop
dbms_output.put_line(text(i));
end loop;
close rows_cv;
exception when others then
dbms_output.put_line('something is wrong:'||sqlerrm);
end;
edit:
if you can't use listagg, check other solutions for example here: alternative to listagg in Oracle?
Related
This question already has answers here:
Querying an Oracle Database with Dynamic Table names
(1 answer)
dynamic table name without using cursors
(1 answer)
dynamic table name in cursor
(2 answers)
dynamic table name in select statement
(4 answers)
Closed 2 years ago.
without going into details - the tools we use for performance test, it spits out several tables (name changes with every new run) with different type of data. One table has list of all the table names and data type.
I am going to use oracle table as an example so it can be easily explained.
What I am wanting to do...
Query all_table like:
QueryA:
select table_name from all_tables where table_name like 'HZ_CUST_%'
Result will say: Table_name = 'HZ_CUST_ACCOUNTS'
Query using the QueryA result like:
Select * from [QUERYA_RESULT] WHERE creation_date > sysdate, account_number between '500000' and '599999'
I got something like this but not quite sure how to do this.
declare variable TABLE_NAME CHAR(10);
SELECT TABLE_NAME into :V_NAME from all_tables WHERE TABLE_NAME LIKE 'HZ_CUST_ACCOUNTS';
select *
from :V_NAME;
Thank you for the help.
The easiest solution for your problem given what you have expressed in the comment section is to use DYNAMIC SQL
declare
v_table_name varchar2(130);
v_sql clob;
begin
-- get the table name
select table_name into v_table_name from all_tables where table_name like 'HZ_CUST_%' ;
-- Build dynamic statement
v_sql := ' select * from '||v_table_name||' where creation_date > sysdate
and account_number between ''500000'' and ''599999'' ';
execute immediate v_sql;
end;
/
From this small version, you can expand this code to whatever you need to do, specialy with the output. Of course, depending on what you need to do with it. I could for example store the maximum value of a field based on this same sentence.
declare
v_table_name varchar2(130);
v_sql clob;
vmaxvalue pls_integer;
begin
-- get the table name
select table_name into v_table_name from all_tables where table_name like 'HZ_CUST_%' ;
-- Build dynamic statement
v_sql := ' select max(table_field) from '||v_table_name||' where creation_date > sysdate
and account_number between ''500000'' and ''599999'' ';
execute immediate v_sql into vmaxvalue;
end;
/
I don't believe you need for your case to use PTF ( Polymorphic Table Functions ) which are intended to evolve the concept of pipeline functions. However, as you did explain very well what you intent to do with the result of the query, you might want to have a look at them. Check this very good example of PTF ( Remember Oracle 18c or higher ).
Polymorphic Table Functions
Code:
lc_tab1_col1 VARCHAR2(4000);
lc_tab1_col2 VARCHAR2(4000);
lc_tab2_col2 VARCHAR2(4000);
lc_tab2_col2 VARCHAR2(4000);
CURSOR my_cursor IS select col1, col2 from tab1;
[...]
OPEN my_cursor;
LOOP
FETCH my_cursor INTO lc_tab1_col1, lc_tab1_col2;
EXIT WHEN my_cursor%NOTFOUND;
SELECT lc_tab1_col2.col1, lc_tab1_col2.col2 INTO lc_tab2_col2, lc_tab2_col2 FROM lc_tab1_col2 WHERE lc_tab1_col2.col3 = lc_tab1_col1;
[...]
END LOOP;
CLOSE my_cursor;
Hey folks,
I am trying to get the above code working.
The issue I am having is that a SELECT INTO statement apparently does not support using a variable (in that case lc_tab1_col2) as the table name in the FROM clause of the statement.
When compiling the package an ORA-000942 is thrown (table or view does not exist), which tells me the variable is interpreted directly instead of being replaced and interpreted at runtime.
I can't think of a workaround on the fly, any ideas on how to fix this?
Some more background: lc_tab1_col2 contains the name of a table in the database whereas lc_tab1_col1 contains an ID.
This ID is present in all of the tables that can be contained in lc_tab1_col2 (hence the WHERE clause).
Apart from the ID there are two other columns (lc_tab1_col2.col1 and lc_tab1_col2.col2) that are present in all those tables, but that are not present in tab1. I need to select those two values to work with them inside the loop.
As there are many tables to consider, I need this SELECT INTO statement to be dynamic. It wouldn't be feasible to parse the tables one by one. Looking forward to anyone sharing a clever idea for overcoming this issue :) Thanks in advance!
I think, you exception really means that this table does not exist or you don't have privileges to SELECT it.
I've executed a below code and everything was ok. I have tried to compile it in a package and also I didn't have any compilation errors
DECLARE
user_tables varchar2(30) := 'TBLCOMPANIES';
BEGIN
SELECT table_name
INTO user_tables
FROM user_tables
WHERE user_tables.table_name = user_tables;
dbms_output.put_line(user_tables) ;
END;
/
I have a table that store the name of other tables. Like
COL_TAB
--------------
TABLE_NAME
--------------
TAB1
TAB2
TAB3
What i want to do is that, i want to run a sql query on table like this,
SELECT * FROM (SELECT TABLE_NAME from COL_TAB WHERE TABLE_NAME = 'TAB1')
Thanks
An Oracle SQL query can use a dynamic table name, using Oracle Data Cartridge and the ANY* types. But before you use those advanced features, take a step back and ask yourself if this is really necessary.
Do you really need a SQL statement to be that dynamic? Normally this is better handled by an application that can submit different types of queries. There are many application programming languages and toolkits that can handle unexpected types. If this is for a database-only operation, then normally the results are stored somewhere, in which case PL/SQL and dynamic SQL are much easier.
If you're sure you've got one of those rare cases that needs a totally dynamic SQL statement, you'll need something like my open source project Method4. Download and install it and try the below code.
Schema Setup
create table tab1(a number);
create table tab2(b number);
create table tab3(c number);
insert into tab1 values(10);
insert into tab2 values(20);
insert into tab3 values(30);
create table col_tab(table_name varchar2(30), id number);
insert into col_tab values('TAB1', 1);
insert into col_tab values('TAB1', 2);
insert into col_tab values('TAB1', 3);
commit;
Query
select * from table(method4.dynamic_query(
q'[
select 'select * from '||table_name sql
from col_tab
where id = 1
]'));
Result:
A
--
10
You'll quickly discover that queries within queries are incredibly difficult. There's likely a much easier way to do this, but it may require a design change.
I don't have a database by hand to test this but I think you are looking for something like this:
DECLARE
-- Create a cursor on the table you are looking through.
CURSOR curTable IS
SELECT *
FROM MainTable;
recTable curTable%ROWTYPE;
vcQuery VARCHAR2(100);
BEGIN
-- Loop through all rows of MainTable.
OPEN curTable;
LOOP
FETCH curTable INTO recTable;
EXIT WHEN curTable%NOTFOUND;
-- Set up a dynamic query, with a WHERE example.
vcQuery := 'SELECT ColumnA, ColumnB FROM ' || recTable.Table_Name || ' WHERE 1 = 1';
-- Execute the query.
OPEN :dyn_cur FOR vcQuery;
END LOOP;
CLOSE curTable;
END;
/
Try this
CREATE OR REPLACE PROCEDURE TEST IS
sql_stmt VARCHAR2(200);
V_NAME VARCHAR2(20);
BEGIN
sql_stmt := 'SELECT * FROM ';
EXECUTE IMMEDIATE sql_stmt|| V_NAME;
END;
Update
select statement dont work in procedure.
in sql server you can try sql block
Declare #name varchar2(50)
Select #name='Select * from '+TABLE_NAME from COL_TAB WHERE TABLE_NAME = 'TAB1'
EXEC(#name);
I have 2 procedures inside a package. I am calling one procedure to get a comma separated list of user ids.
I am storing the result in a VARCHAR variable. Now when I am using this comma separated list to put inside an IN clause in it is throwing "ORA-01722:INVALID NUMBER" exception.
This is how my variable looks like
l_userIds VARCHAR2(4000) := null;
This is where i am assigning the value
l_userIds := getUserIds(deptId); -- this returns a comma separated list
And my second query is like -
select * from users_Table where user_id in (l_userIds);
If I run this query I get INVALID NUMBER error.
Can someone help here.
Do you really need to return a comma-separated list? It would generally be much better to declare a collection type
CREATE TYPE num_table
AS TABLE OF NUMBER;
Declare a function that returns an instance of this collection
CREATE OR REPLACE FUNCTION get_nums
RETURN num_table
IS
l_nums num_table := num_table();
BEGIN
for i in 1 .. 10
loop
l_nums.extend;
l_nums(i) := i*2;
end loop;
END;
and then use that collection in your query
SELECT *
FROM users_table
WHERE user_id IN (SELECT * FROM TABLE( l_nums ));
It is possible to use dynamic SQL as well (which #Sebas demonstrates). The downside to that, however, is that every call to the procedure will generate a new SQL statement that needs to be parsed again before it is executed. It also puts pressure on the library cache which can cause Oracle to purge lots of other reusable SQL statements which can create lots of other performance problems.
You can search the list using like instead of in:
select *
from users_Table
where ','||l_userIds||',' like '%,'||cast(user_id as varchar2(255))||',%';
This has the virtue of simplicity (no additional functions or dynamic SQL). However, it does preclude the use of indexes on user_id. For a smallish table this shouldn't be a problem.
The problem is that oracle does not interprete the VARCHAR2 string you're passing as a sequence of numbers, it is just a string.
A solution is to make the whole query a string (VARCHAR2) and then execute it so the engine knows he has to translate the content:
DECLARE
TYPE T_UT IS TABLE OF users_Table%ROWTYPE;
aVar T_UT;
BEGIN
EXECUTE IMMEDIATE 'select * from users_Table where user_id in (' || l_userIds || ')' INTO aVar;
...
END;
A more complex but also elegant solution would be to split the string into a table TYPE and use it casted directly into the query. See what Tom thinks about it.
DO NOT USE THIS SOLUTION!
Firstly, I wanted to delete it, but I think, it might be informative for someone to see such a bad solution. Using dynamic SQL like this causes multiple execution plans creation - 1 execution plan per 1 set of data in IN clause, because there is no binding used and for the DB, every query is a different one (SGA gets filled with lots of very similar execution plans, every time the query is run with a different parameter, more memory is needlessly used in SGA).
Wanted to write another answer using Dynamic SQL more properly (with binding variables), but Justin Cave's answer is the best, anyway.
You might also wanna try REF CURSOR (haven't tried that exact code myself, might need some little tweaks):
DECLARE
deptId NUMBER := 2;
l_userIds VARCHAR2(2000) := getUserIds(deptId);
TYPE t_my_ref_cursor IS REF CURSOR;
c_cursor t_my_ref_cursor;
l_row users_Table%ROWTYPE;
l_query VARCHAR2(5000);
BEGIN
l_query := 'SELECT * FROM users_Table WHERE user_id IN ('|| l_userIds ||')';
OPEN c_cursor FOR l_query;
FETCH c_cursor INTO l_row;
WHILE c_cursor%FOUND
LOOP
-- do something with your row
FETCH c_cursor INTO l_row;
END LOOP;
END;
/
I have the following table name template, there are a couple with the same name and a number at the end: fmj.backup_semaforo_geo_THENUMBER, for example:
select * from fmj.backup_semaforo_geo_06391442
select * from fmj.backup_semaforo_geo_06398164
...
Lets say I need to select a column from every table which succeeds with the 'fmj.backup_semaforo_geo_%' filter, I tried this:
SELECT calle --This column is from the backup_semaforo_geo_# tables
FROM (SELECT table_name
FROM all_tables
WHERE owner = 'FMJ' AND table_name LIKE 'BACKUP_SEMAFORO_GEO_%');
But I'm getting the all_tables tables name data:
TABLE_NAME
----------
BACKUP_SEMAFORO_GEO_06391442
BACKUP_SEMAFORO_GEO_06398164
...
How can I achieve that without getting the all_tables output?
Thanks.
Presumably your current query is getting ORA-00904: "CALLE": invalid identifier, because the subquery doesn't have a column called CALLE. You can't provide a table name to a query at runtime like that, unfortunately, and have to resort to dynamic SQL.
Something like this will loop through all the tables and for each one will get all the values of CALLE from each one, which you can then loop through. I've used DBMS_OUTPUT to display them, assuming you're doing this in SQL*Plus or something that can deal with that; but you may want to do something else with them.
set serveroutput on
declare
-- declare a local collection type we can use for bulk collect; use any table
-- that has the column, or if there isn't a stable one use the actual data
-- type, varchar2(30) or whatever is appropriate
type t_values is table of table.calle%type;
-- declare an instance of that type
l_values t_values;
-- declare a cursor to generate the dynamic SQL; where this is done is a
-- matter of taste (can use 'open x for select ...', then fetch, etc.)
-- If you run the query on its own you'll see the individual selects from
-- all the tables
cursor c1 is
select table_name,
'select calle from ' || owner ||'.'|| table_name as query
from all_tables
where owner = 'FMJ'
and table_name like 'BACKUP_SEMAFORO_GEO%'
order by table_name;
begin
-- loop around all the dynamic queries from the cursor
for r1 in c1 loop
-- for each one, execute it as dynamic SQL, with a bulk collect into
-- the collection type created above
execute immediate r1.query bulk collect into l_values;
-- loop around all the elements in the collection, and print each one
for i in 1..l_values.count loop
dbms_output.put_line(r1.table_name ||': ' || l_values(i));
end loop;
end loop;
end;
/
May be a dynamic SQL in a PLSQL program;
for a in (SELECT table_name
FROM all_tables
WHERE owner = 'FMJ' AND table_name LIKE 'BACKUP_SEMAFORO_GEO_%')
LOOP
sql_stmt := ' SELECT calle FROM' || a.table_name;
EXECUTE IMMEDIATE sql_stmt;
...
...
END LOOP;