How to get customer's items purchased? - sql

I have two tables:
customer(customer_id (PK), first_name, last_name, phone)
orders(order_id (PK), item_name, quantity)
With customer_id = order_id (one order_id per customer, that identifies the customer).
Inside my database I have:
INSERT INTO customer(first_name, last_name, phone) VALUES ('Colin', 'Farell', '123453');
INSERT INTO customer(first_name, last_name, phone) VALUES ('Aaron', 'Smith', '123451');
INSERT INTO customer(first_name, last_name, phone) VALUES ('Becky', 'Roberts', '123452');
INSERT INTO orders(item_name, quantity) VALUES ('Tissues', 2);
INSERT INTO orders(item_name, quantity) VALUES ('Lamp', 1);
INSERT INTO orders(item_name, quantity) VALUES ('Chocolate', 3);
How can I get the list of items a specific customer bought? For instance, Colin bought 2 tissues, a lamp and 3 chocolates.
The right output would be (without the first_name repeating):
First_name - Item_name - Quantity
Colin - Tissues - 2
Colin - Lamp - 1
Colin - Chocolate - 3
I tried:
SELECT customer.first_name, customer.last_name, orders.item_name, orders.quantity
FROM customer, orders
WHERE customer.customer_id = orders.order_id
ORDER BY customer.first_name;
But it shows all the customers, not a specific one...
Is the problem also lying in: WHERE customer.customer_id = orders.order_id ? Because if a customer can have many orders, if I insert more things inside the orders table, then my WHERE clause would not make sense?
Any ideas to clear my confusion? thanks

Data model is wrong. Should be something like this:
SQL> create table customer
2 (customer_id number primary key,
3 first_name varchar2(20),
4 last_name varchar2(20),
5 phone varchar2(20));
Table created.
SQL> create table items
2 (item_id number primary key,
3 item_name varchar2(20));
Table created.
SQL> create table orders
2 (order_id number primary key,
3 customer_id number constraint fk_ord_cust references customer (customer_id)
4 );
Table created.
SQL> create table order_details
2 (order_det_id number primary key,
3 order_id number constraint fk_orddet_ord references orders (order_id),
4 item_id number constraint fk_orddet_itm references items (item_id),
5 amount number
6 );
Table created.
Some quick & dirty sample data:
SQL> insert all
2 into customer values (100, 'Little', 'Foot', '00385xxxyyy')
3 into items values (1, 'Apple')
4 into items values (2, 'Milk')
5 into orders values (55, 100)
6 into order_details values (1000, 55, 1, 5) -- I'm ordering 5 apples
7 into order_details values (1001, 55, 2, 2) -- and 2 milks
8 select * from dual;
6 rows created.
SQL> select c.first_name, sum(d.amount) count_of_items
2 from customer c join orders o on o.customer_id = c.customer_id
3 join order_details d on d.order_id = o.order_id
4 group by c.first_name;
FIRST_NAME COUNT_OF_ITEMS
-------------------- --------------
Little 7
SQL>
Or, list of items:
SQL> select c.first_name, i.item_name, d.amount
2 from customer c join orders o on o.customer_id = c.customer_id
3 join order_details d on d.order_id = o.order_id
4 join items i on i.item_id = d.item_id;
FIRST_NAME ITEM_NAME AMOUNT
-------------------- -------------------- ----------
Little Apple 5
Little Milk 2
SQL>

There is no relation between the two tables which you wish to combine data from. Kindly create a foreign key relation between the two tables which would help you get a common value based on which you could extract data.
For e.g. - The column Customer_id from customers table could be the foreign key in table orders which would specify the order placed by each customer.
The following query should return you the expected result:
SELECT customer.first_name, customer.last_name, orders.item_name, orders.quantity
FROM customer, orders
WHERE customer.customer_id = orders.customer_id
ORDER BY customer.first_name;
The query specified by you does not return any result as there is no match for any order and customer id in the two tables as both depict two different values.
Hope it helps. Cheers!

In your last query you have shown that your tables are linked ( customer.customer_id = orders.order_id ), but in the tables you have created, there is no link between them. I think this should work:
Step 1: Create a Customer table as follow:
Create table customer
(customer_id id primary key,
first_name varchar(25),
last_name varchar(25),
phone int);
Step 2: Create Items table as follow:
Create table Items
(item_id primary key,
item_name varchar(25));
Step 3: Create link table that relates two tables above as follow:
Create table Orders
(Customer_Id int,
Item_ID int,
Quantity int);
Step 4: Use this query to pull out the information you want:
select c.first_name,i.item_name,o.Quantity from
customer c inner join orders o on c.customer_id = o.customer_id
inner join items i on i.item_id = o.Item_id;
Please try it and let me know if there is any problem.

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>

