Update then insert in oracle - sql

Hi I would like to do an update then insert the same record with a bit modification again under oracle.
Examples as:
ID NAME TEACHER STATUS CourseTaken
1 Jack TA ENROLL 1
2 Rose TA ENROLL 2
3 William TB ENROLL 2
4 Caledon TB ENROLL 2
And Say I want to switch Jack from Teacher TA to teacher TB, but meanwhile, keep the record.
So it ends up like
ID NAME TEACHER STATUS CourseTaken
1 Jack TA TRANSFERRED 1
2 Rose TA ENROLL 2
3 William TB ENROLL 2
4 Caledon TB ENROLL 2
5 Jack TA ENROLL
There is a fifth row been added, and the first row is updated.
the actual table has way more columns.
So my goal here is to
Insert the 5th row as a copy of row 1
Update row 1 and row 5
accordingly
And done in one statement. Is it possible?
It will be in a transaction, #Transactional is annotated on the method. The reason for asking for one statement or constant amount of statement is to avoid N+1 problem, and performance concerns. Since there could be changes on multiple rows.

I am assumming that ID column is a primary key.
If yes, then try:
MERGE INTO mytable m
USING (
SELECT ID, NAME, TEACHER, STATUS, CourseTaken, d.*
FROM mytable
CROSS JOIN (
SELECT * FROM dual
UNION ALL
SELECT null FROM dual
) d
WHERE MyTable.Name = 'Jack'
) p
ON ( p.id = m.id and DUMMY IS NOT NULL )
WHEN MATCHED THEN UPDATE SET STATUS = 'TRANSFERRED'
WHEN NOT MATCHED THEN INSERT( ID, NAME, TEACHER, STATUS )
VALUES ( some_sequence.nextval, p.name, p.teacher, 'ENROLL' );
I am assumming that you are using some_sequence to generate new values for ID column.
Personally I would do simple INSERT and UPDATE (as separate commands) in a one transaction, and then commit the whole transaction at the end, instead of using this horrible MERGE.
But if you must use a single command, then you must live with it.

Related

Get every unique pair combination of a column in SQL

