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

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

Related

How to unescape % in LIKE clause

I have my search patterns stored in database in patterns table. For example my table column name_pattern contains string 'Basic%'. I'd like to create dynamic search where search patterns will be fetched from name_pattern column.
So my SQL query should look something like:
SELECT *
FROM products
WHERE product_name LIKE name_pattern <-- somehow joined from patterns table
Seems that Oracle escapes % in my string but I want to take it unescapped in order my query to work like:
SELECT *
FROM products
WHERE product_name LIKE 'Basic%'
I found that my problem is with stable set of rows:
CREATE TABLE patterns(code CHAR(1),name_pattern VARCHAR2(20));
INSERT INTO patterns(code,name_pattern) VALUES('B','Basic%');
INSERT INTO patterns(code,name_pattern) VALUES('T','%thing');
CREATE TABLE products (id NUMBER,name VARCHAR2(20),code CHAR(1));
INSERT INTO products(id,name,found) VALUES(1,'Basic instinct',NULL);
INSERT INTO products(id,name,found) VALUES(2,'Basic thing',NULL);
INSERT INTO products(id,name,found) VALUES(3,'Super thing',NULL);
INSERT INTO products(id,name,found) VALUES(4,'Hyper instinct',NULL);
MERGE INTO products p USING
(
SELECT code,name_pattern FROM patterns
) s
ON (p.name LIKE s.name_pattern)
WHEN MATCHED THEN UPDATE SET p.code=s.code;
SELECT * FROM products;
If my search patterns were Basic% and Super% in patterns table then this MERGE will work, but if my search patterns are Basic% and %thing, the second product should be marked with both codes 'B' and 'T' and that causes the error:
ORA-30926: unable to get a stable set of rows in the source tables
So my problem is not in (un)escaping :-(, sorry
You don't have to (un)escape anything, I'd say.
SQL> with
2 patterns (name_pattern) as
3 (select 'Basic%' from dual union all
4 select '%foot%' from dual
5 ),
6 products (id, name) as
7 (select 1, 'Basic instinct' from dual union all
8 select 2, 'Visual Basic' from dual union all
9 select 3, 'Littlefoot' from dual union all
10 select 4, 'Happy feet' from dual
11 )
12 select b.id, b.name, a.name_pattern
13 from products b join patterns a on b.name like a.name_pattern;
ID NAME NAME_P
---------- -------------- ------
1 Basic instinct Basic%
3 Littlefoot %foot%
SQL>
Based on test case you provided: don't merge, update!
SQL> update products p set
2 p.found = 1
3 where exists (select null
4 from patterns o
5 where p.name like o.name_pattern
6 );
3 rows updated.
SQL> select * from products;
ID NAME FOUND
---------- -------------------- ----------
1 Basic instinct 1
2 Basic thing 1
3 Super thing 1
4 Hyper instinct 0
SQL>
After you changed your mind (again), it is still update. Though, you didn't explain which code you want to take when there's multiple match (for example, product 2 matches both "Basic%" and "%thing") so I took any of them, using the min function.
SQL> update products p set
2 p.code = (select min(o.code)
3 from patterns o
4 where p.name like o.name_pattern
5 );
4 rows updated.
SQL> select * from products;
ID NAME CODE
---------- -------------------- ----------
1 Basic instinct B
2 Basic thing B
3 Super thing T
4 Hyper instinct NULL
SQL>

Return list of supplied values that are not in table in Oracle

There is an EMPLOYEE table as below
EPM_ID
------
1001
1002
1004
And I have a list of EPM_IDs which I want to validate:
(1000, 1001, 1002, 1003, 1004, 1005)
How to write Oracle SQL query only (without creating any temporary table) to return list of EPM_IDs that are not in the EMPLOYEE table, but are in my list? For instance, the result should be 1000, 1003, 1005.
This will do it:
select EPM_IDs
from
(
select 1000 as EPM_IDs union
select 1001 union
select 1002 union
select 1003 union
select 1004 union
select 1005
)a
where not exists(select 1 from EMPLOYEE e where a.EPM_IDs = e.EPM_IDs)
You can use connect by clause to convert your list to rows and then do MINUS operation as follows:
SQL> -- GENERATING SAME DATA AS YOUR TABLE
SQL> WITH YOUR_DATAA (EMPLOYEE_ID) AS
2 (SELECT '1001' FROM DUAL UNION ALL
3 SELECT '1002' FROM DUAL UNION ALL
4 SELECT '1004' FROM DUAL),
5 -- YOUR QUERY STARTS FROM HERE -- WITH
6 YOUR_LIST (LST) AS
7 (SELECT '1000, 1001, 1002, 1003, 1004, 1005' FROM DUAL)
8 --
9 SELECT TRIM(REGEXP_SUBSTR(LST, '[^,]+', 1, LEVEL)) AS EMPLOYEE_IDS
10 FROM YOUR_LIST
11 CONNECT BY LEVEL <= REGEXP_COUNT(LST, ',') + 1
12 MINUS
13 SELECT EMPLOYEE_ID
14 FROM YOUR_DATAA;
EMPLOYEE_IDS
--------------------------------------------------------------------------------
1000
1003
1005
SQL>
Cheers!!
Can you please let us know the Oracle database version which you are using and the output of DESC command of the Employee Table?
Also, please let us know the number of rows in your table (I.e., existing no of rows in the table) and the length/size of the list of employee IDs which you're trying to find/not-find from your table?
Also, this list of employee IDs which you have : are they stored in a File, or another table, or in a webpage, etc.? Kindly advise.
This will help answer your question further.
Regards!
Suddhasatwa
So, here is the solution which saved my day.
I had restrictions to create temporary table in production DB which is a simple and obvious solution to solve this problem.
SELECT column_value AS EMP_ID
FROM TABLE(sys.Odcinumberlist(1000, 1001, 1002, 1003, 1004, 1005))
MINUS
SELECT emp_id
FROM employee
Explanation : table(sys.odcinumberlist(1000, 1001, 1002, 1003, 1004, 1005)) converts the supplied values to a table like structure with single column named EMP_ID like below. Then normal SQL MINUS operation is applied on it.
EMP_ID
------
1000
1001
1002
1003
1004
1005
(Note : sys.odcinumberlist() has limitation to take maximum 999 arguments)
Now don't ask me what exactly sys.odcinumberlist() creates internally. I am not a DB professional. Please refer http://www.dba-oracle.com/t_advanced_sql_table_expressions.htm

How to sum column values from two tables and preserve another column which is only in one table

i have created tables item and item2, I know maybe it's data redundancy but i want to know how can select it, and create a view?
create table item(
id number(10) primary key,
name varchar2(20),
mark number(10));
insert into item values(10,'Apple1',23);
insert into item values(11,'Apple2',0);
insert into item values(12,'Apple3',0);
insert into item values(13,'Apple4',0);
insert into item values(14,'Apple4',0);
insert into item values(15,'Apple4',0);
insert into item values(16,'Apple4',0);
create table item2(
id number(10),
mark number(10));
alter table item2 add(constraint id_fk FOREIGN KEY (id) references item(id));
Insert into item2 values(10,1);
Insert into item2 values(10,1);
Insert into item2 values(11,7);
Insert into item2 values(12,14);
I can query both:
select * from item;
ID Name Mark
10 Apple1 23
11 Apple2 0
12 Apple3 0
13 Apple4 0
14 Apple4 0
15 Apple4 0
16 Apple4 0
select * from item2;
ID Mark
10 1
10 1
11 7
12 14
I want to get the result set below using the select statement sum from the item and item2 tables:
ID Name Mark
10 Apple1 25
11 Apple2 7
12 Apple3 14
13 Apple4 0
14 Apple4 0
15 Apple4 0
16 Apple4 0
How can I combine my queries to produce that output?
If I understand this correctly, you want to "pretend" that the second table had the NAME column also, populated according to the first table; then you would want to GROUP BY id and get the sum of MARK.
If so, instead of joining the tables to get the names (either before or after combining the tables and computing the sums), you can use a UNION ALL, in which you insert a fake NAME column with NULL in it for the second table; then you group by id, you sum the MARK column, and you take the MAX over NAME. MAX ignores NULL, so it will just pick the name from table ITEM.
The solution below follows that logic in every detail.
select id, max(name) as name, sum(mark) as mark
from ( select id, name, mark
from item
union all
select id, null as name, mark
from item2
)
group by id
;
You can union the two tables together on the id and mark. The name you can either add a null name column into the union and do a max/min on that field to get one value from that table. Otherwise you can union the id and marks, and then join back to the original table with the name to grab it from there and include it in the group by.
select item_table.id, item_table.name, sum(mark_data_set.mark) as mark_score
from
(select
id, mark
from item
union all
select id, mark
from item2
) mark_data_set
inner join item item_table on (mark_data_set.id = item_table.id)
group by item_table.id, item_table.name
How about this?
select id,name,(m1 +nvl(m2,0)) mark
from
(select t1.id,t1.name,t1.mark m1,t2.mark m2
from
item t1
LEFT OUTER JOIN
(select id,sum(mark) mark from item2
group by id) t2
ON
t2.id = t1.id
)
order by id;

Update then insert in oracle

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.

How to add sequence number for groups in a SQL query without temp tables

I've created a complex search query in SQL 2008 that returns data sorted by groups, and the query itself has paging and sorting functions in it, but rather than returning a set number of records based on the paging options, it needs to return a set number of groups (so the number of records will vary).
I'm currently doing this through the use of Temp Tables (the first temp table creates a list of the Groups that will be selected as part of the search, and then numbers them... and the second query joins this table to the actual search... so, it ends up running the search query twice).
What I'm looking for is a more efficient way to do this using some of the new functions in SQL 2008 (which wouldn't require the use of temp tables).
If I can get the data in a format like this, I'd be set...
Record Group GroupSequence
-------|---------|--------------
1 Chickens 1
2 Chickens 1
3 Cows 2
4 Horses 3
5 Horses 3
6 Horses 3
Any ideas on how to accomplish this with a single query in SQL 2008, without using temp tables?
Sample data
create table sometable([group] varchar(10), id int, somedata int)
insert sometable select 'Horses', 9, 11
insert sometable select 'chickens', 19, 121
insert sometable select 'Horses', 29, 123
insert sometable select 'chickens', 49, 124
insert sometable select 'Cows', 98, 1
insert sometable select 'Horses', 99, 2
Query
select
Record = ROW_NUMBER() over (order by [Group], id),
[Group],
GroupSequence = DENSE_RANK() over (order by [Group])
from sometable
Output
Record Group GroupSequence
-------------------- ---------- --------------------
1 chickens 1
2 chickens 1
3 Cows 2
4 Horses 3
5 Horses 3
6 Horses 3
Without more details about the tables you have, I'd say look into CTE queries and the row_number function... something along the lines of:
;with groups as (
select top 10 name, row_number() over(order by name) 'sequence'
from table1
group by name
order by name
)
select row_number() over(order by g.name) 'Record',
g.name 'GroupName',
g.sequence 'GroupSequence'
from groups