Inner Join with Sum in oracle 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>

oracle sql - merge table with nested table as column

I have 2 tables and one nested table:
1.stores data about products which include following columns:
ITEM - product id(key)
STORE - store id(key)
PRICE
NORMAL_PRICE
DISCOUNTS - nested table with info about discounts include columns:
PromotionId(key)
PromotionDescription
PromotionEndDate
MinQty
DiscountedPrice
DiscountedPricePerMida
2- temp table with new discounts include columns:
PROMOTIONID(key)
PRODUCTID(key)
PROMOTIONDESCRIPTION
PROMOTIONENDDATE
MINQTY
DISCOUNTEDPRICE
DISCOUNTEDPRICEPERMIDA
What i need to do is merge table 2 into table 1 - if no match insert else ignore
(when match is: product id matching in table 1 and 2 and for this product sub table PROMOTIONID match PROMOTIONID from table 2)
This is where I got so far and I have difficulty with nested part - ON clause and Insert clause
MERGE INTO PRICES P
USING(SELECT * FROM TMP_PROMO)T
ON(P.ITEM=T.PRODUCTID AND P.STORE=50 AND P.DISCOUNTS.PROMOTIONID=T.PROMOTIONID)
WHEN NOT MATCHED THEN INSERT (P.DISCOUNTS)
VALUES(T.PROMOTIONID,
T.PROMOTIONDESCRIPTION,
T.PROMOTIONENDDATE,
T.MINQTY,
T.DISCOUNTEDPRICE,
T.DISCOUNTEDPRICEPERMIDA);
I know that this is wrong but I can't find anywhere how to do it
example:
Prices table:
row1(1,50,...,nested_table[(11,...),(12,...)])
row2(2,50,...,nested_table[(10,...),(12,...)])
new promo table:
(15,1,...)
(11,1,...)
new promo with id 15 will be added to row1 and row2
and promo with id 11 will not be added
Please help,
thanks
What you intend to do is not realy a MERGE. You are adding a new promotion in each record that doesn't contain it.
Below is an answer how yu would approach it if you would use not a nested table but a conventional child table.
Setup (simplified to a minimum)
create table ITEM
(ITEM_ID NUMBER PRIMARY KEY);
create table ITEM_PROMO
(ITEM_ID NUMBER REFERENCES ITEM(ITEM_ID),
PROMO_ID NUMBER);
create table TMP_PROMO
(PROMO_ID NUMBER);
insert into ITEM values (1);
insert into ITEM values (2);
insert into ITEM_PROMO values (1,11);
insert into ITEM_PROMO values (1,12);
insert into ITEM_PROMO values (2,10);
insert into ITEM_PROMO values (2,12);
insert into TMP_PROMO values (15);
insert into TMP_PROMO values (11);
commit;
The first thing you need to find is which promotions are missing for an item.
Use a cross join to get all combination and constrain those promotions that EXISTS for a particular ITEM_ID:
select ITEM.ITEM_ID, TMP_PROMO.PROMO_ID
from ITEM cross join TMP_PROMO
where NOT EXISTS (select NULL from ITEM_PROMO where ITEM_ID = ITEM.ITEM_ID and PROMO_ID = TMP_PROMO.PROMO_ID)
;
This gives as expected
ITEM_ID PROMO_ID
---------- ----------
2 11
1 15
2 15
Now simple add those new promotions
INSERT INTO ITEM_PROMO
select ITEM.ITEM_ID, TMP_PROMO.PROMO_ID
from ITEM cross join TMP_PROMO
where NOT EXISTS (select NULL from ITEM_PROMO where ITEM_ID = ITEM.ITEM_ID and PROMO_ID = TMP_PROMO.PROMO_ID)
;
This should give you a hint how to approach while using nested tables (or how to change the DB design:)

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

How to find the need of materials from nested estimates in Postgres

