Oracle PL/SQL set column names using other table - oracle

I'm struggling with naming columns in one table, using a reference table containing these names. I'm sure it should be possible, but I can't seem to find the right solution or think of the correct logic to achieve it...
Situation: I got 2 tables, one with data, columns having descriptive names, and one with a translation of these column names to meaningful names (reference table, or 'codebook').
I'm looking for a way to return the data of the first table, with the names of the columns given in the second column of the second table.
Tables look like:
dataTable:
q1,q2,q3
1,2,3
4,5,6
and
translationTable:
descName, meanName
q1, meaning1
q2, meaning2
q3, meaning3
Result should be:
meaning1,meaning2,meaning3
1,2,3
4,5,6
Help would be highly appreciated!

You can not directly do it, because you need a query whose columns are variable, based on some value.
Slightly different, what you can do is build a dynamic SQL to have your query created by Oracle:
SETUP:
SQL> create table dataTable(q1,q2,q3) as
2 select 1,2,3 from dual union all
3 select 4,5,6 from dual
4 ;
Table created.
SQL> create table translationTable(descName, meanName) as
2 select 'q1', 'meaning1' from dual union all
3 select 'q2', 'meaning2' from dual union all
4 select 'q3', 'meaning3' from dual ;
Table created.
This will create and print your query:
SQL> declare
2 vSQL varchar2(1000);
3 begin
4 select listagg (column_name || ' AS "' || meanName || '"', ', ') within group (order by column_name)
5 into vSQL
6 from user_tab_columns col
7 inner join translationTable tr
8 on (upper(tr.descName) = col.column_name)
9 where table_name = upper('dataTable');
10 --
11 vSQL := 'select ' || vSQL || ' from dataTable';
12 dbms_output.put_line(vSQL);
13 end;
14 /
select Q1 AS "meaning1", Q2 AS "meaning2", Q3 AS "meaning3" from dataTable
PL/SQL procedure successfully completed.
If you copy the statement and run it:
SQL> select Q1 AS "meaning1", Q2 AS "meaning2", Q3 AS "meaning3" from dataTable;
meaning1 meaning2 meaning3
---------- ---------- ----------
1 2 3
4 5 6
SQL>
This way you have your query, but you can not fetch it, because it still has variable columns.
You can easily edit this code to make it build a query that returns strings, composed by concatenating the felds; this way you will always have a single field, but it's different from what you asked:
SQL> select 'meaning1, meaning2, meaning3' from dual
2 union all
3 select Q1 || ',' || Q2 || ',' || Q3 from dataTable;
'MEANING1,MEANING2,MEANING3'
--------------------------------------------------------------------------------
meaning1, meaning2, meaning3
1,2,3
4,5,6

Related

List all tables,numrows,numcol and primary key for table if any in oracle

