Correctly use collection method - oracle

CREATE OR REPLACE TYPE nvarchar2_list_type AS TABLE OF NVARCHAR2(100);
CREATE TABLE test_table(
id number primary key,
cars_list nvarchar2_list_type
)
NESTED TABLE cars_list STORE AS cars_list_storage_table;
insert into test_table(id, cars_list)
values(1, nvarchar2_list_type( 'AUDI', 'MERCEDES') );
All above operations completed success, 1 rows inserted in table test_table, now i write this function:
create or replace function get_cnt
return number
as
ret_val number;
begin
SELECT cars_list.COUNT
INTO ret_val
from test_table where id = 1;
return ret_val;
end;
This gives error: ORA-00904: "CARS_LIST"."COUNT": invalid identifier
Tell please what is wrong here?
As I know, COUNT method must be used just so (from here)

No, you cannot use count method in this situation. You have SQL nested table at hand, count method is used only with PL/SQL collections.
To count number of nested table's elements you can either unnest that nested table or use scalar sub-query:
Unnesting:
SQL> select id
2 , count(*) as cnt
3 from test_table t
4 cross join table(t.cars_list)
5 group by id
6 ;
ID CNT
---------- ----------
1 2
Scalar sub-query:
SQL> select id
2 , (select count(column_value)
3 from table(t.cars_list)) as cnt
4 from test_table t
5 ;
ID CNT
---------- ----------
1 2

Use
Select
Cardinality(cars_list) from test_table

I can't explain why that doesn't work, but this does:
select (select count(*) from table(cars_list))
into ret_val
from test_table
where id = 1;

Oracle is expecting column name or function in its select list, but what you are giving is collection build in method that operates on collections only.
You can achieve the same using scalar sub query
SELECT (select count(1) from table(cars_list)) as "COUNT"
FROM test_table
WHERE id = 1;

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(+);

PL/SQL select the minimum value of a vector

I have a table that looks like this :
CREATE OR REPLACE TYPE tip_orase AS VARRAY(10) of VARCHAR2(50)
/
CREATE table excursie_try (
cod_excursie NUMBER(4),
denumire VARCHAR2(20),
orase tip_orase,
status varchar2(20)
);
And i need to find out 'cod_excursie' of the entry that has in orase the lowest number of entries.
I can do this with a lot of work by counting for each entry the number of cities and selecting a minimum. Then making a query to give 'cod_excursie' of the entry that has the lowest number of entries in orase.
Is there a simpler way ? I tried something like:
select cod_excursie
from excursie_try, (select max(orase.count()) m
from excursie_try) T
where orase.count = T.m
and ROWNUM <= 1;
but it does not work. Any ideas or i have to take the LONG way ?
Try this:
select cod_excursie from (
select et.cod_excursie,
(select count(*) from table(et.orase)) n
from excursie_try et order by 2 desc
) where rownum = 1;
(select count(*) from table(et.orase)) is a single-row subquery, I used TABLE to emulate sql table on varray.
order by 2 desc in the subquery + where rownum = 1 is used for top-N reporting.

Oracle INSERT INTO SELECT(...) DUP_VAL_ON_INDEX exception behavior

I have a stored procedure which looks like this:
BEGIN
INSERT INTO result_table
(SELECT (...) FROM query_table);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
NULL;
END;
I'm doing it in a loop which passes multiple parameters to the SELECT statement and in some cases some of the values might duplicate that is why I have to catch the DUP_VAL_ON_INDEX exception.
My question is that if the SELECT statement returns more rows and only one from them exists already in *result_table*, f. ex.
1 'A'
2 'B'
3 'C'
And first row (1 'A') would already be in the table, would other rows which don't exist (second and third from case above) be inserted? Or none of them would be inserted at all?
I'm afraid that none of them would be inserted (and my test case partially confirms that)... If so, what option do I have to achieve desired bahavior? Is there a good way to insert the rows that don't violate the primary key using the construction above?
You can use the MERGE statement. Insert the records if they don't exist and do nothing if they already exist.
http://psoug.org/reference/merge.html
You are right, if one record violates constraint, none will be inserted.
I'd do
INSERT INTO result_table
(SELECT (...) FROM query_table a WHERE NOT EXISTS
(SELECT NULL FROM result_table b WHERE b.b_unique_key = a.b_unique_key)
)
Another option is to use error logging
INSERT INTO result_table
SELECT ... FROM query_table
LOG ERRORS INTO err$_dest ('INSERT') REJECT LIMIT UNLIMITED;
Note: you have to create error table prior to run this query.
If you're using 11g, then you can use the ignore_row_on_dupkey_index hint to suppress the errors:
create table tab (id integer);
alter table tab add constraint tab_pk primary key (id);
insert into tab
select rownum from dual connect by level <= 1;
1 rows inserted.
ID
----------
1
SELECT * FROM tab;
insert into tab
select rownum from dual connect by level <= 3;
SQL Error: ORA-00001: unique constraint (CAM_OWNER.TAB_PK) violated
insert /*+ ignore_row_on_dupkey_index(tab, tab_pk) */into tab
select rownum from dual connect by level <= 3;
SELECT * FROM tab;
2 rows inserted.
ID
----------
1
2
3

