Oracle Nested Table predicate in where clause - sql

I have a table that supposed to be searched with multiple columns, which can have multiple values
create table t as select * from all_objects;
create bitmap index IDX_DATA_OBJECT_ID on T (DATA_OBJECT_ID);
create bitmap index IDX_LAST_DDL_TIME on T (LAST_DDL_TIME);
create bitmap index IDX_OBJECT_NAME on T (OBJECT_NAME);
create bitmap index IDX_OBJECT_TYPE on T (OBJECT_TYPE);
create or replace type strarray as table of varchar2(4000)
CREATE OR REPLACE PROCEDURE p_search(op_cursor out SYS_REFCURSOR
,a strarray
,b strarray) IS
ca constant number:= a.count;
cb constant number:= b.count;
BEGIN
OPEN op_cursor FOR
SELECT /*+ gather_plan_statistics asda*/ *
FROM t
WHERE object_name IN (SELECT * FROM TABLE(a))
AND object_type IN (SELECT * FROM TABLE(b));
END;
declare
op_cursor sys_refcursor;
c t%rowtype;
begin
p_search(op_cursor,strarray('ICOL$'),strarray('TABLE'));
loop
fetch op_cursor into c;
exit when op_cursor%notfound;
end loop;
end;
-----------------------------------------------------------------
| Id | Operation | Name |
-----------------------------------------------------------------
| 0 | SELECT STATEMENT | |
|* 1 | HASH JOIN SEMI | |
| 2 | NESTED LOOPS | |
| 3 | NESTED LOOPS | |
| 4 | SORT UNIQUE | |
| 5 | COLLECTION ITERATOR PICKLER FETCH| |
| 6 | BITMAP CONVERSION TO ROWIDS | |
|* 7 | BITMAP INDEX SINGLE VALUE | IDX_OBJECT_NAME |
| 8 | TABLE ACCESS BY INDEX ROWID | T |
| 9 | COLLECTION ITERATOR PICKLER FETCH | |
-----------------------------------------------------------------
It looks fine to me as it does index lookup on more selective column.
But I also have a requirement to search for all values if argument is not passed and I am really stuck with that.
The main question I think is how to write sql to search a table by multiple columns with multiple possible values in these columns? I want to be able to take advantage of bitmap indexes.
Should I maybe stick to Dynamic SQL for such task?
UPDATE.
That is how I currently solved it for the moment.
create context my_ctx using p_search;
CREATE OR REPLACE FUNCTION in_list(p_string IN VARCHAR2) RETURN strarray AS
l_string LONG DEFAULT p_string || ',';
l_data strarray := strarray();
n NUMBER;
BEGIN
LOOP
EXIT WHEN l_string IS NULL;
n := instr(l_string, ',');
l_data.extend;
l_data(l_data.count) := ltrim(rtrim(substr(l_string, 1, n - 1)));
l_string := substr(l_string, n + 1);
END LOOP;
RETURN l_data;
END;
CREATE OR REPLACE PROCEDURE p_search(op_cursor OUT SYS_REFCURSOR
,a VARCHAR2
,b VARCHAR2) IS
l VARCHAR2(4000);
BEGIN
l := 'SELECT /*+ gather_plan_statistics lvv3*/
*
FROM t
WHERE 1=1';
IF a IS NOT NULL
THEN
dbms_session.set_context('MY_CTX', 'OBJ_NAME', a);
l := l || ' and t.object_name in (select /*+ cardinality (objn 5)*/ * from table(cast(in_list(sys_context( ''MY_CTX'',''OBJ_NAME'' )) as strarray)
) objn
)';
END IF;
IF b IS NOT NULL
THEN
dbms_session.set_context('MY_CTX', 'OBJ_TYPE', b);
l := l || ' and t.object_type in (select /*+ cardinality (objt 5)*/ * from table(cast(in_list(sys_context( ''MY_CTX'',''OBJ_TYPE'' )) as strarray)
) objt
)';
END IF;
OPEN op_cursor FOR l;
dbms_session.clear_context('MY_CTX');
END;

Related

Query table A or table B based on some condition

