auto increment logic in DML - sql

I have two tables test2 and test_hist. i want to load data into test_hist from test2 but it is failing due to unique constraint .
CREATE TABLE TEST2 (ID NUMBER , TEXT VARCHAR2(10));
create table test_hist (id number , text varchar2(10) , constraint t_pk PRIMARY key (id , text));
INSERT INTO TEST2 VALUES(100 , '20180909-I');
INSERT INTO TEST2 VALUES(101 , '20180909-I');
INSERT INTO TEST2 VALUES(102 , '20180809-I');
INSERT INTO TEST2 VALUES(100 , '20180909-I');
COMMIT;
INSERT INTO test_hist SELECT ID , TEXT FROM TEST2;
I want to append the TEXT field with auto increment number whenever there is a duplicate like below.
expected OUTPUT
ID TEXT
100 20180909-I
101 20180909-I
102 20180809-I
100 20180909-I-1
100 20180909-I-2
102 20180809-I-1
Could any one help me to achieve this.
Thanks in Advance
if i execute the insert statements multiple times it should inserted into the test_hist with autoincrement text .
e.g
insert into test_hist
select id,
text || case when row_number() over (partition by id, text order by null) > 1
then (1 - row_number() over (partition by id, text order by null)) end
from test2;
9 rows inserted.
select *
from test_hist
order by id, text;
ID TEXT
---------- ------------
100 20180909-I
100 20180909-I-1
100 20180909-I-2
100 20180909-I-3
101 20180909-I
101 20180909-I-1
102 20180809-I
102 20180809-I-1
102 20180809-I-2

Same basic idea as #Barbaros, but arranged slightly differently, and with the second table's column size increased so it can hold the amended value:
create table test2 (
id number, text varchar2(10)
);
create table test_hist (id number,
text varchar2(12), -- increased size to 12; may need to be larger
constraint t_pk primary key (id , text)
);
insert into test2 values(100 , '20180909-I');
insert into test2 values(101 , '20180909-I');
insert into test2 values(102 , '20180809-I');
insert into test2 values(100 , '20180909-I');
insert into test2 values(100 , '20180909-I');
insert into test2 values(102 , '20180809-I');
Then the same analytic function, in one level if you don't mind repeating it, and including all the PK columns in the partition-by clause:
insert into test_hist
select id,
text || case when row_number() over (partition by id, text order by null) > 1
then (1 - row_number() over (partition by id, text order by null)) end
from test2;
6 rows inserted.
select *
from test_hist
order by id, text;
ID TEXT
---------- ------------
100 20180909-I
100 20180909-I-1
100 20180909-I-2
101 20180909-I
102 20180809-I
102 20180809-I-1
If you actually have more columns in your real scenario and there is another non-PK column in the original table which you want to influence the order the history rows are incremented, you can just use that in the function's order by instead of null, which is just a dummy placeholder really.
If it needs to keep doing the same thing on multiple inserts (which suggests a data model issue even more than the original requirement did) then you'll need to count existing matches in the history table too.
Starting from the same original data as before and an empty historytable:
insert into test_hist
select id,
text || case when appearance > 1 then (1 - appearance) end
from (
select t.id,
t.text,
row_number() over (partition by t.id, t.text order by null) + (
select count(*) from test_hist th
where th.id = t.id
and th.text like t.text || '%'
) as appearance
from test2 t
);
6 rows inserted.
select *
from test_hist
order by id, text;
ID TEXT
---------- ------------
100 20180909-I
100 20180909-I-1
100 20180909-I-2
101 20180909-I
102 20180809-I
102 20180809-I-1
6 rows selected.
and running the same statement a second time:
insert into test_hist
select id,
text || case when appearance > 1 then (1 - appearance) end
from (
select t.id,
t.text,
row_number() over (partition by t.id, t.text order by null) + (
select count(*) from test_hist th
where th.id = t.id
and th.text like t.text || '%'
) as appearance
from test2 t
);
6 rows inserted.
select *
from test_hist
order by id, text;
ID TEXT
---------- ------------
100 20180909-I
100 20180909-I-1
100 20180909-I-2
100 20180909-I-3
100 20180909-I-4
100 20180909-I-5
101 20180909-I
101 20180909-I-1
102 20180809-I
102 20180809-I-1
102 20180809-I-2
102 20180809-I-3
12 rows selected.
There are probably ways to optimise it so you don't have to hit the history table so often, but this may give you a starting point to work from.
The use of like really means this only works if the text is always the same length, or at least you can't have values that are extensions of other values; otherwise you'll get more matches than you want. From your sample data, at least, that doesn't look like an issue. You could probably work around that by switching to regexp_like if necessary, depending on your actual data.