Product estimates contain sub-products.
Sub-products can contain also sub-products etc.
Finally tree leafs contians materials.
Maximum nesting level is 10.
Orders contain also products, sub-products and materials with ordered quantities.
How to find the need of materials required to fullfill the orders?
Products, sub-products and materials are in single table:
create table toode (productid char(10) primary key );
Estimate table:
create table dok (
dokumnr serial primary key,
productid char(10) not null references toode
);
Sub-products and materials in estimates:
create table rid (
id serial primary key,
dokumnr int not null references dok,
itemid char(10) not null references toode,
quantity numeric(12,4) -- quantity required to make one product
);
Orders:
create table orderrows (
id serial primary key,
itemid char(10) not null references toode,
quantity numeric(12,4) -- ordered quantity
);
Result should be query which return the need of materials and sub-products:
itemid char(10) not null references toode,
requiredquantity numeric(12,4) -- total quantity of items required to make ordered products
How to implement this in Postgresql 9.2?
Described fields should remain in those tables. It is possible to add additional
columns and tables if this helps.
Is it possible to make some universal query which works with unilimited nesting level.
Or is it best way to create query which repeats some parts 10 times for maximum nensting level ?
Update
estimates
product1
material1 2 pcs
subproduct2 3 pcs
subproduct2
material2 4 pcs
are described as
insert into dok values (1,'product1');
insert into rid (dokumnr, itemid, quantity) values (1, 'material1', 2);
insert into rid (dokumnr, itemid, quantity) values (1, 'subproduct2', 3);
insert into dok values (2,'subproduct2');
insert into rid (dokumnr, itemid, quantity) values (2, 'material2', 4);
If 10 pieces of product1 are ordered this is described as:
insert into orderrows (itemid, quantity ) values ('product1', 10);
Result should be:
material1 20
material2 120
material1 quantity is calculated as 10*2.
material2 quantity is calculated as 10*3*4
Update 2
Joachim answer gives incorrect result on multi level estimates when last level contains more that one row. Last join LEFT JOIN rid rid2 ON rid2.dokumnr = dok2.dokumnr returns multiple rows and result table is duplicated.
Testcase http://sqlfiddle.com/#!12/e5c11/1/0 :
create table toode (productid char(15) primary key );
create table dok (
dokumnr serial primary key,
productid char(15) not null references toode
);
create table rid (
id serial primary key,
dokumnr int not null references dok,
itemid char(15) not null references toode,
quantity numeric(12,4) -- quantity required to make one product
);
create table orderrows (
id serial primary key,
itemid char(15) not null references toode,
quantity numeric(12,4) -- ordered quantity
);
INSERT INTO toode VALUES ('product1'),('material1'),('subproduct2'), ('material2'), ('material3');
insert into dok values (1,'product1');
insert into dok values (2,'subproduct2');
insert into rid (dokumnr, itemid, quantity) values (1, 'material1', 1);
insert into rid (dokumnr, itemid, quantity) values (1, 'subproduct2', 1);
insert into rid (dokumnr, itemid, quantity) values (2, 'material2', 1);
insert into rid (dokumnr, itemid, quantity) values (2, 'material3', 1);
insert into orderrows (itemid, quantity ) values ('product1', 1);
Expected:
Every quantity is 1 so result quantity must be 1 for every material.
Observed:
Material2 and matererial3 rows are duplicated.
How to fix this ? Query should determine leaf nodes itself. Leaf nodes are not marked specially in data.
This should do it using a recursive query;
WITH RECURSIVE t(itemid,qty) AS (
SELECT itemid,quantity,false isleaf FROM orderrows
UNION ALL
SELECT rid.itemid,(rid.quantity*t.qty)::NUMERIC(12,4),
dok2.productid IS NULL
FROM t
JOIN dok ON dok.productid=t.itemid
JOIN rid ON rid.dokumnr=dok.dokumnr
LEFT JOIN dok dok2 ON dok2.productid=rid.itemid
)
SELECT itemid, SUM(qty) FROM t WHERE isleaf GROUP BY itemid
An SQLfiddle to test with.
Try this query:
;with recursive cte as (
select r.itemid, r.quantity * o.quantity as quantity, false as is_material
from orderrows as o
inner join dok as d on d.productid = o.itemid
inner join rid as r on r.dokumnr = d.dokumnr
union
select r.itemid, r.quantity * o.quantity as quantity, itemid like 'material%'
from cte as o
inner join dok as d on d.productid = o.itemid
inner join rid as r on r.dokumnr = d.dokumnr
)
select * from cte as c
where c.itemid not in (select t.productid from dok as t);
here's SQL FIDDLE example to test it. Here I'm assuming that your define materials as products which name starts with 'material', but I think that you should have an attribute is_material or something like that in your DB, so you could change this condition.
update - test case sql fiddle