Inner Join with Sum in oracle SQL - sql

I'm using Oracle SQL and I have two tables, invoice and invoice_item.
invoice:
id(pk) total_invoice_price
1
2
invoice_item:
invoice total_item_price
1 10
1 20
2 25
2 35
I need that total_invoice_price be the sum of every total_item_price where invoice = id.
invoice_item.invoice is a fk that references to invoice.id
The best I could make was in the lines of:
update(
select invoice.total_invoice_price as old, SUM(invoice_item.total_item_price) as total
from invoice
inner join invoice_item
on invoice.id = invoice_item.invoice
) t
set t.old = t.total;
but it obviously doesn't work.
Tables creation:
create table invoice(
id number(5) not null,
customer_name varchar2(50) not null,
issue_date date not null,
due_date date not null,
comments varchar2(50) ,
total_invoice_price number(9) ,
constraint pk_invoice
primary key (id)
);
create table invoice_item(
id number(5) not null,
product_name varchar2(50) not null,
unit_price number(9) not null,
quantity number(9) not null,
total_item_price number(9) ,
invoice number(5) not null,
constraint pk_invoice_item
primary key (id),
constraint fk_invoice_item_invoice
foreign key (invoice)
references invoice(id)
);

I would use Merge. See below
MERGE INTO invoice tb1
USING ( SELECT invoice, SUM (total_item_price) tot_price
FROM invoice_item
GROUP BY invoice) tb2
ON (tb1.id = tb2.invoice)
WHEN MATCHED
THEN
UPDATE SET tb1.total_invoice_price = tb2.tot_price;

update
( select i.total_invoice_price, x.total_price
from invoice i
join
(
select invoice as id, sum(total_item_price) as total_price
from invoice_item
group by invoice
) x
on i.id = x.id
)
set total_invoice_price = total_price
;
Comments:
You need to aggregate within the second table, before joining. Then you join by id. In this arrangement, you will never run into issues with "uniqueness" or "primary key" being defined; the only condition that matters is that the id be unique in the "other" table, which in this case is the subquery x. Since it is an aggregation where you group by id, that uniqueness is guaranteed by the very definition of GROUP BY.
Then: It is unfortunate that you have a table invoice and a column (in a different table) also called invoice; the invoice id column should be called something like invoice_id in both tables. In my subquery, I changed the column name (from the second table) from invoice to id, by giving it that alias in the SELECT clause of the subquery.
Further comment: In a comment below this replies, the OP says he ran into an error. That means he didn't use the solution as I wrote it above. Since this is really annoying, I decided to present a full SQL*Plus session to prove that the solution is correct as written.
Create table INVOICE:
SQL> create table invoice ( id, total_invoice_price ) as
2 select 1, cast(null as number) from dual union all
3 select 2, null from dual;
Table created.
Elapsed: 00:00:00.01
SQL> select * from invoice;
ID TOTAL_INVOICE_PRICE
---------- -------------------
1
2
2 rows selected.
Create table INVOICE_ITEM:
Elapsed: 00:00:00.00
SQL> create table invoice_item ( invoice, total_item_price ) as
2 select 1, 10 from dual union all
3 select 1, 20 from dual union all
4 select 2, 25 from dual union all
5 select 2, 35 from dual;
Table created.
Elapsed: 00:00:00.01
SQL> select * from invoice_item;
INVOICE TOTAL_ITEM_PRICE
---------- ----------------
1 10
1 20
2 25
2 35
4 rows selected.
Elapsed: 00:00:00.00
UPDATE statement:
SQL> update
2 ( select i.total_invoice_price, x.total_price
3 from invoice i
4 join
5 (
6 select invoice as id, sum(total_item_price) as total_price
7 from invoice_item
8 group by invoice
9 ) x
10 on i.id = x.id
11 )
12 set total_invoice_price = total_price
13 ;
2 rows updated.
Elapsed: 00:00:00.01
SQL> select * from invoice;
ID TOTAL_INVOICE_PRICE
---------- -------------------
1 30
2 60
2 rows selected.
Elapsed: 00:00:00.02
Clean-up:
SQL> drop table invoice purge;
Table dropped.
Elapsed: 00:00:00.02
SQL> drop table invoice_item purge;
Table dropped.
Elapsed: 00:00:00.01
SQL>