You may try the following :
select ID, text||decode(rn-1,0,null,'-'||(rn-1)) as text
from
(
with test2(rnk,ID,text) as
(
select 1, 100 , '20180909-I' from dual union all
select 2, 101 , '20180909-I' from dual union all
select 3, 102 , '20180809-I' from dual union all
select 4, 100 , '20180909-I' from dual union all
select 5, 100 , '20180909-I' from dual union all
select 6, 102 , '20180909-I' from dual
)
select t.ID, t.rnk,
t.text, row_number() over (partition by ID order by Text,ID) as rn
from test2 t
)
order by rn, rnk
Rextester Demo

Related

Create a duplicate row on top of Select statement

table TEST
id
Name
1
abc
2
xyz
In general i used to get records from below query
Select id,name from TEST.
id
Name
1
abc
2
xyz
but now i want to create a duplicate for each row on top my select query
expected output: please suggest how can i achieve result like below
id
Name
1
abc
1
abc
2
xyz
2
xyz
You may cross join your table with a sequence table containing how ever many copies you want. Here is an example using an inline sequence table:
SELECT t1.id, t1.Name
FROM yourTable t1
CROSS JOIN (
SELECT 1 AS seq FROM dual UNION ALL
SELECT 2 FROM dual UNION ALL
SELECT 3 FROM dual
) t2
WHERE t2.seq <= 2
ORDER BY t1.id;
To me, UNION (ALL) set operator seems to be quite simple.
Sample data:
SQL> select * from test;
ID NAME
---------- ----
1 abc
2 xyz
UNION ALL:
SQL> select * from test
2 union all
3 select * from test;
ID NAME
---------- ----
1 abc
2 xyz
1 abc
2 xyz
SQL>
CREATE table test(
id integer,
name VARCHAR2(4)
);
INSERT into test (id, name) VALUES (1,'ABC');
INSERT into test (id, name) VALUES (2,'XYZ');
with data as (select level l from dual connect by level <= 2)
select *
from test, data
order by id, l
/
One more option is LATERAL
SELECT t.*
FROM test
, LATERAL (
SELECT id, name FROM DUAL
union all
SELECT id, name FROM DUAL
) t
One option is using a self-join along with ROW_NUMBER analytic function such as
WITH t AS
(
SELECT t1.id, t1.name, ROW_NUMBER() OVER (PARTITION BY t1.id ORDER BY 0) AS rn
FROM test t1,
test t2
)
SELECT id, name
FROM t
WHERE rn <= 2
Demo

Rows to Columns - Oracle - Not using Union ALL