Dynamic table name in pl/sql

I need to select columns from a table Table_A, However there is another table which has the same schema Table_B. The query should determine the from table dynamically. For ex. if Table_A has more rows then use Table_A else use Table_B.
Query something like this
select employee, salary, id from (condition to count rows and select the table )table;
Is this possible without using cursors and EXECUTE IMMEDIATE??.
Normally, you would use dynamic SQL for this sort of thing. That would involve either using the DBMS_SQL package, EXECUTE IMMEDIATE or doing an OPEN <<cursor>> FOR <<SQL statement string>>.
If you really want to use static SQL, you could query both tables and only return one set of results. I cannot envision a situation where this would really make sense but you can certainly do it
Create a FOO and a FOO2 table. FOO2 has two rows to the one row from FOO
SQL> create table foo( col1 number );
Table created.
SQL> create table foo2( col1 number );
Table created.
SQL> insert into foo values( 1 );
1 row created.
SQL> insert into foo2 values( 1 );
1 row created.
SQL> insert into foo2 values( 2 );
1 row created.
Run the query. This will return all the data from FOO2
SQL> ed
Wrote file afiedt.buf
1 select col1
2 from (select the_union.*,
3 max(cnt) over () max_cnt
4 from (select col1, count(*) over () cnt from foo
5 union all
6 select col1, count(*) over () from foo2) the_union)
7* where cnt = max_cnt
SQL> /
COL1
----------
1
2
Insert more rows into FOO. Now the same query will return all the data from FOO
SQL> insert into foo values( 3 );
1 row created.
SQL> insert into foo values( 5 );
1 row created.
SQL> commit;
Commit complete.
SQL> select col1
2 from (select the_union.*,
3 max(cnt) over () max_cnt
4 from (select col1, count(*) over () cnt from foo
5 union all
6 select col1, count(*) over () from foo2) the_union)
7 where cnt = max_cnt;
COL1
----------
1
3
5
As I said, though, I cannot fathom a situation where it would actually make sense to do this.
I am completely guessing at what you are really trying to do, but I am thinking you want to use a synonym. I am guessing that there is some sort of event that triggers when you should be using TableA vs. TableB. Create a synonym "my_table" pointing to TableA and have your view select from "my_table". Then whenever you want your view to point to TableB instead, you just switch your synonym to point to TableB, and you don't have to do anything to the view.
For more info, read about synonyms in the Concepts Guide.

Oracle: how to INSERT if a row doesn't exist