in an Oracle DB (12) I have 2 tables:
table: STEP_DETAILS
+-----------+---------+---------------+
| record_id | step_id | material_type |
+===========+=========+===============+
| 1 | 1 | in |
+-----------+---------+---------------+
| 2 | 1 | in |
+-----------+---------+---------------+
| 3 | 1 | out |
+-----------+---------+---------------+
| 4 | 2 | in |
+-----------+---------+---------------+
| 5 | 2 | out |
+-----------+---------+---------------+
| 6 | 2 | out |
+-----------+---------+---------------+
table: ACTIONS_DETAILS
+-----------+-----------+---------------+
| record_id | action_id | material_type |
+===========+===========+===============+
| 1 | 11 | in |
+-----------+-----------+---------------+
| 2 | 11 | out |
+-----------+-----------+---------------+
| 3 | 12 | in |
+-----------+-----------+---------------+
| 4 | 12 | out |
+-----------+-----------+---------------+
all id-columns are of type INTEGER.
I need to count the input materials for both tables.
in a PL/SQL block I have the following functions, each has 'almost' the same query:
--count from step_details:
FUNCTION get_step_input_count(p_step_id step_details.step_id%TYPE)
RETURN INTEGER
IS
l_count INTEGER := 0;
BEGIN
SELECT COUNT(1)
INTO l_count
FROM step_details
WHERE step_id = p_step_id
AND material_type = 'in';
RETURN l_count;
END get_step_input_count;
--count from action_details:
FUNCTION get_action_input_count(p_action_id action_details.action_id%TYPE)
RETURN INTEGER
IS
l_count INTEGER := 0;
BEGIN
SELECT COUNT(1)
INTO l_count
FROM action_details
WHERE action_id = p_action_id
AND material_type = 'in';
RETURN l_count;
END get_action_input_count;
is it possible to write one single SELECT-statement that can query one of the 2 tables each time based on some condition, so I will eventually write one function that uses one query instead of 2 functions, something like:
FUNCTION get_input_count(p_parent_id integer,
p_from varchar2)
RETURN INTEGER
IS
l_count INTEGER := 0;
BEGIN
SELECT COUNT(1)
INTO l_count
FROM (when p_from = 'S' then 'step_details'
when p_from = 'A' then 'action_details')
WHERE (when p_from = 'S' then 'step_id = p_parent_id'
when p_from = 'A' then 'action_id = p_parent_id')
AND material_type = 'in';
RETURN l_count;
END get_input_count;
You can try with something like the following:
select sum(num_rows)
from
(
select count(*) as num_rows
from tab1 /* first table */
where :param = 1
union all
select count(*) as num_rows
from tab2 /* second table */
where :param = 2
)
Here you use a single select that wraps the UNION ALL of queries from all the possible tables; every table gives its contribution or not, depending on the value of some parameter, so that you only get the rows from the table you want, based on the parameter value.
One option would be using dynamic SQL composed of concatenations for table and column names, and a bind variable for the value which will be common(p_parent_id) for querying from each tables.
SQL> CREATE OR REPLACE FUNCTION get_input_count(p_parent_id INT, p_from VARCHAR2) RETURN INT IS
l_count INT;
crs SYS_REFCURSOR;
v_sql VARCHAR2(32767);
v_from VARCHAR2(32);
v_col VARCHAR2(99);
BEGIN
SELECT DECODE(p_from,'A','actions_details','S','step_details'),
DECODE(p_from,'A','action_id','S','step_id')
INTO v_from, v_col
FROM dual;
v_sql := 'SELECT COUNT(*)
FROM '||v_from||'
WHERE material_type = ''in'' AND '||v_col||' = :prt_id';
OPEN crs FOR v_sql USING p_parent_id;
LOOP
FETCH crs INTO l_count;
EXIT WHEN crs%NOTFOUND;
END LOOP;
CLOSE crs;
RETURN l_count;
END;
/
Demo
where
initializing the value of l_count variable with zero is redundant,
since the query will return zero without exception whenever no
matching records found
the keyword FUNCTION should be prepended by CREATE [OR REPLACE]
ending the stored function with its name is optional(might be
neglected)
You could also use dynamic SQL like below.
SQL injection should be cared in this case.
sql_statement := 'select count(*)';
IF table = 'xxx'
THEN
sql_statement := sql_statement || ' from xxx where material_type = ''in''';
ELSIF table = 'yyy'
sql_statement := sql_statement || ' from yyy where yyyy_type = ''in''';
END IF;
sql_statement := sql_statement blur blur;
EXECUTE IMMEDIATE sql_statement INTO l_count USING p_1;

