ORACLE SQL : IF EXISTS UPDATE ELSE INSERT - sql

Lets say :
i have data on OracleDb like what i mentioned above.
TRANSFERNUMBER | VALUE1 | VALUE2
2250 | 1000 | 2000
2251 | 1000 | 3000
My main purpose is when add some data on table if data exists it should update the data . if data not exists on the table it should insert new row on table . That is why i want to use if exists on my query .
However i can't handle the query . Also i can't write procedure because of some reasons on the table . Is anyone help me for writing this by using query on Oracle ?

MERGE is what we usually do. Here's an example:
Test table and sample data:
SQL> create table test (tn number, val1 number, val2 number);
Table created.
SQL> insert into test
2 select 2250, 1000, 2000 from dual union all
3 select 2251, 1000, 3000 from dual;
2 rows created.
SQL> select * From test order by tn;
TN VAL1 VAL2
---------- ---------- ----------
2250 1000 2000
2251 1000 3000
How to do it? using represents data you're going to insert or update:
SQL> merge into test t
2 using (select 2250 tn, 1 val1, 2 val2 from dual union all --> for update
3 select 3000 , 8 , 9 from dual --> for insert
4 ) x
5 on (t.tn = x.tn)
6 when matched then update set t.val1 = x.val1,
7 t.val2 = x.val2
8 when not matched then insert values (x.tn, x.val1, x.val2);
2 rows merged.
Result:
SQL> select * From test order by tn;
TN VAL1 VAL2
---------- ---------- ----------
2250 1 2 --> updated
2251 1000 3000
3000 8 9 --> inserted
SQL>

Related

How to copy all data in a table into the same table, but only changing one column to have subsequent numbers

I have to do the following and can't figure out how to do it all correctly:
I have a table test
Test1
Test2
1
ABC
2
DEF
I want to duplicate this, and have the test1 column have subsequent numbering. When I do a simple insert I can of course just duplicate it all and I have what I need. But I can't get the numbering of Test1 right.
The result I'm looking for is this:
Test1
Test2
1
ABC
2
DEF
3
ABC
4
DEF
What I'm getting at the moment is:
Test1
Test2
1
ABC
2
DEF
1
ABC
2
DEF
I tried the following but did not get subsequent numbering in the test1 column:
INSERT INTO test (test1,
test2) 
SELECT test,
test
FROM test; drop table if exists temp.tmp; 
create temporary table tmp as
select test1, row_number() over (order by test1) rn
from test; update test
set test1 = (
  select rn from temp.tmp
  where temp.tmp.test1 = test.test1
  );
  drop table temp.tmp;
You can use a window function for this task. Specifically, you can duplicate all your rows, yet increasing the Test1 values, by adding the current total count of records to every record of your table.
INSERT INTO tab
SELECT Test1 + COUNT(*) OVER(), Test2 FROM tab;
Output:
TEST1
TEST2
1
ABC
2
DEF
3
ABC
4
DEF
Check the demo here.
How about this?
Sample data:
SQL> select * from test;
TEST1 TES
---------- ---
1 ABC
2 DEF
5 GHI
Insert duplicates into test2 column:
SQL> insert into test (test2) select test2 from test;
3 rows created.
Fix numbering:
SQL> update test set test1 = rownum;
6 rows updated.
Result:
SQL> select * from test;
TEST1 TES
---------- ---
1 ABC
2 DEF
3 GHI
4 ABC
5 DEF
6 GHI
6 rows selected.
SQL>
Creating an identical table which contains an IDENTITY column would be straightforward I think, provided that the DB's version is 12c+, such as
CREATE TABLE Test_( Test1 INT GENERATED ALWAYS AS IDENTITY, Test2 VARCHAR2(99) );
INSERT INTO Test_(Test2) SELECT Test2 FROM Test ORDER BY Test1; -- run as many times as you wish
DROP TABLE Test;
ALTER TABLE Test_ RENAME TO Test;
SELECT * FROM Test; -- assuming the INSERT Statement above run twice
TEST1 | TEST2
------|-------
1 | ABC
2 | DEF
3 | ABC
4 | DEF
Demo