Lets say I have given table:
1 A
2 A
3 A
How do I JOIN / combine the table with itself so I get every possible unique pair combination of the first column:
1 1 A
1 2 A
1 3 A
2 1 A
2 2 A
2 3 A
...
You can do something like this.
Cross JOIN is used for cross product
-- create
CREATE TABLE EMPLOYEE (
empId INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
-- insert
INSERT INTO EMPLOYEE VALUES (0001, 'Clark');
INSERT INTO EMPLOYEE VALUES (0002, 'Dave');
INSERT INTO EMPLOYEE VALUES (0003, 'Ava');
-- fetch
SELECT e1.empId, e2.empId, e1.name FROM EMPLOYEE e1
CROSS JOIN EMPLOYEE e2;

ORACLE: Creating unique values without using sequence while multi-inserts

There is a parent table (MAIN_TABLE) and need to do multi-inserts into a child table(CHILD_TABLE)
for various types (TYPE_1/TYPE_2/TYPE_3 etc).
That is for single parent record I will have three childs if there are three types.
WITH MAIN_TABLE AS
(
SELECT 100 AS ID,'RICK' AS NAME,5 AS LINE FROM DUAL
UNION ALL
SELECT 101 AS ID,'TOM' AS NAME,6 AS LINE FROM DUAL
)
SELECT * FROM MAIN_TABLE;
ID NAME LINE
---------- -------------------- ----------
100 RICK 5
101 TOM 6
2 rows selected.
Need to insert parent record into the child table where for the line column it has to be populated in the increasing order.
INSERT ALL
INTO CHILD_TABLE(ID, NAME, LINE, TYPE)
VALUES (ID, NAME, LINE, 'TYPE_1')
INTO CHILD_TABLE(ID, NAME, LINE, TYPE)
VALUES (ID, NAME, LINE, 'TYPE_2')
INTO CHILD_TABLE(ID, NAME, LINE, TYPE)
VALUES (ID, NAME, LINE, 'TYPE_3')
INTO CHILD_TABLE(ID, NAME, LINE, TYPE)
VALUES (ID, NAME, LINE, 'TYPE_4')
SELECT ID, NAME, LINE
FROM MAIN_TABLE;
SQL> SELECT * FROM CHILD_TABLE
100 RICK 5 TYPE_1
101 TOM 6 TYPE_1
100 RICK 5 TYPE_2
101 TOM 6 TYPE_2
100 RICK 5 TYPE_3
101 TOM 6 TYPE_3
100 RICK 5 TYPE_4
101 TOM 6 TYPE_4
8 rows selected.
Here, instead of 5,6 as the line, I need to have 5,6,7,8,9,10,11,12.
How to go about this?
Note: 1. I cannot create sequence 2. No multiple separate inserts statements if possible.
I certainly hope this is a homework assignment:
INSERT INTO CHILD_TABLE
SELECT m.ID,
m.NAME,
o.MIN_LINE + ROWNUM - 1 AS LINE,
t.TYPE
FROM MAIN_TABLE m
CROSS JOIN (SELECT MIN(LINE) AS MIN_LINE FROM MAIN_TABLE) o
CROSS JOIN (SELECT 'TYPE_1' AS TYPE FROM DUAL UNION ALL
SELECT 'TYPE_2' AS TYPE FROM DUAL UNION ALL
SELECT 'TYPE_3' AS TYPE FROM DUAL UNION ALL
SELECT 'TYPE_4' AS TYPE FROM DUAL) t;
dbfiddle here
I would strongly recommend using an Oracle sequence whenever its possible to. If you still feel the strong need for not using a sequence then try building pseudo-sequence tables and write some PLSQL functions to perform the inserts by choosing the available sequence + 1.
Or you could do something like
creating a variable for your current max value and try to use ROW_NUMBER() OVER (ORDER BY column_name)+#your_var where #your_var= SELECT MAX(column_name) FROM child

Derive groups of records that match over multiple columns, but where some column values might be NULL

I would like an efficient means of deriving groups of matching records across multiple fields. Let's say I have the following table:
CREATE TABLE cust
(
id INT NOT NULL,
class VARCHAR(1) NULL,
cust_type VARCHAR(1) NULL,
terms VARCHAR(1) NULL
);
INSERT INTO cust
VALUES
(1,'A',NULL,'C'),
(2,NULL,'B','C'),
(3,'A','B',NULL),
(4,NULL,NULL,'C'),
(5,'D','E',NULL),
(6,'D',NULL,NULL);
What I am looking to get is the set of IDs for which matching values unify a set of records over the three fields (class, cust_type and terms), so that I can apply a unique ID to the group.
In the example, records 1-4 constitute one match group over the three fields, while records 5-6 form a separate match.
The following does the job:
SELECT
DISTINCT
a.id,
DENSE_RANK() OVER (ORDER BY max(b.class),max(b.cust_type),max(b.terms)) AS match_group
FROM cust AS a
INNER JOIN
cust AS b
ON
a.class = b.class
OR a.cust_type = b.cust_type
OR a.terms = b.terms
GROUP BY a.id
ORDER BY a.id
id match_group
-- -----------
1 1
2 1
3 1
4 1
5 2
6 2
**But, is there a better way?** Running this query on a table of over a million rows is painful...
As Graham pointed out in the comments, the above query doesn't satisfy the requirements if another record is added that would group all the records together.
The following values should be grouped together in one group:
INSERT INTO cust
VALUES
(1,'A',NULL,'C'),
(2,NULL,'B','C'),
(3,'A','B',NULL),
(4,NULL,NULL,'C'),
(5,'D','E',NULL),
(6,'D',NULL,NULL),
(7,'D','B','C');
Would yield:
id match_group
-- -----------
1 1
2 1
3 1
4 1
5 1
6 1
...because the class value of D groups records 5, 6 and 7. The terms value of C matches records 1, 2 and 4 to that group, and cust_type value B ( or class value A) pulls in record 3.
Hopefully that all makes sense.
I don't think you can do this with a (recursive) Select.
I did something similar (trying to identify unique households) using a temporary table & repeated updates using following logic:
For each class|cust_type|terms get the minimum id and update that temp table:
update temp
from
(
SELECT
class, -- similar for cust_type & terms
min(id) as min_id
from temp
group by class
) x
set id = min_id
where temp.class = x.class
and temp.id <> x.min_id
;
Repeat all three updates until none of them updates a row.

how to update multiple rows in oracle

I would like to update multiple rows with different values for all different records, but don't have any idea how to do that, i am using below sql to update for single record but i have 200 plus records to update
update employee
set staff_no = 'ab123'
where depno = 1
i have 50 dep and within those dep i need to update 200 plus staff no. any idea.
At the moment if i just do a
select * from Departments
i can see list of all employee which needs staff no updating.
UPDATE person
SET staff_no =
CASE person_no
WHEN 112 THEN 'ab123'
WHEN 223 THEN 'ab324'
WHEN 2343 THEN 'asb324'
and so on.....
END
You should be able to use MERGE statement to do it in a single shot. However, the statement is going to be rather large:
MERGE INTO employee e
USING (
SELECT 1 as d_id, 'cd234' as staff_no FROM Dual
UNION ALL
SELECT 2 as d_id, 'ef345' as staff_no FROM Dual
UNION ALL
SELECT 3 as d_id, 'fg456' as staff_no FROM Dual
UNION ALL
... -- More selects go here
SELECT 200 as d_id, 'za978' as staff_no FROM Dual
) s
ON (e.depno = S.d_id)
WHEN MATCHED THEN UPDATE SET e.staff_no= s.staff_no
use a case expression
UPDATE employee
SET staff_no =
CASE depno
WHEN 1 THEN 'ab123'
WHEN 2 THEN 'ab321'
--...
ELSE staff_no
END
WHERE depno IN ( 1, 2 ) -- list all cases here. use a subquery if you don't want to / cannot enumerate
For conditional update, you could use multiple update statements, or use CASE expression in the SET clause.
Something like,
UPDATE table
SET schema.column = CASE
WHEN column1= 'value1' AND column2='value2' THEN
'Y'
ELSE
'N'
END
I wish you tried to search for a similar question on this site, there was a recent question and this was my answer.
If you have two tables like:
CREATE TABLE test_tab_1 (id NUMBER, name VARCHAR2(25));
CREATE TABLE test_tab_2 (id NUMBER, name VARCHAR2(25));
You can use UPDATE statement as below:
UPDATE test_tab_1
SET test_tab_1.name = (SELECT test_tab_2.name FROM test_tab_2
WHERE test_tab_1.id = test_tab_2.id);

MySQL count() problem

Setup:
create table main(id integer unsigned);
create table test1(id integer unsigned);
create table test2(id integer unsigned);
insert into main(id) value(1);
insert into test1(id) value(1);
insert into test1(id) value(1);
insert into test2(id) value(1);
insert into test2(id) value(1);
insert into test2(id) value(1);
Using:
select main.id,
count(test1.id),
count(test2.id)
from main
left join test1 on main.id=test1.id
left join test2 on main.id=test2.id
group by main.id;
...returns:
+------+-----------------+-----------------+
| id | count(test1.id) | count(test2.id) |
+------+-----------------+-----------------+
| 1 | 6 | 6 |
+------+-----------------+-----------------+
How to get the desired result of 1 2 3?
EDIT
The solution should be extensible,I'm going to query multiple count() information about main.id in the future.
Not optimal, but works:
select
count(*),
(select count(*) from test1 where test1.id = main.id) as test1_count,
(select count(*) from test2 where test2.id = main.id) as test2_count
from main
You created tables that contain the following:
Table main
id
----
1
Table test1
id
----
1
1
Table test2
id
----
1
1
1
When you join this like you do you will get the following
id id id
-----------
1 1 1
1 1 1
1 1 1
1 1 1
1 1 1
1 1 1
So how should SQL answer differently?
You can call:
SELECT id,COUNT(id) FROM main GROUP BY id
for every table, then join them by id.
Not sure if this works in MySQL exactly as written (I'm using Oracle):
1 select main.id, t1.rowcount, t2.rowcount
2 from main
3 left join (select id,count(*) rowcount from test1 group by id) t1
4 on t1.id = main.id
5 left join (select id,count(*) rowcount from test2 group by id) t2
6* on t2.id = main.id
SQL> /
ID ROWCOUNT ROWCOUNT
1 2 3
You're inadvertently creating a Cartesian product between test1 and test2, so every matching row in test1 is combined with every matching row in test2. The result of both counts, therefore, is the count of matching rows in test1 multiplied by the count of matching rows in test2.
This is a common SQL antipattern. A lot of people have this problem, because they think they have to get both counts in a single query.
Some other folks on this thread have suggested ways of compensating for the Cartesian product through creative use of subqueries, but the solution is simply to run two separate queries:
select main.id, count(test1.id)
from main
left join test1 on main.id=test1.id
group by main.id;
select main.id, count(test2.id)
from main
left join test2 on main.id=test2.id
group by main.id;
You don't have to do every task in a single SQL query! Frequently it's easier to code -- and easier for the RDBMS to execute -- multiple simpler queries.
You can get the desired result by using:
SELECT COUNT(*) as main_count,
(SELECT COUNT(*) FROM table1) as table1Count,
(SELECT COUNT(*) from table2) as table2Count FROM main