Is there a way to TRIM all data in a SELECT * FROM statement?

I am trying to select and trim all the entries from a table using the following statement:
SELECT TRIM(*) FROM TABLE
But I get an error. Is there a way to return all entries selected so they are trimmed for blank characters at the beginning and end of each string?
You need to specify each string column by hand:
SELECT TRIM(col1), --LTRIM(RTRIM(...)) If RDBMS is SQL Server
TRIM(col2),
TRIM(col3),
TRIM(col4)
-- ...
FROM table
There is another problem with your proposal. * is placeholder for each column in table so there will be problem with trimming date/decimal/spatial data ....
Addendum
Using Oracle 18c Polymorphic Table Functions(provided code is just PoC, there is a space for a lot of improvements):
CREATE TABLE tab(id INT, d DATE,
v1 VARCHAR2(100), v2 VARCHAR2(100), v3 VARCHAR2(100) );
INSERT INTO tab(id, d,v1, v2, v3)
VALUES (1, SYSDATE, ' aaaa ', ' b ', ' c');
INSERT INTO tab(id, d,v1, v2, v3)
VALUES (2, SYSDATE+1, ' afasd', ' ', ' d');
COMMIT;
SELECT * FROM tab;
-- Output
.----.-----------.-----------.-----------.-----.
| ID | D | V1 | V2 | V3 |
:----+-----------+-----------+-----------+-----:
| 1 | 02-MAR-18 | aaaa | b | c |
:----+-----------+-----------+-----------+-----:
| 2 | 03-MAR-18 | afasd | | d |
'----'-----------'-----------'-----------'-----'
And table function:
CREATE OR REPLACE PACKAGE ptf AS
FUNCTION describe(tab IN OUT dbms_tf.table_t)RETURN dbms_tf.describe_t;
PROCEDURE FETCH_ROWS;
END ptf;
/
CREATE OR REPLACE PACKAGE BODY ptf AS
FUNCTION describe(tab IN OUT dbms_tf.table_t) RETURN dbms_tf.describe_t AS
new_cols DBMS_TF.COLUMNS_NEW_T;
BEGIN
FOR i IN 1 .. tab.column.count LOOP
IF tab.column(i).description.type IN ( dbms_tf.type_varchar2) THEN
tab.column(i).pass_through:=FALSE;
tab.column(i).for_read:= TRUE;
NEW_COLS(i) :=
DBMS_TF.COLUMN_METADATA_T(name=> tab.column(i).description.name,
type => tab.column(i).description.type);
END IF;
END LOOP;
RETURN DBMS_TF.describe_t(new_columns=>new_cols, row_replication=>true);
END;
PROCEDURE FETCH_ROWS AS
inp_rs DBMS_TF.row_set_t;
out_rs DBMS_TF.row_set_t;
rows PLS_INTEGER;
BEGIN
DBMS_TF.get_row_set(inp_rs, rows);
FOR c IN 1 .. inp_rs.count() LOOP
FOR r IN 1 .. rows LOOP
out_rs(c).tab_varchar2(r) := TRIM(inp_rs(c).tab_varchar2(r));
END LOOP;
END LOOP;
DBMS_TF.put_row_set(out_rs, replication_factor => 1);
END;
END ptf;
And final call:
CREATE OR REPLACE FUNCTION trim_col(tab TABLE)
RETURN TABLE pipelined row polymorphic USING ptf;
SELECT *
FROM trim_col(tab); -- passing table as table function argument
.----.-----------.-------.-----.----.
| ID | D | V1 | V2 | V3 |
:----+-----------+-------+-----+----:
| 1 | 02-MAR-18 | aaaa | b | c |
:----+-----------+-------+-----+----:
| 2 | 03-MAR-18 | afasd | - | d |
'----'-----------'-------'-----'----'
db<>fiddle demo

Update only dynamically chosen columns