How to generate a dynamic sequence in Oracle

I have a table A which represents a valid sequence of numbers, which looks something like this:
| id | start | end | step |
|----|-------|-------|------|
| 1 | 4000 | 4999 | 4 |
| 2 | 3 | 20000 | 1 |
A[1] thus represents the sequence [4000, 4004, 4008, ...4996]
and another B of "occupied" numbers that looks like this:
| id | number | ... |
|-----|--------|-----|
| 1 | 4000 | ... |
| 2 | 4003 | ... |
| ... | ... | ... |
I want to construct a query which using A and B, finds the first unoccupied number for a particular sequence.
I have been trying – and failing – to do, is to generate a list of valid numbers from a row in A and then left outer join table B on B.number = valid_number where B.id is null from which result I could then select min(...).
How about this?
I simplified your test case (END value isn't that high) in order to save space (otherwise, I'd have to use smaller font :)).
What does it do?
CTEs A and B are your sample data
FULL_ASEQ creates a sequence of numbers from table A
if you want what it returns, remove everything from line #17 and - instead of it - run select * from full_aseq
the final query returns the first available sequence number, i.e. the one that hasn't been used yet (lines #19 - 23).
Here you go:
SQL> with
2 a (id, cstart, cend, step) as
3 (select 1, 4000, 4032, 4 from dual union all
4 select 2, 3, 20, 1 from dual
5 ),
6 b (id, cnumber) as
7 (select 1, 4000 from dual union all
8 select 1, 4004 from dual union all
9 select 2, 4003 from dual
10 ),
11 full_aseq as
12 (select a.id, a.cstart + column_value * a.step seq_val
13 from a cross join table(cast(multiset(select level from dual
14 connect by level <= (a.cend - a.cstart) / a.step
15 ) as sys.odcinumberlist))
16 )
17 select f.id, min(f.seq_val) min_seq_val
18 from full_aseq f
19 where not exists (select null
20 from b
21 where b.id = f.id
22 and b.cnumber = f.seq_val
23 )
24 group by f.id;
ID MIN_SEQ_VAL
---------- -----------
1 4008
2 4
SQL>
You can use LEAD to compute the difference between ordered rows in table B. Any row having a difference (to the next row) that exceeds the step value for that sequence is a gap.
Here's that concept, implemented (below). I threw in a sequence ID "3" that has no values in table B, to illustrate that it generates the proper first value.
with
a (id, cstart, cend, step) as
(select 1, 4000, 4032, 4 from dual union all
select 2, 3, 20000, 1 from dual union all
select 3, 100, 200, 3 from dual
),
b (id, cnumber) as
(select 1, 4000 from dual union all
select 1, 4004 from dual union all
select 1, 4012 from dual union all
select 2, 4003 from dual
),
work1 as (
select a.id,
b.cnumber cnumber,
lead(b.cnumber,1) over ( partition by b.id order by b.cnumber ) - b.cnumber diff,
a.step,
a.cstart,
a.cend
from a left join b on b.id = a.id )
select w1.id,
CASE WHEN min(w1.cnumber) is null THEN w1.cstart
WHEN min(w1.cnumber)+w1.step < w1.cend THEN min(w1.cnumber)+w1.step
ELSE null END next_cnumber
from work1 w1
where ( diff is null or diff > w1.step )
group by w1.id, w1.step, w1.cstart, w1.cend
order by w1.id
+----+--------------+
| ID | NEXT_CNUMBER |
+----+--------------+
| 1 | 4008 |
| 2 | 4004 |
| 3 | 100 |
+----+--------------+
You can further improve the results by excluding rows in table B that are impossible for the sequence. E.g., exclude a row for ID #1 having a value of, say, 4007.
I'll ask the obvious and suggest why not use an actual sequence?
SQL> set timing on
SQL> CREATE SEQUENCE SEQ_TEST_A
START WITH 4000
INCREMENT BY 4
MINVALUE 4000
MAXVALUE 4999
NOCACHE
NOCYCLE
ORDER
Sequence created.
Elapsed: 00:00:01.09
SQL> CREATE SEQUENCE SEQ_TEST_B
START WITH 3
INCREMENT BY 1
MINVALUE 3
MAXVALUE 20000
NOCACHE
NOCYCLE
ORDER
Sequence created.
Elapsed: 00:00:00.07
SQL> -- get nexvals from A
SQL> select seq_test_a.nextval from dual
NEXTVAL
----------
4000
1 row selected.
Elapsed: 00:00:00.09
SQL> select seq_test_a.nextval from dual
NEXTVAL
----------
4004
1 row selected.
Elapsed: 00:00:00.08
SQL> select seq_test_a.nextval from dual
NEXTVAL
----------
4008
1 row selected.
Elapsed: 00:00:00.08
SQL> -- get nextvals from B
SQL> select seq_test_b.nextval from dual
NEXTVAL
----------
3
1 row selected.
Elapsed: 00:00:00.08
SQL> select seq_test_b.nextval from dual
NEXTVAL
----------
4
1 row selected.
Elapsed: 00:00:00.08
SQL> select seq_test_b.nextval from dual
NEXTVAL
----------
5
1 row selected.
Elapsed: 00:00:00.08

