I have many tables that have the same column 'customer_number'.
I can get a list of all these table by query:
SELECT table_name FROM ALL_TAB_COLUMNS
WHERE COLUMN_NAME = 'customer_number';
The question is how do I get all the records that have a specific customer number from all these tables without running the same query against each of them.
To get record from a table, you have write a query against that table. So, you can't get ALL the records from tables with specified field without a query against each one of these tables.
If there is a subset of columns that you are interested in and this subset is shared among all tables, you may use UNION/UNION ALL operation like this:
select * from (
select customer_number, phone, address from table1
union all
select customer_number, phone, address from table2
union all
select customer_number, phone, address from table3
)
where customer_number = 'my number'
Or, in simple case where you just want to know what tables have records about particular client
select * from (
select 'table1' src_tbl, customer_number from table1
union all
select 'table2', customer_number from table2
union all
select 'table3', customer_number from table3
)
where customer_number = 'my number'
Otherwise you have to query each table separatelly.
DBMS_XMLGEN enables you to run dynamic SQL statements without custom PL/SQL.
Sample Schema
create table table1(customer_number number, a number, b number);
insert into table1 values(1,1,1);
create table table2(customer_number number, a number, c number);
insert into table2 values(2,2,2);
create table table3(a number, b number, c number);
insert into table3 values(3,3,3);
Query
--Get CUSTOMER_NUMBER and A from all tables with the column CUSTOMER_NUMBER.
--
--Convert XML to columns.
select
table_name,
to_number(extractvalue(xml, '/ROWSET/ROW/CUSTOMER_NUMBER')) customer_number,
to_number(extractvalue(xml, '/ROWSET/ROW/A')) a
from
(
--Get results as XML.
select table_name,
xmltype(dbms_xmlgen.getxml(
'select customer_number, a from '||table_name
)) xml
from user_tab_columns
where column_name = 'CUSTOMER_NUMBER'
);
TABLE_NAME CUSTOMER_NUMBER A
---------- --------------- -
TABLE1 1 1
TABLE2 2 2
Warnings
These overly generic solutions often have issues. They won't perform as well as a plain old SQL statements and they are more likely to run into bugs. In general, these types of solutions should be avoided for production code. But they are still very useful for ad hoc queries.
Also, this solution assumes that you want the same columns from each row. If each row is different then things get much more complicated and you may need to look into technologies like ANYDATASET.
I assume you want to automate this. Two approaches.
SQL to generate SQL scripts
.
spool run_rep.sql
set head off pages 0 lines 200 trimspool on feedback off
SELECT 'prompt ' || table_name || chr(10) ||
'select ''' || table_name ||
''' tname, CUSTOMER_NUMBER from ' || table_name || ';' cmd
FROM all_tab_columns
WHERE column_name = 'CUSTOMER_NUMBER';
spool off
# run_rep.sql
PLSQL
Similar idea to use dynamic sql:
DECLARE
TYPE rcType IS REF CURSOR;
rc rcType;
CURSOR c1 IS SELECT table_name FROM all_table_columns WHERE column_name = 'CUST_NUM';
cmd VARCHAR2(4000);
cNum NUMBER;
BEGIN
FOR r1 IN c1 LOOP
cmd := 'SELECT cust_num FROM ' || r1.table_name ;
OPEN rc FOR cmd;
LOOP
FETCH rc INTO cNum;
EXIT WHEN rc%NOTFOUND;
-- Prob best to INSERT this into a temp table and then
-- select * that to avoind DBMS_OUTPUT buffer full issues
DBMS_OUTPUT.PUT_LINE ( 'T:' || r1.table_name || ' C: ' || rc.cust_num );
END LOOP;
CLOSE rc;
END LOOP;
END;
Related
I'am trying UNION ALL many tables into a new table.The columns of the old tables are the same, but the order of the columns is different, so the below SQL statement will get wrong result:
CREATE TABLE sum_7_2018_xia_weijian
AS
(
SELECT * FROM huiwen
UNION
SELECT * FROM penglai
UNION
SELECT * FROM baoluo
UNION
SELECT * FROM dongge
UNION
SELECT * FROM resultdonglu
UNION
SELECT * FROM resultwencheng
UNION
SELECT * FROM tan_illeg
);
I finally corrected it, but the SQL statements is too redundant:
step 1. get column names of one of the old tables named huiwen
SELECT string_agg(column_name, ',')
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'huiwen';
results:
> string_agg
> ----------------------------------------------------------------------
>
> gid,id,geom,sxm,sxdm,sxxzqdm,xzqhdm,xzmc,sfzgjsyd,sfkfbj,sfjbnt,sfld,sflyhx,sfhyhx
step 2. union tables as a new table. I copy the string_agg of table huiwen to each SELECT-UNION to keep the order of columns, this is clumsy.
CREATE TABLE sum_2018_xia_weijian
AS
(
SELECT gid,id,geom,sxm,sxdm,sxxzqdm,xzqhdm,xzmc,sfzgjsyd,sfkfbj,sfjbnt,sfld,sflyhx,sfhyhx
FROM huiwen
UNION ALL
SELECT gid,id,geom,sxm,sxdm,sxxzqdm,xzqhdm,xzmc,sfzgjsyd,sfkfbj,sfjbnt,sfld,sflyhx,sfhyhx
FROM penglai
UNION ALL
SELECT gid,id,geom,sxm,sxdm,sxxzqdm,xzqhdm,xzmc,sfzgjsyd,sfkfbj,sfjbnt,sfld,sflyhx,sfhyhx
FROM baoluo
);
results:
> Query returned successfully: 2206 rows affected, 133 msec execution time.
I tried to do some optimization by pl/pgSQL using Declarations of variable to handle column names, but failed to find any SQL data type can handle this. Using of RECORD result Pseudo-Types ERROR:
CREATE or replace FUNCTION ct() RETURNS RECORD AS $$
DECLARE
clms RECORD;
BEGIN
SELECT column_name INTO clms
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'huiwen';
RETURN clms;
END;
$$ LANGUAGE plpgsql;
CREATE TABLE sum_2018_xia_weijian
AS
(
SELECT ct() FROM huiwen
UNION ALL
SELECT ct() FROM penglai
UNION ALL
SELECT ct() FROM baoluo
UNION ALL
SELECT ct() FROM dongge
UNION ALL
SELECT ct() FROM resultdonglu
UNION ALL
SELECT ct() FROM resultwencheng
UNION ALL
SELECT ct() FROM tan_illeg
);
You may use STRING_AGG twice for getting the UNION ALL. You can get all the columns in specific order by explicitly ordering it by column_name in the string_agg.
Here's a generic function which takes an array of tables and a final table name.
CREATE or replace FUNCTION fn_create_tab(tname_arr TEXT[], p_tab_name TEXT)
RETURNS VOID AS $$
DECLARE
l_select TEXT;
BEGIN
select STRING_AGG(query,' UNION ALL ' ) INTO l_select
FROM
(
SELECT 'select ' || string_agg( column_name,','
ORDER BY column_name ) || ' from ' || table_name as query
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = ANY (tname_arr)
GROUP BY table_name
) s;
IF l_select IS NOT NULL
THEN
EXECUTE format ('DROP TABLE IF EXISTS %I',p_tab_name);
EXECUTE format ('create table %I AS %s',p_tab_name,l_select);
END IF;
END;
$$ LANGUAGE plpgsql;
Now, run the function like this:
select fn_create_tab(ARRAY['huiwen','penglai'],'sum_2018_xia_weijian');
Instead of making the programming block complex you can follow some below concepts from the documentation of Union or Union All as it says :
The number of columns in all queries must be the same.
The corresponding columns must have the compatible data type.
The column names of the first query determine the column names of the combined result set.
The GROUP BY and HAVING clauses are applied to each individual query, not the final result set.
The ORDER BY clause is applied to the combined result set, not within the individual result set.
By following the 3rd point make your Union query adjusted to refer to the table whose column order is expected in the result.
I have table like this:
Table-1
Table-2
Table-3
Table-4
Table-5
each table is having many columns and one of the column name is employee_id.
Now, I want to write a query which will
1) return all the tables which is having this columns and
2) results should show the tables if the column is having values or empty values by passing employee_id.
e.g. show table name, column name from Table-1, Table-2,Table-3,... where employee_id='1234'.
If one of the table doesn't have this column, then it is not required to show.
I have verified with link, but it shows only table name and column name and not by passing some column values to it.
Also verified this, but here verifies from entire schema which I dont want to do it.
UPDATE:
Found a solution, but by using xmlsequence which is deprecated,
1)how do I make this code as xmltable?
2) If there are no values in the table, then output should have empty/null. or default as "YES" value
WITH char_cols AS
(SELECT /*+materialize */ table_name, column_name
FROM cols
WHERE data_type IN ('CHAR', 'VARCHAR2') and table_name in ('Table-1','Table-2','Table-3','Table-4','Table-5'))
SELECT DISTINCT SUBSTR (:val, 1, 11) "Employee_ID",
SUBSTR (table_name, 1, 14) "Table",
SUBSTR (column_name, 1, 14) "Column"
FROM char_cols,
TABLE (xmlsequence (dbms_xmlgen.getxmltype ('select "'
|| column_name
|| '" from "'
|| table_name
|| '" where upper("'
|| column_name
|| '") like upper(''%'
|| :val
|| '%'')' ).extract ('ROWSET/ROW/*') ) ) t ORDER BY "Table"
/
This query can be done in one step using the (non-deprecated) XMLTABLE.
Sample Schema
--Table-1 and Table-2 match the criteria.
--Table-3 has the right column but not the right value.
--Table-4 does not have the right column.
create table "Table-1" as select '1234' employee_id from dual;
create table "Table-2" as select '1234' employee_id from dual;
create table "Table-3" as select '4321' employee_id from dual;
create table "Table-4" as select 1 id from dual;
Query
--All tables with the column EMPLOYEE_ID, and the number of rows where EMPLOYEE_ID = '1234'.
select table_name, total
from
(
--Get XML results of dynamic query on relevant tables and columns.
select
dbms_xmlgen.getXMLType(
(
--Create a SELECT statement on each table, UNION ALL'ed together.
select listagg(
'select '''||table_name||''' table_name, count(*) total
from "'||table_name||'" where employee_id = ''1234'''
,' union all'||chr(10)) within group (order by table_name) v_sql
from user_tab_columns
where column_name = 'EMPLOYEE_ID'
)
) xml
from dual
) x
cross join
--Convert the XML data to relational.
xmltable('/ROWSET/ROW'
passing x.xml
columns
table_name varchar2(128) path 'TABLE_NAME',
total number path 'TOTAL'
);
Results
TABLE_NAME TOTAL
---------- -----
Table-1 1
Table-2 1
Table-3 0
Just try to use code below.
Pay your attention that may be nessecery clarify scheme name in loop.
This code works for my local db.
set serveroutput on;
DECLARE
ex_query VARCHAR(300);
num NUMBER;
emp_id number;
BEGIN
emp_id := <put your value>;
FOR rec IN
(SELECT table_name
FROM all_tab_columns
WHERE column_name LIKE upper('employee_id')
)
LOOP
num :=0;
ex_query := 'select count(*) from ' || rec.table_name || ' where employee_id = ' || emp_id;
EXECUTE IMMEDIATE ex_query into num;
if (num>0) then
DBMS_OUTPUT.PUT_LINE(rec.table_name);
end if;
END LOOP;
END;
I tried with the xml thing, but I get an error I cannot solve. Something about a zero size result. How difficult is it to solve this instead of raising exception?! Ask Oracle.
Anyway.
What you can do is use the COLS table to know what table has the employee_id column.
1) what table from table TABLE_LIKE_THIS (I assume column with table names is C) has this column?
select *
from COLS, TABLE_LIKE_THIS t
where cols.table_name = t
and cols.column_name = 'EMPLOYEE_ID'
-- think Oracle metadata/ think upper case
2) Which one has the value you are looking for: write a little chunk of Dynamic PL/SQL with EXECUTE IMMEDIATE to count the tables matching above condition
declare
v_id varchar2(10) := 'JP1829'; -- value you are looking for
v_col varchar2(20) := 'EMPLOYEE_ID'; -- column
n_c number := 0;
begin
for x in (
select table_name
from all_tab_columns cols
, TABLE_LIKE_THIS t
where cols.table_name = t.c
and cols.column_name = v_col
) loop
EXECUTE IMMEDIATE
'select count(1) from '||x.table_name
||' where Nvl('||v_col||', ''##'') = ''' ||v_id||'''' -- adding quotes around string is a little specific
INTO n_c;
if n_c > 0 then
dbms_output.put_line(n_C|| ' in ' ||x.table_name||' has '||v_col||'='||v_id);
end if;
-- idem for null values
-- ... ||' where '||v_col||' is null '
-- or
-- ... ||' where Nvl('||v_col||', ''##'') = ''##'' '
end loop;
dbms_output.put_line('done.');
end;
/
Hope this helps
How do I structure an SQL statement to run across multiple flat unrelated tables and display the result with the result of the select and the name of the table where the result came from.
The scenario is such that I have several tables with the same column name in each. It is data that I have received from outside parties that I store as it is in different tables.
Same tables look like:
Table 1: pid, parent_name, student_name, student_number, class_name, columnN
Table 2: pid, previous_school, previous_school, student_number, columnN
Table 3: pid, student_name, student_number, parent_name, column4, columnN
Table 14: pid, student_number, parent_name, column4, columnN
Table N: pid, previous_school, parent_name, column4, columnN
I need an SQL statement that searches for student_name across all tables
In pseudo code: for each table, find a student named john doe and return to me the row where you got the result and the table where you found the result
Give the result in the following presentation:
john doe, Table 1, pid
john doe, Table 9, pid
To make it a bit complicated, the column student_name might not be in all tables so the query needs to proceed graciously if doesn't find the column there.
You are looking for dynamic SQL. Assemble your query from the system catalog automatically:
SELECT string_agg('SELECT student_name, '''
|| c.oid::regclass || ''' AS tbl, pid FROM '
|| c.oid::regclass
|| $$ WHERE student_name = 'John Doe'$$
, E'\nUNION ALL\n')
FROM pg_namespace n
JOIN pg_class c ON c.relnamespace = n.oid
WHERE n.nspname = 'public' -- schema name where your tables lie
AND c.relname LIKE 't%' -- and / or filter table names
AND EXISTS (
SELECT 1 FROM pg_attribute
WHERE attrelid = c.oid
AND attname = 'student_name' -- make sure column exists
AND NOT attisdropped -- and is alive
);
Produces the query string:
SELECT student_name, 'tbl1' AS tbl, pid FROM tbl1 WHERE student_name = 'John Doe'
UNION ALL
SELECT student_name, 'tbl2' AS tbl, pid FROM tbl2 WHERE student_name = 'John Doe'
UNION ALL
SELECT student_name, 'tbl3' AS tbl, pid FROM tbl3 WHERE student_name = 'John Doe'
...
Then run it in a second call or completely automate it with a PL/pgSQL function using EXECUTE. Example:
Select a dynamic set of columns from a table and get the sum for each
This query produces safe code with sanitized identifiers preventing SQL injection. (Explanation for oid::regclass here.)
There are more related answers. Use a search.
BTW, LIKE in student_name LIKE 'John Doe' is pointless. Without wildcards, just use =.
Just add a literal value to each select that describes the source table:
select student_name, 'Table 1', pid
from table1
where ...
union
select some_name, 'Table 2', pid
from table2
where ...
union
...
You could get the names of the tables from the all_tables view, create an union query dinamically and then execute it with execute immediate. Something like this --be careful the code might have errors--:
DECLARE
v_query VARCHAR2(4000) := '';
v_student_name VARCHAR2(50) := 'John Doe';
type r_results is record (
student_name VARCHAR2(500),
table_name VARCHAR2(100),
pid NUMBER
);
v_results r_results;
CURSOR c_tables IS
SELECT table_name
FROM all_tables
WHERE upper(table_name) LIKE '%_STUDENT_RECORDS';
BEGIN
FOR client_table IN c_tables LOOP
IF v_query IS NOT NULL THEN
v_query := v_query || ' UNION ';
END IF;
v_query := v_query || 'SELECT student_name, ' || client_table.table_name || ', ' || pid FROM ' || client_table.table_name || ' WHERE student_name = ''' || v_student_name || '''';
END LOOP;
EXECUTE IMMEDIATE v_query INTO v_results;
END;
I have a series of history tables in an oracle 9 database. History_table_00 contains last months data, History_table_01 contains the month before, and History_table_02 the month before that. Next month, History_table_02 will automatically get renamed to history_table_03, history_table_01 renamed to history_table_02, history_table_00 renamed to history_table_01, and a new history_table_00 will be created to gather the newest history (I really hope I am making sense).
Anyway, I need to write a select statement that will dynamically select all history tables. I am hoping this won't be too complicated because they all share the same name, just appended with sequential number so I can discover the table names with:
select table_name from all_tables where table_name like 'HISTORY_TABLE_%';
My standard query for each table is going to be:
select id, name, data_column_1, data_column_2 from history_table_%;
What do I have to do to accomplish the goal of writing a sql statement that will always select from all history tables without me needing to go in every month and add the new table? Thanks for anything you guys can provide.
you can use ref cursor but i wouldn't recommend it.
it goes like this
create table tab_01 as select 1 a , 10 b from dual;
create table tab_02 as select 2 a , 20 b from dual;
create table tab_03 as select 3 a , 30 b from dual;
create or replace function get_all_history
return sys_refcursor
as
r sys_refcursor;
stmt varchar2(32000);
cursor c_tables is
select table_name
from user_tables
where table_name like 'TAB_%';
begin
for x in c_tables loop
stmt := stmt || ' select * from ' || x.table_name ||' union all';
end loop;
stmt := substr(stmt , 1 , length(stmt) - length('union all'));
open r for stmt;
return r;
end;
/
SQL> select get_all_history() from dual;
GET_ALL_HISTORY()
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
A B
---------- ----------
1 10
2 20
3 30
I would suggest you to define a view in which you select from all history tables using union all
and each time the tables are renamed you modify the view as well.
create OR replace view history_data as
SELECT id, name, data_column_1, data_column_2 FROM history_table_01
union all
SELECT id, name, data_column_1, data_column_2 FROM history_table_02
union all
SELECT id, name, data_column_1, data_column_2 FROM history_table_03
;
then you can simle SELECT * FROM history_data;
you can build the view dynamicaly with the help of the following statment:
SELECT 'SELECT id, name, data_column_1, data_column_2 FROM ' || table_name || ' union all '
FROM user_tables
WHERE table_name like 'HISTORY_TABLE_%'
The best idea is to do a dynamic SQL statement that builds up a large query for each table existing in the database. Give the following SQL query try. (please forgive my formatting, I am not sure how to do line-breaks on here)
DECLARE #table VARCHAR(255)
, #objectID INT
, #selectQuery VARCHAR(MAX)
SELECT #objectID = MIN(object_id)
FROM sys.tables
WHERE name LIKE 'history_table_%'
WHILE #objectID IS NOT NULL
BEGIN
SELECT #table = name
FROM sys.tables
WHERE object_id = #objectID
ORDER BY object_id
SELECT #selectQuery = ISNULL(#selectQuery + ' UNION ALL ', '') + 'select id, name, data_column_1, data_column_2 FROM ' + #table
SELECT #objectID = MIN(object_id)
FROM sys.tables
WHERE name LIKE 'tblt%'
AND object_id > #objectID
END
SELECT #selectQuery
--EXEC (#selectQuery)
A Possible Solution:
CREATE OR REPLACE PROCEDURE GET_HIST_DETAILS IS
DECLARE
QUERY_STATEMENT VARCHAR2(4000) := NULL;
CNT NUMBER;
BEGIN
select COUNT(table_name) INTO CNT from all_tables where table_name like 'HISTORY_TABLE_%';
FOR loop_counter IN 1..CNT
LOOP
IF LOOP_COUNTER <> CNT THEN
{
QUERY_STATEMENT := QUERY_STATEMENT || 'select id, name, data_column_1, data_column_2 from history_table_0' || loop_counter || ' UNION';
}
ELSE
{
QUERY_STATEMENT := QUERY_STATEMENT || 'select id, name, data_column_1, data_column_2 from history_table_0' || loop_counter ;
}
EXECUTE_IMMEDIATE QUERY_STATEMENT;
END LOOP;
END GET_DETAILS;
PS:I dont have Oracle installed , so havent tested it for syntax errors.
I need to rotate left table to right:
And left table is very long, about 1000 lines. The problem is to name columns in the right table automatically.
Thanks.
If you are in Oracle 11g, there is a pivot query feature. I have not used it yet, but it looks like something like this is what you need:
select * from (
select bill, goods
from bills
)
pivot
(
count(goods)
for goods in ('TV', 'CD', 'phone', 'mouse')
)
order by bill
It seems to require that you list the possible values for goods. But if you need to generate this on the fly, you could do one query to get the distinct values, turn that list into a comma-delimited string, and then execute the real query as dynamic SQL or a generated script.
(I would expect there to be a table listing the possible values of goods, which you could use to generate the list of values instead of querying the large fact table.)
You would do something like this
select bill, sum(p.TV), sum(p.CD)
from (select bill, decode(goods,'TV',1,0) as TV, decode(goods,'CD',1,0) as CD
from original_table) p
group by bill
more info here: http://orafaq.com/node/1871
You can try this, but you would need to hardcode the values. You could write a script to produce the sql for you.
SELECT
SUM(bill) AS bill
SUM(CASE goods WHEN ‘TV’ THEN bill ELSE 0 END) AS TV,
SUM(CASE goods WHEN ‘CD’ THEN 1 ELSE 0 END) AS CD,
-- 998 more like this
FROM
tab
GROUP BY goods;
UPDATE
You can use a plsql script to build the sql for you, or you can modify it to build a view for you. You can run the script to update the view when values change.
DECLARE
query_str VARCHAR;
BEGIN
select column_name from user_tab_columns where table_name = 'TAB';
query_str := 'SELECT SUM(bill) AS bill,';
for col in (select column_name from user_tab_columns where table_name = 'TAB') loop
query_str := query_str || SUM(CASE goods WHEN ‘'' || col.column_name || ''’ THEN bill ELSE 0 END) AS ' || col.column_name || ', '
end loop;
query_str := query_str || ' FROM tab GROUP BY goods;';
dbms_output.put_line(query_str);
END;