I've created a function copy_rows_from_table(_tbl regclass) which copies records in a table and gives them a new primary key value. It returns a hstore containing pairs of old_id => new_id. For example for table books my function would create two additional records and return a hstore.
books initially:
id | title | price | author_id | publisher_id
----+---------------+-------+-----------+--------------
1 | The Cyberiad | 15.00 | 23 | 46
2 | The Trial | 10.00 | 12 | 67
books after evaluation of copy_rows_from_table('books'):
id | title | price | author_id | publisher_id
----+---------------+-------+-----------+--------------
1 | The Cyberiad | 15.00 | 23 | 46
2 | The Trial | 10.00 | 12 | 67
3 | The Cyberiad | 15.00 | 23 | 46
4 | The Trial | 10.00 | 12 | 67
returned hstore:
"1"=>"3", "2"=>"4"
It works fine. Now I would like to create a function that copies records from a few tables (passed in an array) and then updates all foreign keys using returned hstore. For example after copying books and authors I want author_id column to be updated in books table. After using my function on books, authors and publishers, if I had a hstore which contains "1"=>"3", "2"=>"4","23"=>"167","12"=>"98","46"=>"87","67"=>"102", my function should update books table in this way:
id | title | price | author_id | publisher_id
----+---------------+-------+-----------+--------------
1 | The Cyberiad | 15.00 | 23 | 46
2 | The Trial | 10.00 | 12 | 67
3 | The Cyberiad | 15.00 | 167 | 87
4 | The Trial | 10.00 | 98 | 102
I came up with something like this:
CREATE OR REPLACE FUNCTION copy_tables(_tbls regclass[])
RETURNS void AS
$func$
DECLARE
_tbl regclass;
_id_pairs hstore;
_table_id_pairs hstore;
_row record;
BEGIN
FOR _tbl IN SELECT _tbls
LOOP
EXECUTE format('SELECT copy_rows_from_table(''%1$s'')', _tbl)
INTO _table_id_pairs;
SELECT COALESCE(_id_pairs, hstore('')) || COALESCE(_table_id_pairs, hstore('')) INTO _id_pairs;
END LOOP;
FOR _tbl IN SELECT _tbls
LOOP
FOR _row IN EXECUTE format('SELECT * FROM %1$s WHERE id = ANY(''%2$s''::uuid[])', _tbl, avals(_id_pairs))
LOOP
EXECUTE (
SELECT format('UPDATE %1$s SET (%2$s) = (%3$s) WHERE id = %4$s'
, _tbl, string_agg(quote_ident(attname), ', '),
string_agg(COALESCE(_id_pairs -> ('_row.' || quote_ident(attname)), '_row.' || quote_ident(attname)), ', '), _row.id)
FROM pg_attribute
WHERE attrelid = _tbl
AND NOT attisdropped
AND attnum > 0
AND attname LIKE '%_id'
);
END LOOP;
END LOOP;
END
$func$
LANGUAGE plpgsql;
But it doesn't quite work. Is there any possibility to update records in a way I explained?
I have finally found a way to do it in PLpgSQL. I just iterate through every column of every record. Here is my working function:
CREATE OR REPLACE FUNCTION copy_tables(_tbls regclass[])
RETURNS void AS
$func$
DECLARE
_id_pairs hstore;
_table_id_pairs hstore;
_row record;
_hs_row record;
BEGIN
FOR I IN array_lower(_tbls, 1)..array_upper(_tbls, 1)
LOOP
EXECUTE format('SELECT copy_rows_from_table(''%1$s'')', _tbls[I])
INTO _table_id_pairs;
SELECT COALESCE(_id_pairs, hstore('')) || COALESCE(_table_id_pairs, hstore('')) INTO _id_pairs;
END LOOP;
FOR I IN array_lower(_tbls, 1)..array_upper(_tbls, 1)
LOOP
FOR _row IN EXECUTE format('SELECT * FROM %1$s WHERE id = ANY(''%2$s''::uuid[])', _tbls[I], avals(_id_pairs))
LOOP
FOR _hs_row IN SELECT kv."key", kv."value" FROM each(hstore(_row)) kv
LOOP
IF _hs_row."value" = ANY(akeys(_id_pairs)) THEN
EXECUTE format('UPDATE %1$s SET %2$s = ''%3$s'' WHERE id = ''%4$s''',
_tbls[I], _hs_row."key", _id_pairs -> _hs_row."value", _row.id);
END IF;
END LOOP;
END LOOP;
END LOOP;
END
$func$
LANGUAGE plpgsql;
PLpgSQL isn't good tool for dynamic updates. Probably there are some few others possibilities, but no one is trivial. The other ways:
Using some more dynamic PL language - PLPythonu, PLPerl
Using extension https://github.com/okbob/pltoolbox - there are functions record_get_fields and record_set_fields