Is there any better way achieve the below result other than using union all?
The table has millions of records, so looking for a better option where the result set is fetched once.
create table test_tab (
rec_id number(3),
p_code varchar2(5),
q_code varchar2(5),
r_code varchar2(5),
p_amt number(8),
q_amt number(8),
r_amt number(8)
);
delete from test_tab;
insert into test_tab (rec_id, p_code, q_code,r_code , p_amt,q_amt,r_amt)
values (1, 'p1','q1','r1',18,9,9);
insert into test_tab (rec_id, p_code, q_code,r_code , p_amt,q_amt,r_amt)
values (2, 'p2','q2','r2',28,6,4);
insert into test_tab (rec_id, p_code, q_code,r_code , p_amt,q_amt,r_amt)
values (3, 'p1',null,null,18,null,null);
insert into test_tab (rec_id, p_code, q_code,r_code , p_amt,q_amt,r_amt)
values (4, null,'q3','r3',null,9,9);
commit;
select rec_id, p_code,p_amt from test_tab where p_code is not null
union all
select rec_id, q_code,q_amt from test_tab where q_code is not null
union all
select rec_id, r_code,r_amt from test_tab where r_code is not null;
Result:
REC_ID
P_CODE
P_AMT
1
q1
9
1
p1
18
1
r1
9
2
p2
28
2
q2
6
2
r2
4
3
p1
18
4
q3
9
4
r3
9
This is a basic application of the unpivot operator, available since Oracle 11.1.
select rec_id, code, amt
from test_tab
unpivot ((code, amt) for ord in
((p_code, p_amt) as 1, (q_code, q_amt) as 2, (r_code, r_amt) as 3))
order by rec_id, ord -- if needed
;
REC_ID CODE AMT
---------- ----- ----------
1 p1 18
1 q1 9
1 r1 9
2 p2 28
2 q2 6
2 r2 4
3 p1 18
4 q3 9
4 r3 9
9 rows selected.
Notice a few things. I call the output columns code and amt - it makes no sense to have the prefix p_ in the output column names. Also, "exclude nulls" is the default in unpivot, so I didn't need to mention it explicitly (although it wouldn't hurt anything). Finally, while perhaps not critical, I also created a column ord to reflect column order, and ordered the rows in the output in the same order as you had the columns in the input.
You can use Hiearchy query and cross join as follows:
select * from (select rec_id,
case lvl when 1 then p_code when 2 then q_code else r_code end as p_code,
case lvl when 1 then p_amt when 2 then q_amt else r_amt end as p_amount
from test_tab
cross join (select level as lvl from dual connect by level <= 3) )
where p_code is not null
You can use a lateral join:
select x.*
from test_tab t cross join lateral
(select t.rec_id, t.p_code as code, t.p_amount as amount from dual union all
select t.rec_id, t.q_code, t.q_amount from dual union all
select t.rec_id, t.r_code, t.r_amount from dual
) x
where code is not null;
Technical, this still has a union all, but it is only scanning the original table once.

Is it possible to update rows randomly with a group of set values?

