Can someone please modify the INSERT code below to use reject unlimited clause and log the errors in err$_t
I am running into syntax errors when I try to implement the following. Can the clause be used in this manner?
create table t(val1 NUMBER(4 ) );
ALTER TABLE t
ADD ( CONSTRAINT t_pk
PRIMARY KEY (val1));
exec dbms_errlog.create_error_log ( 't' );
INSERT INTO T(val1)
WITH cte AS
(
SELECT 1 from dual
UNION ALL
SELECT 1 from dual
)
SELECT * from cte;
Add the log errors clause at the end of the INSERT statement:
SQL> create table t(val1 number primary key);
Table created.
SQL> exec dbms_errlog.create_error_log ('t');
PL/SQL procedure successfully completed.
SQL> insert into t(val1)
2 with cte as
3 (
4 select 1 from dual
5 union all
6 select 1 from dual
7 )
8 select * from cte
9 log errors into err$_t ('TEST')
10 reject limit unlimited;
1 row created.
SQL> select count(*) rows_inserted from t;
ROWS_INSERTED
-------------
1
SQL> select count(*) rows_with_errors from err$_t;
ROWS_WITH_ERRORS
----------------
1
SQL>
Notice how I used used SQL*Plus to demonstrate the code. That program is not good for developing code, but it's helpful for creating simple, reproducible test cases. That tool is available to almost every Oracle developer, and it shows exactly how each command was run, including full error messages.
(One quick way to add syntax highlighting to your question is to select the code and click the "Code Sample" button, the two curly braces.)
Related
I am experimenting with a recursive CTE to split a string into multiple values. The data is then inserted into another table.
The following works in PostgreSQL:
CREATE TABLE test(data varchar(255));
WITH RECURSIVE
cte(genres) AS (SELECT 'apple,banana,cherry,date'),
split(genre,rest,genres) AS (
SELECT '', genres||',',genres FROM cte
UNION ALL
SELECT
substring(rest,0,position(',' IN rest)),
substring(rest,position(',' IN rest)+1),
genres
FROM split WHERE rest<>''
)
INSERT INTO test(data)
SELECT genre
FROM split;
However, the Oracle version:
CREATE TABLE test(data varchar(255));
WITH
cte(genres) AS (SELECT 'apple,banana,cherry,date' FROM dual),
split(genre,rest,genres) AS (
SELECT '', genres||',',genres FROM cte
UNION ALL
SELECT
substr(rest,1,instr(rest,',')-1),
substr(rest,instr(rest,',')+1),
genres
FROM split WHERE rest IS NOT NULL
)
INSERT INTO test(data)
SELECT genre
FROM split WHERE genre IS NOT NULL;
gives me the error message:
ORA-00928: missing SELECT keyword.
Now I’m pretty sure that I’ve got one, there between the INSERT and the following FROM.
If you comment out the INSERT, the rest of it will give the results. Elsewhere, I know that a simple INSERT … SELECT does work.
Is it something to do with the recursive CTE? How can I get this to work properly using a standard recursive CTE?
Using Oracle 18c in a Docker image. There is a fiddle here: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=d24f3105634933af1b7072bded1dacdf .
An INSERT-SELECT means "INSERT" command first, then the SELECT part, and the WITH is part of the SELECT not the insert.
SQL> create table t ( x int );
Table created.
SQL> with blah as ( select 1 c from dual)
2 insert into t
3 select * from blah;
insert into t
*
ERROR at line 2:
ORA-00928: missing SELECT keyword
SQL> insert into t
2 with blah as ( select 1 c from dual)
3 select * from blah;
1 row created.
The standard PIVOT syntax uses a static FOR list:
SELECT *
FROM (
SELECT log_id, event_id, event_time
FROM patient_events
WHERE event_id IN (10,20,30,40,50)
) v
PIVOT (
max(event_time) event_time
FOR event_id IN( 10,20,30,40,50 )
)
Is there a way to make this dynamic?
I know the sub-select in the WHERE clause will work, but can I use one in the FOR?
SELECT *
FROM (
SELECT log_id, event_id, event_time
FROM patient_events
WHERE event_id IN ( sub-select to generate list of IDs )
) v
PIVOT (
max(event_time) event_time
FOR event_id IN( sub-select to generate list of IDs )
)
You can't in pure SQL, but I don't think quite because of the reason suggested - it's not that the IN clause needs to be ordered, it's that it has to be constant.
When given a query, the database needs to know the shape of the result set and the shape needs to be consistent across queries (assuming no other DDL operations have taken place that might affect it). For a PIVOT query, the shape of the result is defined by the IN clause - each entry becomes a column, with a data type corresponding to the aggregation clause.
Hypothetically if you were to allow a sub-select for the IN clause then you could alter the shape of the result set just by performing DML operations. Imagine your sub-select worked and got you a list of all event_ids known to the system - by inserting a new record into whatever drives that sub-select, your query returns a different number of columns even though no DDL has occurred.
Now we're stuck - any view built on that query is invalid because its shape wouldn't match that of the query, but Oracle couldn't know that it's invalid because none of the objects it depends on have been changed by DDL.
Depending on where you're consuming the result, dynamic SQL's your only option - either at the application level (build the IN list yourself) or via a ref cursor in a database function or procedure.
Interesting question.
On the face of it, it shouldn't work, since the list of values (which will become column names) must be ordered. This is not the case for an "IN" list in the WHERE clause. But perhaps it would work with an ORDER BY condition in the sub-SELECT?
Unfortunately, no. This is easy to test. Got the same error message with or without ORDER BY. (And the query works fine if the IN list is just 10, 20, 30, 40 - the actual department numbers from the DEPT table.) Using tables from the standard SCOTT schema.
SQL> select deptno from scott.dept;
DEPTNO
----------
10
20
30
40
4 rows selected.
SQL> select * from (
2 select sal, deptno
3 from scott.emp
4 )
5 pivot (sum(sal) as total_sal
6 for deptno in (10, 20, 30, 40))
7 ;
10_TOTAL_SAL 20_TOTAL_SAL 30_TOTAL_SAL 40_TOTAL_SAL
------------ ------------ ------------ ------------
8750 10875 9400
1 row selected.
SQL> select * from (
2 select sal, deptno
3 from scott.emp
4 )
5 pivot (sum(sal) as total_sal
6 for deptno in (select deptno from scott.dept order by deptno))
7 ;
for deptno in (select deptno from scott.dept order by deptno))
*
ERROR at line 6:
ORA-00936: missing expression
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
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.
I am using an oracle 11 table with interval partitioning and list subpartitioning like this (simplified):
CREATE TABLE LOG
(
ID NUMBER(15, 0) NOT NULL PRIMARY KEY
, MSG_TIME DATE NOT NULL
, MSG_NR VARCHAR2(16 BYTE)
) PARTITION BY RANGE (MSG_TIME) INTERVAL (NUMTOYMINTERVAL (1,'MONTH'))
SUBPARTITION BY LIST (MSG_NR)
SUBPARTITION TEMPLATE (
SUBPARTITION login VALUES ('FOO')
, SUBPARTITION others VALUES (DEFAULT)
)
(PARTITION oldvalues VALUES LESS THAN (TO_DATE('01-01-2010','DD-MM-YYYY')));
How do I drop a specific subpartitition for a specific month without knowing the (system generated) name of the subpartition? There is a syntax "alter table ... drop subpartition for (subpartition_key_value , ...)" but I don't see a way to specify the month for which I am deleting the subpartition. The partition administration guide does not give any examples, either. 8-}
You can use the metadata tables to get the specific subpartition name:
SQL> insert into log values (1, sysdate, 'FOO');
1 row(s) inserted.
SQL> SELECT p.partition_name, s.subpartition_name, p.high_value, s.high_value
2 FROM user_tab_partitions p
3 JOIN
4 user_tab_subpartitions s
5 ON s.table_name = p.table_name
6 AND s.partition_name = p.partition_name
7 AND p.table_name = 'LOG';
PARTITION_NAME SUBPARTITION_NAME HIGH_VALUE HIGH_VALUE
--------------- ------------------ ------------ ----------
OLDVALUES OLDVALUES_OTHERS 2010-01-01 DEFAULT
OLDVALUES OLDVALUES_LOGIN 2010-01-01 'FOO'
SYS_P469754 SYS_SUBP469753 2012-10-01 DEFAULT
SYS_P469754 SYS_SUBP469752 2012-10-01 'FOO'
SQL> alter table log drop subpartition SYS_SUBP469752;
Table altered.
If you want to drop a partition dynamically, it can be tricky to find it with the ALL_TAB_SUBPARTITIONS view because the HIGH_VALUE column may not be simple to query. In that case you could use DBMS_ROWID to find the subpartition object_id of a given row:
SQL> insert into log values (4, sysdate, 'FOO');
1 row(s) inserted.
SQL> DECLARE
2 l_rowid_in ROWID;
3 l_rowid_type NUMBER;
4 l_object_number NUMBER;
5 l_relative_fno NUMBER;
6 l_block_number NUMBER;
7 l_row_number NUMBER;
8 BEGIN
9 SELECT rowid INTO l_rowid_in FROM log WHERE id = 4;
10 dbms_rowid.rowid_info(rowid_in =>l_rowid_in ,
11 rowid_type =>l_rowid_type ,
12 object_number =>l_object_number,
13 relative_fno =>l_relative_fno ,
14 block_number =>l_block_number ,
15 row_number =>l_row_number );
16 dbms_output.put_line('object_number ='||l_object_number);
17 END;
18 /
object_number =15838049
SQL> select object_name, subobject_name, object_type
2 from all_objects where object_id = '15838049';
OBJECT_NAME SUBOBJECT_NAME OBJECT_TYPE
--------------- --------------- ------------------
LOG SYS_SUBP469757 TABLE SUBPARTITION
As it turns out, the "subpartition for" syntax does indeed work, though that seems to be a secret Oracle does not want to tell you about. :-)
ALTER TABLE TB_LOG_MESSAGE DROP SUBPARTITION FOR
(TO_DATE('01.02.2010','DD.MM.YYYY'), 'FOO')
This deletes the subpartition that would contain MSG_TIME 2010/02/01 and MSG_NR FOO. (It is not necessary that there is an actual row with this exact MSG_TIME and MSG_NR. It throws an error if there is no such subpartition, though.)
Thanks for the post - it was very useful for me.
One observation though on the above script to identify the partition and delete it:
The object_id returned by dbms_rowid.rowid_info is not the object_id of the all_objects table. It is actually the data_object_id. It is observed that usually these ids match. However, after truncating the partitioned table several times, these ids diverged in my database. Hence it might be reasonable to instead use the data_object_id to find out the name of the partition:
select object_name, subobject_name, object_type
from all_objects where data_object_id = '15838049';
From the table description of ALL_OBJECTS:
OBJECT_ID Object number of the object
DATA_OBJECT_ID Object number of the segment which contains the object
http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_rowid.htm
In the sample code provided in the above link, DBMS_ROWID.ROWID_OBJECT(row_id) is used instead to derive the same information that is given by dbms_rowid.rowid_info. However, the documentation around this sample mentions that it is a data object number from the ROWID.
Examples
This example returns the ROWID for a row in the EMP table, extracts
the data object number from the ROWID, using the ROWID_OBJECT function
in the DBMS_ROWID package, then displays the object number:
DECLARE object_no INTEGER; row_id ROWID; ... BEGIN
SELECT ROWID INTO row_id FROM emp
WHERE empno = 7499; object_no := DBMS_ROWID.ROWID_OBJECT(row_id); DBMS_OUTPUT.PUT_LINE('The obj. # is
'|| object_no); ...