Get all siblings in SQL tree

I have to handle a table PRODUCTS which is created to accommodate tree structure of products. It is done to handle situations when one product can contain several others (e.g. one package product holds several other positions). So, I'm making a function that takes OrderDetails, and it must iterate through all PRODUCTS and list out the child products for each product listed. I am facing an issue that I have to iterate through tree of unknown depth. Please, give me an idea how to do it.
I've implemented it in the table below with the function listed along with it. But in that solution the depth of listing is limited to 1, and what i want to do is to fetch all depth of the tree.
Here is the code:
CREATE OR REPLACE FUNCTION foo()RETURNS text AS
$body$
DECLARE _row RECORD;
_result text := '';
_child_row RECORD;
_count integer := -1;
_marker integer := 1;
BEGIN
FOR _row IN SELECT * FROM tree_products
LOOP
_result := _result || _marker || ' ' || _row.name;
_count := (SELECT count(product_id) FROM tree_products WHERE parent_id = _row.product_id);
IF _count > 0 THEN
FOR _child_row IN SELECT * FROM tree_products WHERE parent_id = _row.product_id
LOOP
_result := _result || ' ' || _child_row.name;
END LOOP;
END IF;
_marker := _marker =1;
END LOOP;
END;
$body$
LANGUAGE plpgsql
UPD Done this usign WITH CTE, but the groupiing problem occured:
CREATE OR REPLACE FUNCTION public.__foo (
)
RETURNS SETOF refcursor AS
$body$
DECLARE _returnvalue refcursor;
_q text;
BEGIN
_q :='
WITH RECURSIVE r_p (product_id, name, parent_id) AS -- 1
(SELECT t_p.product_id, t_p.name , t_p.parent_id -- 2
FROM tree_products t_p
WHERE t_p.product_id = 1
UNION ALL
SELECT t_c.product_id, t_c.name, t_c.parent_id -- 3
FROM r_p t_p, tree_products t_c
WHERE t_c.parent_id = t_p.product_id)
SELECT product_id, name, parent_id -- 4
FROM r_p;';
OPEN _returnvalue FOR EXECUTE (_q);
RETURN NEXT _returnvalue;
END
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 100 ROWS 1000;
I want to sibling products be under their respectiveparents, I wonder how to write Grouping statement...
UPD Sorry, the definition of the tree_products is the following:
CREATE TABLE public.tree_products (
product_id INTEGER DEFAULT nextval('ree_products_product_id_seq'::regclass) NOT NULL,
name VARCHAR,
parent_id INTEGER,
CONSTRAINT ree_products_pkey PRIMARY KEY(product_id)
)
WITH (oids = false);
UPD: SAMPLE OUTPUT:
product_id | name | parent_id
---------------------------------------
1 | promo | NULL
3 | fork | 1
4 | spoon | 1
6 | can | 1
10 | big can | 3
11 | small can | 4
12 | large spoon | 6
13 | mega fork | 3
14 | super duper | 6
DESIRED OUTPUT:
product_id | name | parent_id
---------------------------------------
1 | promo | NULL
3 | fork | 1
10 | big can | 3
13 | mega fork | 3
4 | spoon | 1
11 | small can | 4
6 | can | 1
12 | large spoon | 6
14 | super duper | 6
So, the fetched table has structure of the real tree, like the follwing:
- promo
- fork
- big can
- mega fork
- spoon
- small can
- can
- large can
- super duper
This SQLFiddle traverses the tree top-down, keeping an list of parent row numbers in an array, essentially a "parent row position list".
It then sorts the results by the parent-list.
WITH RECURSIVE tree(product_id, name, parentlist) AS (
SELECT product_id, name, ARRAY[ row_number() OVER (ORDER BY product_id) ]
FROM tree_products
WHERE parent_id IS NULL
UNION
SELECT tp.product_id, tp.name, array_append(parentlist, row_number() OVER (ORDER BY tp.product_id))
FROM tree_products tp
INNER JOIN tree t
ON (tp.parent_id = t.product_id)
)
SELECT *
FROM tree
ORDER BY parentlist;