What is the easiest way to INSERT a row if it doesn't exist, in PL/SQL (oracle)?
I want something like:
IF NOT EXISTS (SELECT * FROM table WHERE name = 'jonny') THEN
INSERT INTO table VALUES ("jonny", null);
END IF;
But it's not working.
Note: this table has 2 fields, say, name and age. But only name is PK.
INSERT INTO table
SELECT 'jonny', NULL
FROM dual -- Not Oracle? No need for dual, drop that line
WHERE NOT EXISTS (SELECT NULL -- canonical way, but you can select
-- anything as EXISTS only checks existence
FROM table
WHERE name = 'jonny'
)
Assuming you are on 10g, you can also use the MERGE statement. This allows you to insert the row if it doesn't exist and ignore the row if it does exist. People tend to think of MERGE when they want to do an "upsert" (INSERT if the row doesn't exist and UPDATE if the row does exist) but the UPDATE part is optional now so it can also be used here.
SQL> create table foo (
2 name varchar2(10) primary key,
3 age number
4 );
Table created.
SQL> ed
Wrote file afiedt.buf
1 merge into foo a
2 using (select 'johnny' name, null age from dual) b
3 on (a.name = b.name)
4 when not matched then
5 insert( name, age)
6* values( b.name, b.age)
SQL> /
1 row merged.
SQL> /
0 rows merged.
SQL> select * from foo;
NAME AGE
---------- ----------
johnny
If name is a PK, then just insert and catch the error. The reason to do this rather than any check is that it will work even with multiple clients inserting at the same time. If you check and then insert, you have to hold a lock during that time, or expect the error anyway.
The code for this would be something like
BEGIN
INSERT INTO table( name, age )
VALUES( 'johnny', null );
EXCEPTION
WHEN dup_val_on_index
THEN
NULL; -- Intentionally ignore duplicates
END;
I found the examples a bit tricky to follow for the situation where you want to ensure a row exists in the destination table (especially when you have two columns as the primary key), but the primary key might not exist there at all so there's nothing to select.
This is what worked for me:
MERGE INTO table1 D
USING (
-- These are the row(s) you want to insert.
SELECT
'val1' AS FIELD_A,
'val2' AS FIELD_B
FROM DUAL
) S ON (
-- This is the criteria to find the above row(s) in the
-- destination table. S refers to the rows in the SELECT
-- statement above, D refers to the destination table.
D.FIELD_A = S.FIELD_A
AND D.FIELD_B = S.FIELD_B
)
-- This is the INSERT statement to run for each row that
-- doesn't exist in the destination table.
WHEN NOT MATCHED THEN INSERT (
FIELD_A,
FIELD_B,
FIELD_C
) VALUES (
S.FIELD_A,
S.FIELD_B,
'val3'
)
The key points are:
The SELECT statement inside the USING block must always return rows. If there are no rows returned from this query, no rows will be inserted or updated. Here I select from DUAL so there will always be exactly one row.
The ON condition is what sets the criteria for matching rows. If ON does not have a match then the INSERT statement is run.
You can also add a WHEN MATCHED THEN UPDATE clause if you want more control over the updates too.
Using parts of #benoit answer, I will use this:
DECLARE
varTmp NUMBER:=0;
BEGIN
-- checks
SELECT nvl((SELECT 1 FROM table WHERE name = 'john'), 0) INTO varTmp FROM dual;
-- insert
IF (varTmp = 1) THEN
INSERT INTO table (john, null)
END IF;
END;
Sorry for I don't use any full given answer, but I need IF check because my code is much more complex than this example table with name and age fields. I need a very clear code. Well thanks, I learned a lot! I'll accept #benoit answer.
In addition to the perfect and valid answers given so far, there is also the ignore_row_on_dupkey_index hint you might want to use:
create table tq84_a (
name varchar2 (20) primary key,
age number
);
insert /*+ ignore_row_on_dupkey_index(tq84_a(name)) */ into tq84_a values ('Johnny', 77);
insert /*+ ignore_row_on_dupkey_index(tq84_a(name)) */ into tq84_a values ('Pete' , 28);
insert /*+ ignore_row_on_dupkey_index(tq84_a(name)) */ into tq84_a values ('Sue' , 35);
insert /*+ ignore_row_on_dupkey_index(tq84_a(name)) */ into tq84_a values ('Johnny', null);
select * from tq84_a;
The hint is described on Tahiti.
you can use this syntax:
INSERT INTO table_name ( name, age )
select 'jonny', 18 from dual
where not exists(select 1 from table_name where name = 'jonny');
if its open an pop for asking as "enter substitution variable" then use this before the above queries:
set define off;
INSERT INTO table_name ( name, age )
select 'jonny', 18 from dual
where not exists(select 1 from table_name where name = 'jonny');
You should use Merge:
For example:
MERGE INTO employees e
USING (SELECT * FROM hr_records WHERE start_date > ADD_MONTHS(SYSDATE, -1)) h
ON (e.id = h.emp_id)
WHEN MATCHED THEN
UPDATE SET e.address = h.address
WHEN NOT MATCHED THEN
INSERT (id, address)
VALUES (h.emp_id, h.address);
or
MERGE INTO employees e
USING hr_records h
ON (e.id = h.emp_id)
WHEN MATCHED THEN
UPDATE SET e.address = h.address
WHEN NOT MATCHED THEN
INSERT (id, address)
VALUES (h.emp_id, h.address);
https://oracle-base.com/articles/9i/merge-statement
CTE and only CTE :-)
just throw out extra stuff. Here is almost complete and verbose form for all cases of life. And you can use any concise form.
INSERT INTO reports r
(r.id, r.name, r.key, r.param)
--
-- Invoke this script from "WITH" to the end (";")
-- to debug and see prepared values.
WITH
-- Some new data to add.
newData AS(
SELECT 'Name 1' name, 'key_new_1' key FROM DUAL
UNION SELECT 'Name 2' NAME, 'key_new_2' key FROM DUAL
UNION SELECT 'Name 3' NAME, 'key_new_3' key FROM DUAL
),
-- Any single row for copying with each new row from "newData",
-- if you will of course.
copyData AS(
SELECT r.*
FROM reports r
WHERE r.key = 'key_existing'
-- ! Prevent more than one row to return.
AND FALSE -- do something here for than!
),
-- Last used ID from the "reports" table (it depends on your case).
-- (not going to work with concurrent transactions)
maxId AS (SELECT MAX(id) AS id FROM reports),
--
-- Some construction of all data for insertion.
SELECT maxId.id + ROWNUM, newData.name, newData.key, copyData.param
FROM copyData
-- matrix multiplication :)
-- (or a recursion if you're imperative coder)
CROSS JOIN newData
CROSS JOIN maxId
--
-- Let's prevent re-insertion.
WHERE NOT EXISTS (
SELECT 1 FROM reports rs
WHERE rs.name IN(
SELECT name FROM newData
));
I call it "IF NOT EXISTS" on steroids. So, this helps me and I mostly do so.

Resources