I want to QUERY to generate a table like below:
Tablename||noofrows||noofcolumns||PRIMARKEYCOL(IF ANY for the table)
xyz. 590. 11. xyz_id
bcd. 934 15 null
...
...
So far I was able to do this until now in 2 query:
Query 1:
select a.table_name,count_rows(a.table_name) total_rows,count(b.column_name) total_cols from user_tables a,
,user_tab_columns b
where a.table_name =b.table_name
and a.table_name not like('amp%')
group by a.table_name;
note:Count_rows() is function to calculate rows as stats are not up to date
query 2:
select b.table_name b.column_name PRIMKEY_COL FROM user_constraints a,user_cons_columns b
where
a.constraint_type = 'P'
and a.constraint_name=b.constraint_name
and a.table_name=b.table_name
and b.table_name not like ('amp%');
Problem
I need to merge this table to one query (as shown in example above) so that I can represent the data in one table. My issue in clubbing the table is, with joins and how to make sure table without any primary keys are represent because if I just directly give constraint type ='p' in the where clause of the join I see that it only shows table with Primarykeys I am not able to figure this out.
Primary key can have more than just a single column, so - if you'd want to return them all, you'd either have to "aggregate" them, somehow (listagg looks like a natural choice, but - in 12c - you can't fetch distinct list of columns which you might need because of joining those tables duplicates appear).
As you already use a function to return number of rows (you'd probably rather regularly collect statistics, though, but yes - if tables are frequently modified (inserts and deletes), counting on-the-fly is the way to go.
So, alternative approach with 2 functions and 1 simple query. Yes, I know - context switching and stuff, but - see if it does any good.
This is what you already have (see if your and my code look similar):
SQL> create or replace function count_rows (par_table_name in varchar2)
2 return number
3 is
4 -- return number of rows in PAR_TABLE_NAME
5 retval number;
6 begin
7 execute immediate 'select count(*) from ' ||
8 dbms_assert.sql_object_name(par_table_name) into retval;
9 return retval;
10 end;
11 /
Function created.
This is new (to simplify fetching distinct list of primary key columns):
SQL> create or replace function pk_cols (par_table_name in varchar2)
2 return varchar2
3 is
4 -- return list of primary key columns for PAR_TABLE_NAME, sorted by column's
5 -- position within the primary key constraint
6 retval varchar2(100);
7 begin
8 select listagg(b.column_name, ', ') within group (order by b.position)
9 into retval
10 from user_constraints a join user_cons_columns b on b.constraint_name = a.constraint_name
11 where upper(a.table_name) = dbms_assert.sql_object_name(upper(par_table_name))
12 and a.constraint_type = 'P';
13 return retval;
14 end;
15 /
Function created.
Finally, that simple query I mentioned:
SQL> select a.table_name,
2 count_rows(a.table_name) total_rows,
3 max(a.column_id) total_cols,
4 pk_cols(a.table_name) primkey_cols
5 from user_tab_columns a
6 group by a.table_name;
TABLE_NAME TOTAL_ROWS TOTAL_COLS PRIMKEY_COLS
--------------- ---------- ---------- -------------------------
DEPT 4 3 DEPTNO
SO_TEST 0 1
TEST 7 3 ORD, TERMREGIONAL
EMP 14 8 EMPNO
TRAINING 0 5 TRAINING_PLACE_ID
TEMP_SE 10 3
<snip>
Just join the two queries:
select tab_cols.*, tab_keys.* from
(select a.table_name,count_rows(a.table_name) total_rows,count(b.column_name) total_cols from user_tables a,
,user_tab_columns b
where a.table_name =b.table_name
and a.table_name not like('amp%')
group by a.table_name) tab_cols,
(select b.table_name b.column_name PRIMKEY_COL FROM user_constraints a,user_cons_columns b
where
a.constraint_type = 'P'
and a.constraint_name=b.constraint_name
and a.table_name=b.table_name
and b.table_name not like ('amp%')) tab_keys
where tab_cols.table_name = tab_keys.table_name(+);

How to convert string value returned from oracle apex 20.1 multiselect item into comma separated numbers array

I have a multi select enabled select list. I want to use all the selected ids inside an IN () operator in pl/sql query. Selected values are returned as below,
"1","5","4"
I want to use em as numbers as below,
1,5,4
My query is like,
UPDATE EMPLOYEE SET EMPSTAT = 'Active' WHERE EMPID IN (:P500_EMPIDS);
This is the employee table:
SQL> select * from employee;
EMPID EMPSTAT
---------- --------
1 Inactive
2 Inactive
4 Inactive
5 Inactive
SQL>
This is a way to split comma-separated values into rows (not into a list of values you'd use in IN!). Note that:
line #3: REPLACE function replaces double quotes with an empty string
line #3: then it is split into rows using REGEXP_SUBSTR with help of hierarchical query
SQL> with test (col) as
2 (select '"1","5","4"' from dual)
3 select regexp_substr(replace(col, '"', ''), '[^,]+', 1, level) val
4 from test
5 connect by level <= regexp_count(col, ',') + 1;
VAL
--------------------
1
5
4
SQL>
Usually multiselect items have colon-separated values, e.g. 1:5:4. If that's really the case, regular expression would look like this:
regexp_substr(col, '[^:]+', 1, level) val
Use it in Apex as:
update employee e set
e.empstat = 'Active'
where e.empid in
(select regexp_substr(replace(:P1_ITEM, '"', ''), '[^,]+', 1, level)
from dual
connect by level <= regexp_count(:P1_ITEM, ',') + 1
);
Result is:
3 rows updated.
SQL> select * from employee order by empid;
EMPID EMPSTAT
---------- --------
1 Active
2 Inactive
4 Active
5 Active
SQL>
Try it.
Thanks for helping everyone.Please check this and tell me if anything is wrong. I found a solution as below,
DECLARE
l_selected APEX_APPLICATION_GLOBAL.VC_ARR2;
BEGIN
l_selected := APEX_UTIL.STRING_TO_TABLE(:P500_EMPIDS);
FOR i in 1 .. l_selected.count LOOP
UPDATE EMPLYEE SET EMPSTATUS = 'ACTIVE' WHERE EMPID = to_number(l_selected(i));
END LOOP;
END;
You can use the API apex_string for this. If you want to use the IN operator you'll have to use EXECUTE IMMEDIATE because you cannot use a concatenated string in an IN operator.
Instead what you could do is the following:
DECLARE
l_array apex_t_varchar2;
BEGIN
l_array := apex_string.split(p_str => :P500_EMPIDS, p_sep => ':');
FOR i IN 1..l_array.count LOOP
UPDATE EMPLOYEE SET EMPSTAT = 'Active' WHERE EMPID = l_array(i);
END LOOP;
END;
Explanation: convert the colon separated list of ids to a table of varchar2, then loop through the elements of that table.
Note that I'm using ":" as a separator, that is what apex uses for multi selects. If you need "," then change code above accordingly.
Note that you can use apex_string directly within an update statement, so the answer of Koen Lostrie could be modified to not need a loop:
UPDATE EMPLOYEE
SET EMPSTAT = 'Active'
WHERE EMPID IN (
select to_number(trim('"' from column_value))
from table(apex_string.split(:P500_EMPIDS,','))
);
Testcase:
with cte1 as (
select '"1","2","3"' as x from dual
)
select to_number(trim('"' from column_value))
from table(apex_string.split((select x from cte1),','))

Cursor with multiple queries in oracle not compiling

I have created a cursor that has two queries joined with inner join, but query is not compiling their is error at the end of first query but the same query is getting executed without cursor.
cursor data is
select * from
select rid,id, order from table1
inner join
select pid, name, order from table2
on table1.order = table2.order
original query is much bigger and complicated but end result would be this.
Their are compilation errors at the end of first query and those are generic nature, I guess may be syntax for creating a two joined queries is wrong (this is a wild guess though)
Error:
SQL statement ignored //at select word of first query
Missing right parenthesis //at the last word of first query
Example based on Scott's schema:
SELECT should contain column aliases if columns returned by those inline views share the same name; otherwise, you won't know which one you're using
inline views should have their own aliases; basically, that's always a good idea - prefix columns with table aliases, otherwise you'll soon forget which column belongs to which table
SQL> declare
2 cursor data is
3 select a.empno a_empno, b.ename b_ename
4 from (select empno, ename, deptno from emp) a
5 inner join
6 (select empno, ename, deptno from emp) b
7 on a.deptno = b.deptno
8 where rownum < 5;
9 begin
10 for data_r in data loop
11 dbms_output.put_line(data_r.b_ename);
12 end loop;
13 end;
14 /
SMITH
JONES
SCOTT
ADAMS
PL/SQL procedure successfully completed.
SQL>
You have to put your subqueries in parenthesis and add aliases for the subqueries:
cursor data is
select * from
(select rid,id, order from table1) table1
inner join
(select pid, name, order from table2) table2
on table1.order = table2.order
Here is another answer for you, with just small differences and with an example:
CREATE OR REPLACE PROCEDURE p_test(n_test in number)
AS
CURSOR data
IS
SELECT *
FROM
(SELECT rid
, id
, "order" or1
FROM table1) tab1
INNER JOIN
(SELECT pid
, name
, "order" or1
FROM table2 ) tab2
ON tab1.or1 = tab2.or1;
BEGIN
FOR data_i IN data LOOP
DBMS_OUTPUT.PUT_LINE(data_i.rid);
END LOOP;
END p_test;
Here is the DEMO

How can I return multiple identical rows based on a quantity field in the row itself?

I'm using oracle to output line items in from a shopping app. Each item has a quantity field that may be greater than 1 and if it is, I'd like to return that row N times.
Here's what I'm talking about for a table
product_id, quanity
1, 3,
2, 5
And I'm looking a query that would return
1,3
1,3
1,3
2,5
2,5
2,5
2,5
2,5
Is this possible? I saw this answer for SQL Server 2005 and I'm looking for almost the exact thing in oracle. Building a dedicated numbers table is unfortunately not an option.
I've used 15 as a maximum for the example, but you should set it to 9999 or whatever the maximum quantity you will support.
create table t (product_id number, quantity number);
insert into t values (1,3);
insert into t values (2,5);
select t.*
from t
join (select rownum rn from dual connect by level < 15) a
on a.rn <= t.quantity
order by 1;
First create sample data:
create table my_table (product_id number , quantity number);
insert into my_table(product_id, quantity) values(1,3);
insert into my_table(product_id, quantity) values(2,5);
And now run this SQL:
SELECT product_id, quantity
FROM my_table tproducts
,( SELECT LEVEL AS lvl
FROM dual
CONNECT BY LEVEL <= (SELECT MAX(quantity) FROM my_table)) tbl_sub
WHERE tbl_sub.lvl BETWEEN 1 AND tproducts.quantity
ORDER BY product_id, lvl;
PRODUCT_ID QUANTITY
---------- ----------
1 3
1 3
1 3
2 5
2 5
2 5
2 5
2 5
This question is propably same as this: how to calc ranges in oracle
Update solution, for Oracle 9i:
You can use pipelined_function() like this:
CREATE TYPE SampleType AS OBJECT
(
product_id number,
quantity varchar2(2000)
)
/
CREATE TYPE SampleTypeSet AS TABLE OF SampleType
/
CREATE OR REPLACE FUNCTION GET_DATA RETURN SampleTypeSet
PIPELINED
IS
l_one_row SampleType := SampleType(NULL, NULL);
BEGIN
FOR cur_data IN (SELECT product_id, quantity FROM my_table ORDER BY product_id) LOOP
FOR i IN 1..cur_data.quantity LOOP
l_one_row.product_id := cur_data.product_id;
l_one_row.quantity := cur_data.quantity;
PIPE ROW(l_one_row);
END LOOP;
END LOOP;
RETURN;
END GET_DATA;
/
Now you can do this:
SELECT * FROM TABLE(GET_DATA());
Or this:
CREATE OR REPLACE VIEW VIEW_ALL_DATA AS SELECT * FROM TABLE(GET_DATA());
SELECT * FROM VIEW_ALL_DATA;
Both with same results.
(Based on my article pipelined function)

Validating Column Data Stored as CSV Against Another Table

I wanted to see what some suggested approaches would be to validate a field that is stored as a CSV against a table containing appropriate values. Althought it would be desired, it is NOT an option to split the CSV list into another related table. In the example data below I would be trying to capture the code 99 for widget A.
Below is an example data representation.
Table: Widgets
WidgetName WidgetCodeList
A 1, 2, 3
B 1
C 2, 3
D 99
Table: WidgetCodes
WidgetCode
1
2
3
An earlier approach was to query the CSV column as rows using various string manipulations and CONNECT_BY_LEVEL however the performance was not acceptible.
You could try a pipelined function (here with a lateral join):
SQL> WITH widgets AS (
2 SELECT 'A' WidgetName, '1, 2, 3' WidgetCodeList FROM dual
3 UNION ALL SELECT 'B', '1' FROM DUAL
4 UNION ALL SELECT 'C', '2, 3' FROM DUAL
5 UNION ALL SELECT 'D', '99' FROM DUAL
6 ), widgetcodes AS (
7 SELECT ROWNUM widgetcode from dual CONNECT BY LEVEL <= 3
8 )
9 SELECT w.widgetname,
10 to_number(s.column_value) missing_widget
11 FROM widgets w
12 CROSS JOIN TABLE(demo_pkg.string_to_tab(w.WidgetCodeList)) s
13 WHERE NOT EXISTS (SELECT NULL
14 FROM widgetcodes ws
15 WHERE ws.widgetcode = to_number(s.column_value));
WIDGETNAME MISSING_WIDGET
---------- --------------
D 99
See this other SO for an example of a pipelined function that converts a character string to a table.
In PL/SQL, you could make use of the Apex utility for converting a delimited string to a PL/SQL collection like this:
procedure validate_csv (p_csv varchar2)
is
v_array apex_application_global.vc_arr2;
v_dummy varchar2(1);
begin
v_array := apex_util.string_to_table(p_csv, ', ');
for i in 1..v_array.count
loop
begin
select null
into v_dummy
from widgetcodes
where widgetcode = v_array(i);
exception
when no_data_found then
raise_application_error('Invalid widget code: '||v_array(i));
end;
end loop;
end;

Resources