How to create index on type object?

Currently i'm working with type object in oracle 11g. In this i have DB objects as follows:
table
CREATE TABLE students
(rollno NUMBER(15) primary key, s_Name VARCHAR2(20), Marks type_1
);
Type object specification is
CREATE OR REPLACE type type_1
AS
object
(
sub_1 NUMBER,
sub_2 NUMBER,
sub_3 NUMBER,
member FUNCTION total
RETURN NUMBER,
member FUNCTION e_result
RETURN VARCHAR2);
It's body
CREATE OR REPLACE type body type_1
AS
member FUNCTION total
RETURN NUMBER
IS
BEGIN
RETURN (sub_1+sub_2+sub_3);
END;
member FUNCTION e_result
RETURN VARCHAR2
IS
DECLARE
temp NUMBER;
BEGIN
temp :=sub_1+sub_2+sub_3;
IF(temp>50) THEN
RETURN ('pass');
ELSE
RETURN ('fail');
END IF;
END;
END;
After create all these thing even I've successfully populated the student table using
BEGIN
FOR i IN 1..800 LOOP
FOR j IN 1..400 LOOP
INSERT INTO students
VALUES (sequence1.NEXTVAL,
dbms_random.String('A', 5),
Type_1(Round(dbms_random.Value(10, 100)), Round(
dbms_random.Value(10, 100)), Round(dbms_random.Value(10, 100))));
END LOOP;
END LOOP;
dbms_output.Put_line('completed');
COMMIT;
END;
/
I need to create bitmap index on e_result column and normal index on tot column.
I have tried
create index id1 on students(marks.total)
and
create bitmap index bid1 on students(marks.e-result)
But I can't. What am I supposed to do?
Firstly you have to have your functions declared as DETERMINISTIC for them to be used in an SQL index (i.e. you have to assert to Oracle that given the same input, they give the same output).
eg:
SQL> CREATE OR REPLACE type type_1
2 AS
3 object
4 (
5 sub_1 NUMBER,
6 sub_2 NUMBER,
7 sub_3 NUMBER,
8 member FUNCTION total
9 RETURN NUMBER deterministic,
10 member FUNCTION e_result
11 RETURN VARCHAR2 deterministic
12 );
13 /
Type created.
SQL> CREATE OR REPLACE type body type_1
2 AS
3 member FUNCTION total
4 RETURN NUMBER deterministic
5 IS
6 BEGIN
7 RETURN (sub_1+sub_2+sub_3);
8 END;
9 member FUNCTION e_result
10 RETURN VARCHAR2 deterministic
11 IS
12 temp NUMBER;
13 BEGIN
14 temp :=sub_1+sub_2+sub_3;
15 IF(temp>50) THEN
16 RETURN ('pass');
17 ELSE
18 RETURN ('fail');
19 END IF;
20 END;
21 END;
22 /
secondly you have to use () when calling the function otherwise it will assume it a column named total:
SQL> create index id1 on students (marks.total());
Index created.
SQL> create bitmap index bid1 on students(marks.e_result());
Index created.
then you should see the indexes used:
SQL> exec dbms_stats.gather_table_stats(user, 'STUDENTS', method_opt=>'for all indexed columns size skewonly')
PL/SQL procedure successfully completed.
SQL> explain plan for select * from students s where s.marks.e_result() = 'fail';
Explained.
SQL> #explain ""
Plan hash value: 1595221732
-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 635 | 17780 | 109 (0)| 00:00:02 |
| 1 | TABLE ACCESS BY INDEX ROWID | STUDENTS | 635 | 17780 | 109 (0)| 00:00:02 |
| 2 | BITMAP CONVERSION TO ROWIDS| | | | | |
|* 3 | BITMAP INDEX SINGLE VALUE | BID1 | | | | |
-----------------------------------------------------------------------------------------