I have a work assignment table that I would like help with. What I would like to do is randomly assign peoples names to the rows in the table. For example, the table currently looks like:
TASK |NAME
1 Get Chicken |
2 Clean Chicken|
3 Cook Chicken |
4 Eat Chicken |
5 Eat Corn |
6 Takeout Trash|
I have 4 employees that I want to assign these tasks to, but do not want to show any favoritism. Here is what that table looks like:
NAME
John
Lucy
Fred
Jasmine
How can I randomly update the NAME field based on the above names?
edit based on comments. I changed the number of tasks to something not divisible by 4. In this case the number of tasks is now 6. I want to make it to where no one can get 2 or more tasks more then the rest of their colleagues. But in this case, it's ok for someone to have 1 more task then their colleagues. he result should be something like (but random):
TASK |NAME
1 Get Chicken |John
2 Clean Chicken|Jasmine
3 Cook Chicken |Lucy
4 Eat Chicken |Fred
5 Eat Corn |Fred
6 Takeout Trash|Jasmine
Here is a pure SQL way to do it.
MERGE INTO so_tasks t USING (
WITH numbered_tasks AS ( SELECT t.*,
row_number() OVER (ORDER BY dbms_random.value) task_number,
count(*) OVER () total_tasks FROM so_tasks t ),
numbered_employees AS ( SELECT e.*,
row_number() OVER (ORDER BY dbms_random.value) employee_number,
count(*) OVER () total_employees FROM so_employees e)
SELECT nt.task,
ne.name
FROM numbered_tasks nt
INNER JOIN numbered_employees ne
ON ne.employee_number-1 = mod(nt.task_number-1, ne.total_employees) ) u
ON ( t.task = u.task )
WHEN MATCHED THEN UPDATE SET t.name = u.name;
It sorts each list randomly and assigned a number to each row in each list. It then gets the row from the employee list whose number matched the task number MOD the total number of employees.
Here is a fully example:
Create tables
CREATE TABLE so_tasks
( task VARCHAR2(30) NOT NULL PRIMARY KEY,
name VARCHAR2(30) );
INSERT INTO so_tasks ( task ) VALUES ('Get Chicken');
INSERT INTO so_tasks ( task ) VALUES ('Clean Chicken');
INSERT INTO so_tasks ( task ) VALUES ('Cook Chicken');
INSERT INTO so_tasks ( task ) VALUES ('Eat Chicken');
INSERT INTO so_tasks ( task ) VALUES ('Eat Corn');
INSERT INTO so_tasks ( task ) VALUES ('Takeout Trash');
CREATE TABLE so_employees
( name VARCHAR2(30) NOT NULL PRIMARY KEY );
INSERT INTO so_employees ( name ) VALUES ('John');
INSERT INTO so_employees ( name ) VALUES ('Lucy');
INSERT INTO so_employees ( name ) VALUES ('Fred');
INSERT INTO so_employees ( name ) VALUES ('Jasmine');
COMMIT;
Merge
MERGE INTO so_tasks t USING (
WITH numbered_tasks AS ( SELECT t.*,
row_number() OVER (ORDER BY dbms_random.value) task_number,
count(*) OVER () total_tasks FROM so_tasks t ),
numbered_employees AS ( SELECT e.*,
row_number() OVER (ORDER BY dbms_random.value) employee_number,
count(*) OVER () total_employees FROM so_employees e)
SELECT nt.task,
ne.name
FROM numbered_tasks nt
INNER JOIN numbered_employees ne
ON ne.employee_number-1 = mod(nt.task_number-1, ne.total_employees) ) u
ON ( t.task = u.task )
WHEN MATCHED THEN UPDATE SET t.name = u.name;
Results
SELECT * FROM so_tasks;
+---------------+---------+
| TASK | NAME |
+---------------+---------+
| Get Chicken | John |
| Clean Chicken | Jasmine |
| Cook Chicken | Lucy |
| Eat Chicken | Fred |
| Eat Corn | Jasmine |
| Takeout Trash | Fred |
+---------------+---------+
Your exact assignments for each task will be different, but there will never be more than a one task difference between any two employees.
You can give the tasks random sequential numbers and the employees another random sequential number and then join the two tables using those numbers and then use a MERGE statement to update the table correlating on the ROWID pseudo-column to uniquely identify each task.
Oracle Setup:
CREATE TABLE table_name ( task VARCHAR2(20), name VARCHAR2(20) );
INSERT INTO table_name ( TASK )
SELECT 'Get Chicken' FROM DUAL UNION ALL
SELECT 'Clean Chicken' FROM DUAL UNION ALL
SELECT 'Cook Chicken' FROM DUAL UNION ALL
SELECT 'Eat Chicken' FROM DUAL UNION ALL
SELECT 'Eat Corn' FROM DUAL UNION ALL
SELECT 'Takeout Trash' FROM DUAL;
CREATE TABLE employees ( NAME ) AS
SELECT 'John' FROM DUAL UNION ALL
SELECT 'Lucy' FROM DUAL UNION ALL
SELECT 'Fred' FROM DUAL UNION ALL
SELECT 'Jasmine' FROM DUAL;
Merge:
MERGE INTO table_name dst
USING (
WITH random_tasks ( rid, rn ) AS (
SELECT ROWID,
ROW_NUMBER() OVER ( ORDER BY DBMS_RANDOM.VALUE )
FROM table_name
),
random_names ( name, rn, num_employees ) AS (
SELECT name,
ROW_NUMBER() OVER ( ORDER BY DBMS_RANDOM.VALUE ),
COUNT(*) OVER ()
FROM employees
)
SELECT rid,
name
FROM random_tasks t
INNER JOIN
random_names n
ON ( MOD( t.rn, n.num_employees ) + 1 = n.rn )
) src
ON ( src.rid = dst.ROWID )
WHEN MATCHED THEN
UPDATE SET name = src.name;
Result:
SELECT * FROM table_name;
TASK | NAME
:------------ | :------
Get Chicken | John
Clean Chicken | Jasmine
Cook Chicken | Fred
Eat Chicken | Lucy
Eat Corn | Fred
Takeout Trash | Lucy
db<>fiddle here
Assuming you're fine with a PL/SQL solution (you could do it in a single update statement but unless it's performance critical, I'd find the loop easier to follow)
begin
for src in (select t.task_id, e.name
from (select t.*,
row_number() over (order by dbms_random.value) rnk
from task t) t
join
(select e.*,
row_number() over (order by dbms_random.value) rnk,
count(*) over () num_emps
from employee e) e
on( mod( t.rnk, e.num_emps ) = e.rnk - 1 ) )
loop
update task
set name = src.name
where task_id = src.task_id;
end loop;
end;
/
Basically, this is randomly sorting both lists and then going down the list of tasks and assigning the next employee to that task. If the number of tasks isn't a multiple of the number of employees, some employees will get an extra task but no employee will have more than 1 more task than another.