Related

How to write a simple query or PL/SQL code in Oracle to check if order_id exists in one table before processing another table?

I am looking to populate a table with some data and normally I would run some insert scripts or upload via a csv file. The requirements I have is that the data can only be populated in the 2nd table as long as the order_id is within the orders table.
From what I know I probably need to write some PL/SQL code to check if the order_id exists in the orders table before running the insert scripts but not sure how to write this. I would appreciate it if somebody could get me started.
This is the create statement for the Orders Table:
CREATE TABLE ORDERS (
ORDER_ID NUMBER NOT NULL,
STATUS VARCHAR2(9) NOT NULL,
ORDER_DATE DATE NOT NULL,
PRIMARY KEY(ORDER_ID),
CONSTRAINT CHK_STATUS CHECK (STATUS = 'OPEN' OR STATUS = 'CLOSED')
);
The create statement for the 2nd table is:
CREATE TABLE ORDER2
(
ORDER_ID NUMBER NOT NULL,
PRODUCT_ID NUMBER NOT NULL,
ORDER_DATE DATE NOT NULL,
PRIMARY KEY(PRODUCT_ID)
);
Thanks.
Well yes, you could check it manually. As HoneyBadger commented, exists is a way to do that, for example:
SQL> insert into orders (order_id, status, order_date)
2 select 1, 'OPEN' , trunc(sysdate - 2) from dual union all
3 select 2, 'CLOSED', trunc(sysdate - 1) from dual;
2 rows created.
SQL> select * from orders;
ORDER_ID STATUS ORDER_DATE
---------- ------ ----------
1 OPEN 03.06.2022
2 CLOSED 04.06.2022
Let's try to insert order_id = 1 into order2:
SQL> insert into order2 (product_id, order_id, order_date)
2 select 100, 1, trunc(sysdate - 2) from dual
3 where exists (select null
4 from orders
5 where order_id = 1);
1 row created.
It succeeded as order_id = 1 exists in orders table. What about order_id = 3 which doesn't exist there?
SQL> insert into order2 (product_id, order_id, order_date)
2 select 300, 3, trunc(sysdate) from dual
3 where exists (select null
4 from orders
5 where order_id = 3);
0 rows created.
SQL>
Right, nothing was inserted.
But, why wouldn't you let the database do it for you? Create a foreign key constraint which won't let any rows to be inserted into the order2 table unless that order_id exists in the orders table:
SQL> create table orders (
2 order_id number constraint pk_ord primary key,
3 status varchar2(6) constraint chk_ord_stat check (status in ('OPEN', 'CLOSED'))
4 not null,
5 order_date date not null
6 );
Table created.
SQL> create table order2 (
2 product_id number constraint pk_ord2 primary key,
3 order_id number constraint fk_ord2_ord references orders (order_id)
4 not null,
5 order_date date not null
6 );
Table created.
SQL>
Testing:
SQL> insert into order2 (product_id, order_id, order_date)
2 values (300, 3, trunc(sysdate));
insert into order2 (product_id, order_id, order_date)
*
ERROR at line 1:
ORA-02291: integrity constraint (SCOTT.FK_ORD2_ORD) violated - parent key not
found
SQL>
See? Oracle won't let you do that, you don't have to check anything.
On the other hand, why two tables? Most columns are common (I presume they also share common data), so perhaps you could just add product_id into orders (I don't know whether order_id and product_id make the primary key, though):
SQL> create table orders (
2 order_id number,
3 product_id number,
4 status varchar2(6) constraint chk_ord_stat check (status in ('OPEN', 'CLOSED'))
5 not null,
6 order_date date not null,
7 --
8 constraint pk_ord primary key (order_id, product_id)
9 );
Table created.
SQL>

How to create random function using a stored procedure? PL/SQL

I have a procedure that adds data
add_price (cust_id customers.id%type,
items_id items.id%type,
price number);
and I want to create a function that for each combination of customers and items to create an additional one at random entry in the table price.
How can I do that?
UPD: Please note, I believe the idea from MT0 is better because you'll need only one insert statement. My solution is for the case when using add_price function is required
So, "each combination of customers and items" means you need a cartesian product:
select cust_id, item_id
from customers
cross join items;
For example if you had following data in "customers" and "items" table:
cust_id
cust_name
1
A
2
B
item_id
item_name
1
a
2
b
the query above would return:
cust_id
item_id
1
1
1
2
2
1
2
2
Thus, all is left is to get random value. Use dbms_random.value for that
begin
for q in (select cust_id, item_id from customers cross join items) loop
add_price(q.cust_id, q.item_id, round(dbms_random.value(10, 1000), 2));
end loop;
end;
The parameters for value are lowes_value and highest_value so the result will be between those numbers. You probably will need to set them somehow. And rounding will be needed too
Don't use a function, create a procedure and use INSERT ... SELECT with the CROSS JOIN of the customers and the items tables:
CREATE PROCEDURE generate_random_prices
IS
BEGIN
INSERT INTO prices (customer_id, item_id, price)
SELECT c.customer_id,
i.item_id,
ROUND(DBMS_RANDOM.VALUE(0,100),2)
FROM customers c
CROSS JOIN items i;
END generate_random_prices;
/
Which, if you have the sample data:
CREATE TABLE customers (customer_id PRIMARY KEY) AS
SELECT COLUMN_VALUE FROM TABLE(SYS.ODCINUMBERLIST(1,5,42));
CREATE TABLE items (item_id PRIMARY KEY) AS
SELECT COLUMN_VALUE FROM TABLE(SYS.ODCINUMBERLIST(1,3,61));
CREATE TABLE prices (
customer_id REFERENCES customers(customer_id),
item_id REFERENCES items(item_id),
price NUMBER(4,2)
);
Then after:
BEGIN
generate_random_prices();
END;
/
The prices table may (randomly) contain:
CUSTOMER_ID
ITEM_ID
PRICE
1
1
38.91
1
3
39.74
1
61
67.28
5
1
13.92
5
3
48.17
5
61
70.21
42
1
90.33
42
3
5.7
42
61
40.37
If you want to call your ADD_PRICE procedure then just take the same CROSS JOIN query and use a cursor loop:
CREATE PROCEDURE generate_random_prices
IS
BEGIN
FOR rw IN (SELECT c.customer_id,
i.item_id
FROM customers c
CROSS JOIN items i)
LOOP
ADD_PRICE(rw.customer_id, rw.item_id, ROUND(DBMS_RANDOM.VALUE(0,100),2));
END LOOP;
END generate_random_prices;
/
(But it will be more efficient to just use a single INSERT ... SELECT statement.)
db<.fiddle here

I need to create a Table from two unrelated old tables in sql developer

My problem is that I need to create the table USER_LIBRARY from this two unrelated tables:
USER:
ID_USER NUMBER(9) PRIMARY KEY,
NAME VARCHAR2(50),
SURNAME VARCHAR2(50),
CITY VARCHAR2(50),
ADDRESS VARCHAR2(50),
AGE NUMBER(9),
EMAIL VARCHAR2(50)
LIBRARY:
id_library number(9) PRIMARY KEY NOT NULL,
name VARCHAR2(50) NOT NULL,
city VARCHAR2(50) NOT NULL,
address VARCHAR2(50) NOT NULL,
CP number(9) NOT NULL
I try with an INNER JOIN and with UNION but it doesn´t work, I'm new to Oracle so I know is a basic question but I didn´t know how to solve.
The final table will depend on how many columns of both input tables you want to use. As you have columns with the same name, you need to use alias to resolve this in a good way. Another thing is what result you expect. Normally a user may belong to one or more libraries, but as you have no way to relate both tables, you will get a cartesian product.
Let's assume you want all of them
create table user_library as
select u.id_user ,
l.id_library,
u.name as user_name,
u.surname ,
u.city as user_city ,
u.address as user_address,
u.age, u.email ,
l.name as library_name,
l.city as library_city,
l.address as library_address ,
l.cp as library_cp
from user u cross join library l ;
Let me explain what I mean with an example
SQL> create table test1 ( c1 number , c2 number );
Table created.
SQL> insert into test1 values ( 1 , 1 );
1 row created.
SQL> insert into test1 values ( 2 , 2 );
1 row created.
SQL> commit;
Commit complete.
SQL> create table test2 ( c3 number, c4 number );
Table created.
SQL> insert into test2 values ( 3 , 3 );
1 row created.
SQL> insert into test2 values ( 4 , 4 );
1 row created.
SQL> create table test3 as select a.c1 , a.c2 , b.c3 , b.c4 from test1 a cross join test2 b ;
Table created.
SQL> select * from test1 ;
C1 C2
---------- ----------
1 1
2 2
SQL> select * from test2 ;
C3 C4
---------- ----------
3 3
4 4
SQL> select * from test3 ;
C1 C2 C3 C4
---------- ---------- ---------- ----------
1 1 3 3
1 1 4 4
2 2 3 3
2 2 4 4
SQL>
The cross join creates a row for each combination of values in table1 against table2.
If you want all users to pair with all libraries, you could use:
create table user_library as
select u.id_user, l.id_library
from user u cross join
library l;
This includes only the ids. You can use JOIN to fetch the values corresponding to a particular user and/or library.

How to generate rows for table Order_details, for each row in the Order_header table?

I have two tables. Order_header and Order_detail. For each row in the Order_header table, I want to generate 1 or more Order_detail rows and insert them into the Order_detail table. The number of detail rows for each header row is random, up to the number in the constant max_detailrows. How to proceed with the generate rows for Order_detail table?
You can make use of DBMS_RANDOM.VALUE function, to generate random value.
Use this in hierarchical query to generate rows for each row in your Order_Header table.
Schema Setup:
create table order_header(
order_id number,
order_desc varchar2(10)
);
insert into order_header values(1,'lorem');
insert into order_header values(2,'ewroc');
insert into order_header values(3,'tdsfg');
commit;
create table order_details(
order_id number,
order_detail_nr number,
order_detail_desc varchar2(20)
);
Query:
insert into order_details
select
order_id,
level,
order_desc || '_' || level
from order_header
connect by level <= dbms_random.value(1,7) --max number of rows needed should be given here.
and prior order_id = order_id
and prior sys_guid() is not null;
Output:
select * from order_details
order by 1,2;
ORDER_ID ORDER_DETAIL_NR ORDER_DETAIL_DESC
---------- --------------- ------------------
1 1 lorem_1
1 2 lorem_2
1 3 lorem_3
2 1 ewroc_1
2 2 ewroc_2
2 3 ewroc_3
2 4 ewroc_4
3 1 tdsfg_1

Inline query to update multiple rows

Here is a brief description of the tables I'm working with in Oracle 10g:
Notes:
Table : jnldetail : Single row with data as shown.
There are multiple package id's attached to the same bill_ref_no for an account. Therefore, I'm trying to update "jnldetail " with the multiple package_id's.
Relation between index_bill_ref and bill_ref_no : 1 - 1
Relation between account_no and ( index_bill_ref and bill_ref_no ) : 1 - Many
**Table : jnldetail** :
account_no bill_ref_no amount
8594822 74282843 822
I'm adding another column package_id with the following command:
alter table jnldetail add package_id number(10)
**table: bill_invoice**:
account_no bill_ref_no index_bill_ref
8594822 74282843 763653495
**table: bill_invoice_detail**:
index_bill_ref package_id component_id
763653495 20000077 20000177
763653495 20000250 20000528
763653495 13000019 13000137
**Expected Result:**
**Table : jnldetail** :
account_no bill_ref_no amount package_id
8594822 74282843 822 20000077
8594822 74282843 822 20000250
8594822 74282843 822 13000019
My Query is:
UPDATE jnldetail tp
SET tp.package_id = (
select
t1.package_id
from bill_invoice_detail t1
, bill_invoice t2
where
t1.index_bill_ref = t2.index_bill_ref
and
t2.account_no = tp.account_no
)
The error message is : ora 01427 : single row subquery returns more than one row
Any inputs will be helpful.
Thanks!
The problem is that you're trying to set tp.package_id to more than one number, because your subquery is returning more than one result, e.g. 20000077 and 13000019. You'll need to alter the subquery so that only one value is returned.
Why not keep the tables separate and use a join when you are ready to get the complete data?
This is tricky for two reasons:
1) you want to update the existing row, and want to add two new rows
2) the two new rows need the data from both the original jnldetail table (amount) and the bill_invoice tables (package_id)
To address 1, you can use the MERGE statement, but because of 2, the jnldetail is needed in the using clause of the MERGE statement.
Here is your example:
SQL> create table jnldetail (account_no, bill_ref_no, amount)
2 as
3 select 8594822, 74282843, 822 from dual
4 /
Tabel is aangemaakt.
SQL> alter table jnldetail add package_id number(10)
2 /
Tabel is gewijzigd.
SQL> create table bill_invoice (account_no, bill_ref_no, index_bill_ref)
2 as
3 select 8594822, 74282843, 763653495 from dual
4 /
Tabel is aangemaakt.
SQL> create table bill_invoice_detail (index_bill_ref, package_id, component_id)
2 as
3 select 763653495, 20000077, 20000177 from dual union all
4 select 763653495, 20000250, 20000528 from dual union all
5 select 763653495, 13000019, 13000137 from dual
6 /
Tabel is aangemaakt.
The tables as you described them.
SQL> UPDATE jnldetail tp
2 SET tp.package_id =
3 ( select t1.package_id
4 from bill_invoice_detail t1
5 , bill_invoice t2
6 where t1.index_bill_ref = t2.index_bill_ref
7 and t2.account_no = tp.account_no
8 )
9 /
( select t1.package_id
*
FOUT in regel 3:
.ORA-01427: single-row subquery returns more than one row
Your update statement fails, because you try to assign the result of a 3-rows-returning-query to a single column.
Here is the MERGE statement:
SQL> merge into jnldetail jd
2 using ( select bi.account_no
3 , bi.bill_ref_no
4 , jd.amount
5 , bid.package_id
6 , row_number() over (partition by bi.account_no,bi.bill_ref_no,bi.index_bill_ref order by null) rn
7 from bill_invoice bi
8 , bill_invoice_detail bid
9 , jnldetail jd
10 where bi.index_bill_ref = bid.index_bill_ref
11 and bi.account_no = jd.account_no
12 and bi.bill_ref_no = jd.bill_ref_no
13 ) bi
14 on ( jd.account_no = bi.account_no
15 and jd.bill_ref_no = bi.bill_ref_no
16 and bi.rn = 1
17 )
18 when matched then
19 update set package_id = package_id
20 when not matched then
21 insert values (bi.account_no,bi.bill_ref_no,bi.amount,bi.package_id)
22 /
3 rijen zijn samengevoegd.
Note that we pick an arbitrary row to be updated: the one with rn = 1.
It leads to the desired result set:
SQL> select * from jnldetail
2 /
ACCOUNT_NO BILL_REF_NO AMOUNT PACKAGE_ID
---------- ----------- ---------- ----------
8594822 74282843 822 13000019
8594822 74282843 822 20000077
8594822 74282843 822 20000250
3 rijen zijn geselecteerd.
Regards,
Rob.