Oracle - Create Surrogate key for 300 million records [duplicate]

This question already has answers here:
How to add a sequence column to an existing table with records
(3 answers)
Closed 4 years ago.
There is a table with millions of records which has duplicate records as well. what is the process of creating a new entity as surrogate key (which denotes sequence no.)
E.g table structure
col1 col2
101 A
101 A
101 B
102 A
102 B
I would like to create a new column (col3) - which denotes a seq no.
col1 col2 col3
101 A 1
101 A 2
101 B 3
102 A 1
102 B 2
Please suggest me steps to follow to create surrogate key for existing records(300 million), and even when new records are loaded ( I assume trigger is needed to while inserting).
Just use row_number function to populate col3 :
For already existing records apply :
SQL> create table tab(col1 int , col2 varchar2(1));
Table created
SQL> insert all
2 into tab values(101,'A')
3 into tab values(101,'A')
4 into tab values(101,'B')
5 into tab values(102,'A')
6 into tab values(102,'B')
7 select * from dual;
5 rows inserted
SQL> create table tab_ as
2 select col1, col2,
3 row_number() over (partition by col1 order by col2) as col3
4 from tab;
Table created
SQL> drop table tab;
Table dropped
SQL> alter table tab_ rename to tab;
Table altered
OR Alternatively ( without recreating the table ) :
SQL> create table tab(col1 int , col2 varchar2(1));
Table created
SQL> insert all
2 into tab values(101,'A')
3 into tab values(101,'A')
4 into tab values(101,'B')
5 into tab values(102,'A')
6 into tab values(102,'B')
7 select * from dual;
5 rows inserted
SQL> alter table tab add col3 integer;
Table altered
SQL> declare
2 i pls_integer := 0;
3 begin
4 for c in
5 (
6 select rowid, col1, col2,
7 row_number() over (partition by col1 order by col2) as col3
8 from tab
9 )
10 loop
11 update tab t
12 set t.col3 = c.col3
13 where t.rowid = c.rowid;
14 i:= i+1;
15 if ( ( i mod 10000 ) = 0 ) then commit; end if;
16 end loop;
17 end;
18 commit;
19 /
PL/SQL procedure successfully completed
SQL> select * from tab;
COL1 COL2 COL3
---- ---- -----
101 A 1
101 A 2
101 B 3
102 A 1
102 B 2
5 rows selected
For upcoming (newly inserted) records you may use a trigger as you mentioned :
SQL> create or replace trigger trg_ins_tab
2 before insert on tab
3 referencing new as new old as old for each row
4 declare
5 begin
6 select nvl(max(col3),0) + 1
7 into :new.col3
8 from tab
9 where col1 = :new.col1;
10 end;
11 /
Trigger created
SQL> insert into tab(col1,col2) values(101,'C');
1 row inserted
SQL> select *
2 from tab t
3 order by t.col1, col3;
COL1 COL2 COL3
---- ---- -----
101 A 1
101 A 2
101 B 3
101 C 4
102 A 1
102 B 2
6 rows selected