What's the difference with my two sql to find repeated record in Oralce?

I want to delete and leave only one record if the record is repeated.In my table t_id is a primary key which is int type and automatic increate,and id is a serial number and is varchar type.
At first,my sql is like this.but it seems deadlock,and runs very long time and seems never stop.
delete from tbtest
Where t_id Not In (
select max(t_id) from tbtest having count(id)>1
group by id
)
and id in (
select id from tbtest group by id having count(id)>1
)
and create_time<to_timestamp('2016-02-25 11:26:52','yyyy-mm-dd hh24:mi:ss')
So I change it to the other way,and it runs very quickly?What's the difference between them?
delete from tbtest where t_id in (
select t_id from tbtest
Where t_id Not In (
select max(t_id) from tbtest having count(id)>1
group by id
)
and id in (
select id from tbtest group by id having count(id)>1
)
and create_time<=to_timestamp('2016-02-25 11:26:52','yyyy-mm-dd hh24:mi:ss')
)
Here is a proof of concept. Try it out and see if it helps.
create table ta ( t_id number primary key, id varchar2(10), val number );
insert into ta
select 1, '1', 33 from dual union all
select 2, '2', 44 from dual union all
select 3, '2', 55 from dual;
commit;
select * from ta;
T_ID ID VAL
---------- ---------- ----------
1 1 33
2 2 44
3 2 55
delete from ta
where t_id in (select t_id from (select t_id, id,
row_number() over (partition by id order by t_id desc) rn from ta) where rn > 1);
1 row deleted.
select * from ta;
T_ID ID VAL
---------- ---------- ----------
1 1 33
3 2 55

Don't select duplicates based on a column in a union query

I have a query which does an UNION :
select deal_id, codptf from acc_deals where run_id = 1
union
select deal_id, codptf from acc_deals where run_id = 2
However, I find myself with this result :
AAAA;1234
AAAA;3456
BBBB;4569
There is a duplicate row, aka the rows 1 and 2 (same deal_id).
How can I exclude one of the duplicate rows ?
it looks that you need full outer join here
create table test(id varchar2(10), val int, run_id int);
insert into test values('AAAA', 1234, 1);
insert into test values('BBBB', 4569, 1);
insert into test values('AAAA', 3456, 2);
insert into test values('CCCC', 1111, 2);
select
nvl(t1.id, t2.id) as id, nvl(t1.val, t2.val) as val
from
(select * from test where run_id = 1) t1
full join
(select * from test where run_id = 2) t2
on t1.id = t2.id
ID VAL
1 AAAA 1234
2 CCCC 1111
3 BBBB 4569
As an alternative to #are's solution, you could do a group by instead:
create table test(id varchar2(10), val int, run_id int);
insert into test values('AAAA', 1234, 1);
insert into test values('BBBB', 4569, 1);
insert into test values('AAAA', 3456, 2);
insert into test values('CCCC', 1111, 2);
insert into test values('DDDD', 8958, 1);
insert into test values('DDDD', 2345, 2);
commit;
select id,
max(val) max_val,
min(val) min_val,
max(val) keep (dense_rank first order by run_id) val_from_first_run_id,
max(val) keep (dense_rank last order by run_id) val_from_last_run_id
from test
where run_id in (1, 2)
group by id;
ID MAX_VAL MIN_VAL VAL_FROM_FIRST_RUN_ID VAL_FROM_LAST_RUN_ID
---------- ---------- ---------- --------------------- --------------------
AAAA 3456 1234 1234 3456
BBBB 4569 4569 4569 4569
CCCC 1111 1111 1111 1111
DDDD 8958 2345 8958 2345
You'll note that there are several columns there - I've simply listed out a few of the options depending on which value you wanted to keep. It's up to you to decide which of the columns you're going to use, since you didn't specify which value you wanted displayed in the event of duplicates.