PL/SQL - SQL dynamic row and column parsing

I took a look into the forums and couldn't really find something that I needed.
What I have is two tables one table with (Parse_Table)
File_ID|Start_Pos|Length|Description
------------------------------------
1 | 1 | 9 | Pos1
1 | 10 | 1 | Pos2
1 | 11 | 1 | Pos3
2 | 1 | 4 | Pos1
2 | 5 | 7 | Pos2
and another table that needs to be parsed like (Input_file)
String
ABCDEFGHI12
ASRQWERTQ45
123456789AB
321654852PO
and I want to have the result where If I put it will use this specific parsing spec
select DESCRIPTION, Start_pos,Length from Parse_table where File_ID=1
and be able to parse input file
String | Pos1 |Pos2|Pos3
---------------------------------
ABCDEFGHI12 |ABCDEFGHI | 1 | 2
ASRQWERTQ45 |ASRQWERTQ | 4 | 5
123456789AB |123456789 | A | B
321654852PO |321654852 | P | O
and alternatively if I put file_id=2 it would parse the values differently.
I looked at using the Pivot function, but it looks like number of columns are static, at least to my knowledge.
thanks in advance for your support please let me know what I can do in SQL.
You can get "close-ish" with the standard decode tricks to pivot the table assuming a ceiling on the maximum number of fields expected.
SQL> create table t ( fid int, st int, len int, pos varchar2(10));
Table created.
SQL>
SQL> insert into t values ( 1 , 1 , 9 , 'Pos1');
1 row created.
SQL> insert into t values ( 1 , 10 , 1 , 'Pos2');
1 row created.
SQL> insert into t values ( 1 , 11 , 1 , 'Pos3');
1 row created.
SQL> insert into t values ( 2 , 1 , 4 , 'Pos1');
1 row created.
SQL> insert into t values ( 2 , 5 , 7 , 'Pos2');
1 row created.
SQL>
SQL> create table t1 ( s varchar2(20));
Table created.
SQL>
SQL> insert into t1 values ('ABCDEFGHI12');
1 row created.
SQL> insert into t1 values ('ASRQWERTQ45');
1 row created.
SQL> insert into t1 values ('123456789AB');
1 row created.
SQL> insert into t1 values ('321654852PO');
1 row created.
SQL>
SQL>
SQL> select
2 t1.s,
3 max(decode(t.seq,1,substr(t1.s,t.st,t.len))) c1,
4 max(decode(t.seq,2,substr(t1.s,t.st,t.len))) c2,
5 max(decode(t.seq,3,substr(t1.s,t.st,t.len))) c3,
6 max(decode(t.seq,4,substr(t1.s,t.st,t.len))) c4,
7 max(decode(t.seq,5,substr(t1.s,t.st,t.len))) c5,
8 max(decode(t.seq,6,substr(t1.s,t.st,t.len))) c6
9 from t1,
10 ( select t.*, row_number() over ( partition by fid order by st ) as seq
11 from t
12 where fid = 1
13 ) t
14 group by t1.s
15 order by 1;
S C1 C2 C3 C4 C5 C6
-------------------- ------------- ------------- ------------- ------------- ------------- -------------
123456789AB 123456789 A B
321654852PO 321654852 P O
ABCDEFGHI12 ABCDEFGHI 1 2
ASRQWERTQ45 ASRQWERTQ 4 5
4 rows selected.
SQL>
SQL> select
2 t1.s,
3 max(decode(t.seq,1,substr(t1.s,t.st,t.len))) c1,
4 max(decode(t.seq,2,substr(t1.s,t.st,t.len))) c2,
5 max(decode(t.seq,3,substr(t1.s,t.st,t.len))) c3,
6 max(decode(t.seq,4,substr(t1.s,t.st,t.len))) c4,
7 max(decode(t.seq,5,substr(t1.s,t.st,t.len))) c5,
8 max(decode(t.seq,6,substr(t1.s,t.st,t.len))) c6
9 from t1,
10 ( select t.*, row_number() over ( partition by fid order by st ) as seq
11 from t
12 where fid = 2
13 ) t
14 group by t1.s
15 order by 1;
S C1 C2 C3 C4 C5 C6
-------------------- ------------- ------------- ------------- ------------- ------------- -------------
123456789AB 1234 56789AB
321654852PO 3216 54852PO
ABCDEFGHI12 ABCD EFGHI12
ASRQWERTQ45 ASRQ WERTQ45
4 rows selected.
If you really wanted that result to then come back with only the desired column count and custom column names, then you're into dynamic SQL territory. How you'd tackle that depends on the tool you are providing the data to. If it can consume a REF CURSOR, then a little PL/SQL would do the trick.
An unknown number of columns can be returned from a SQL statement, but it requires code built with PL/SQL, ANY types, and Oracle Data Cartridge.
That code is tricky to write but you can start with my open source project Method4. Download, unzip, #install, and then
write a SQL statement to generate a SQL statement.
Query
select * from table(method4.dynamic_query(
q'[
--Create a SQL statement to query PARSE_FILE.
select
'select '||
listagg(column_expression, ',') within group (order by start_pos) ||
' from parse_file'
column_expressions
from
(
--Create individual SUBSTR column expressions.
select
parse_table.*,
'substr(string, '||start_pos||', '||length||') '||description column_expression
from parse_table
--CHANGE BELOW LINE TO USE A DIFFERENT FILE:
where file_id = 2
order by start_pos
)
]'
));
Sample Schema
create table parse_table as
select 1 file_id, 1 start_pos, 9 length, 'Pos1' description from dual union all
select 1 file_id, 10 start_pos, 1 length, 'Pos2' description from dual union all
select 1 file_id, 11 start_pos, 1 length, 'Pos3' description from dual union all
select 2 file_id, 1 start_pos, 4 length, 'Pos1' description from dual union all
select 2 file_id, 5 start_pos, 7 length, 'Pos2' description from dual;
create table parse_file as
select 'ABCDEFGHI12' string from dual union all
select 'ASRQWERTQ45' string from dual union all
select '123456789AB' string from dual union all
select '321654852PO' string from dual;
Results
When FILE_ID = 1:
POS1 POS2 POS3
---- ---- ----
ABCDEFGHI 1 2
ASRQWERTQ 4 5
123456789 A B
321654852 P O
When FILE_ID = 2:
POS1 POS2
---- ----
ABCD EFGHI12
ASRQ WERTQ45
1234 56789AB
3216 54852PO

SQL Combine two rows into one

I'm trying with SQL to combine two rows into one in SQL with a new ID number
ID Amount
------------
1 100
2 200
3 300
4 400
5 500
6 600
into
ID Amount
-------------
1 100
2 200
101 700
5 500
6 600
I appreciate your help.
Check out SQL Procedures. I'm not sure about your goals but I think what you need is to define a SQL Procedure. It's been I while since I did that, but it should go like:
CREATE OR REPLACE PROCEDURE combine(
ID1 IN <TABLE_NAME>.ID%TYPE,
ID2 IN <TABLE_NAME>.ID%TYPE,
NEWID IN <TABLE_NAME>.ID%TYPE)
IS
BEGIN
INSERT INTO <TABLE_NAME> (ID, Amount) VALUES(NEWID, ((SELECT Amount FROM <TABLE_NAME> WHERE ID=ID1) + (SELECT Amount FROM <TABLE_NAME> WHERE ID=ID2)));
DELETE FROM <TABLE_NAME> WHERE ID = ID1;
DELETE FROM <TABLE_NAME> WHERE ID = ID2;
COMMIT;
END;
/
To get the state as described by you, you can call the routine by
combine(4, 5, 101);