oracle sql - merge table with nested table as column - sql

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:)

Related

Insert multiple rows using the same foreign key that needs to be selected

Assume that there are two tables:
CREATE TABLE products (id SERIAL, name TEXT);
CREATE TABLE comments (id SERIAL, product_id INT, txt TEXT);
I would like to insert multiple comments for the same product. But I don't know the product_id yet, only the product name.
So I could do:
INSERT INTO comments (txt, product_id) VALUES
( 'cool', (SELECT id from products WHERE name='My product name') ),
( 'great', (SELECT id from products WHERE name='My product name') ),
...
( 'many comments later', (SELECT id from products WHERE name='My product name') );
I'd like to reduce the repetition. How to do this?
I tried this but it inserts no rows:
INSERT INTO
comments (txt, product_id)
SELECT
x.txt,
p.id
FROM
(
VALUES
('Great product'),
('I love it'),
...
('another comment')
) x (txt)
JOIN products p ON p.name = 'My product name';
Your query works just fine. The only way it inserts zero rows is if there is no product in the table products for a given string - in your query named My product name. However, #a_horse_with_no_name's suggestion to use a CROSS JOIN might simplify your query a bit. You can combine it with a CTE to collect all comments and then CROSS JOIN it with the record you filtered in from table products.
CREATE TABLE products (id SERIAL, name TEXT);
CREATE TABLE comments (id SERIAL, product_id INT, txt TEXT);
INSERT INTO products VALUES (1, 'My product name'),(2,'Another product name');
WITH j (txt) AS (
VALUES ('Great product'),('I love it'),('another comment')
)
INSERT INTO comments (product_id,txt)
SELECT id,j.txt FROM products
CROSS JOIN j WHERE name = 'My product name';
SELECT * FROM comments;
id | product_id | txt
----+------------+-----------------
1 | 1 | Great product
2 | 1 | I love it
3 | 1 | another comment
Check this db<>fiddle

manage reference table with foreign keys on new incoming data in other table

I have a reference table (in Postgres) and multiple other tables which can reference data in/from this table. It would make sense to use foreign keys to manage this, but I was wondering how one could manage updating the entries in the reference table in the best way when new values come in.
I guess it could be done through the coding that handles the data ingestion and check and insert new values in the reference and after that add the data to the other table. This doesn't seem ideal somehow to me, so I wondered what the best practice is in handling this situation. As there is management of deletion present in SQL I wondered if the opposite might also be available for instance? (not to prevent insertion, but sort of cascading of the values?)
Example reference table;
company_id - company_name
1 - test1
2 - test2
3 - test3
table with sales to companies:
company_id - month - year - sales
1 - January - 2020 - 10000
2 - January - 2020 - 8000
1 - December - 2019 - 9000
3 - November - 2019 - 7000
Now data can come in including rows like;
company_name - month - year - sales
test4 - January - 2020 - 10000
test5 - January - 2020 - 1000
Ideally I could insert this with one query and update the reference table with the new company name so that it gets an id that will be used in the sales table.
You can first check if the record in the reference table exists ..
SELECT
EXISTS(
SELECT TRUE FROM company
WHERE name = 'test2');
And in case it does you can insert the sales normally making reference to the company table. If it does not, you can use a CTE to insert the new company and get its id, so that you can outside the CTE insert the new sales record ..
WITH j AS (
INSERT INTO company (name)
VALUES ('test2')
RETURNING id
)
INSERT INTO sales (company_id, sales)
SELECT j.id, 42000 FROM j;
Testing script to add a new company
CREATE TABLE company (
id SERIAL PRIMARY KEY,
name TEXT);
INSERT INTO company (name) VALUES ('test1');
CREATE TABLE sales (
company_id INTEGER REFERENCES company(id),
sales NUMERIC);
WITH j AS (
INSERT INTO company (name)
VALUES ('test2')
RETURNING id
)
INSERT INTO sales (company_id, sales)
SELECT j.id, 42000 FROM j;
SELECT * FROM sales
JOIN company ON company.id = sales.company_id;
company_id | sales | id | name
------------+-------+----+-------
2 | 42000 | 2 | test2
(1 Zeile)
If you want to ignore records that violate the foreign key constraint, check this answer by #a_horse_with_no_name.
Edit: Using anonymous code blocks including checks and inserts
DO $$
BEGIN
IF EXISTS(SELECT TRUE FROM company WHERE name = 'test1') THEN
INSERT INTO sales (company_id, sales)
VALUES ((SELECT id FROM company WHERE name = 'test1'),42000);
ELSE
WITH j AS (
INSERT INTO company (name)
VALUES ('test1')
RETURNING id
)
INSERT INTO sales (company_id, sales)
SELECT j.id, 42000 FROM j;
END IF;
END$$;

How to get customer's items purchased?

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.

INSERT multiple rows with SELECT and an array

In EXCEL/VBA I can program my way out of a thunderstorm, but in SQL I am still a novice. So apologies, after much Googling I can only get partway to a solution which I presume ultimately will be pretty simple, just not wrapping my head around it.
I need to create an INSERT script to add multiple rows in a 3-column table. A simple insert would be:
INSERT INTO table VALUES(StoreId, ItemID, 27)
First hurdle is dynamically repeat this for every StoreID in a different table. Which I think becomes this:
INSERT INTO table
SELECT (SELECT StoreID FROM Directory.Divisions), ItemID, 27)
If that is actually correct and would effectively create the 50-60 rows for each store, then I'm almost there. The problem is the ItemID. This will actually be an array of ItemIDs I want to feed in manually. So if there are 50 stores and 3 ItemIDs, it would enter 150 rows. Something like:
ItemID = (123,456,789,246,135)
So how can I merge these two ideas? Pull the StoreIDs from another table, feed in the array of items for the second parameter, then my hardcoded 27 at the end. 50 stores and 10 items should create 500 rows. Thanks in advance.
You can use into to insert into the target table. To generate itemid's you will have to use union all with your values and cross join on the divisions table.
select
d.storeid,
x.itemid,
27 as somecolumn
into targettablename
from Directory.Divisions d
cross join (select 123 as itemid union all select 456 union all select 789...) x
Edit: If the table to insert into isn't created yet, it should be created before inserting the data.
create table targettable as (store_id varchar(20), item_id varchar(20),
somecolumn int);
insert into targettable (store_id, item_id, somecolumn)
select
d.storeid,
x.itemid,
27
from Directory.Divisions d
cross join (select 123 as itemid union all select 456 union all select 789...) x
Firstly you need your array of item ids in a table of some sort. Either a permanent table, table variable or temporary table. For example using a temporary table, which you prefix with a hash symbol:
CREATE TABLE #ItemIds (item_id int)
INSERT INTO #ItemIds VALUES (1)
INSERT INTO #ItemIds VALUES (2)
...
INSERT INTO #ItemIds VALUES (10)
Then this should do the trick:
INSERT INTO table
SELECT StoreId, item_Id, 27
FROM Directory.Divisions, #ItemIds
The results set from the SELECT will be inserted into 'table'. This is an example of a cartesian join. Because there is no join condition, every row from Directory.Divisions is joined to every row in #ItemIds. Hence if you have 50 stores and 10 items, that will result in 50 x 10 = 500 rows.
You may declare table variable for item IDs and use CROSS JOIN to multiply division records to items: http://sqlfiddle.com/#!3/99438/1
create table Divisions(StoreId int)
insert into Divisions values (1), (2), (3)
declare #items table(ItemID int)
insert into #items values (5), (6), (7)
-- insert into target(stireid, itemid, otherColumn)
select d.StoreId, i.ItemID, 27
from Divisions d
cross join #items i

Merge table in Oracle with delete condition refering to source table

For the following question it is said that the answer should be C. But I think the correct answer is Answer D as NOT MATCHED block inserts all unmatching records to target table. Can anybody explain this?
Thank you.
Q)View the Exhibit and examine the data in ORDERS_MASTER and MONTHLY_ORDERS tables.
Evaluate the following MERGE statement:
MERGE INTO orders_master o
USING monthly_orders m
ON (o.order_id = m.order_id)
WHEN MATCHED THEN
UPDATE SET o.order_total = m.order_total
DELETE WHERE (m.order_total IS NULL)
WHEN NOT MATCHED THEN
INSERT VALUES (m.order_id, m.order_total);
What would be the outcome of the above statement?
A. The ORDERS_MASTER table would contain the ORDER_IDs 1 and 2.
B. The ORDERS_MASTER table would contain the ORDER_IDs 1,2 and 3.
C. The ORDERS_MASTER table would contain the ORDER_IDs 1,2 and 4.
D. The ORDERS_MASTER table would contain the ORDER IDs 1,2,3 and 4.
Answer: C
The correct answer is indeed C, this is because the source of the merge operation is the monthly_orders table, which only contains two records with order_id 2 and 3 respectively.
Think about what will happen for each of these records:
For order_id = 2, because this id exists in the order_master table, we'll execute the MATCHED part of the merge statement, updating the order_total to 2500. Since the quantity for this record is not NULL, the DELETE won't do anything.
For order_id = 3, again, the id exists in the order_master table, so we execute the MATCHED part of the merge statement, updating the order_total to NULL and then issuing a DELETE on order_master for the row we just updated because the quantity on monthly_order is NULL.
This leaves us with order_id 1, 2 and 4, which matches answer C.
Code
CREATE TABLE orders_master (
order_id NUMBER(1) NOT NULL
,order_total NUMBER(10) NULL
)
/
CREATE TABLE monthly_orders (
order_id NUMBER(1) NOT NULL
,order_total NUMBER(10) NULL
)
/
INSERT INTO orders_master (order_id, order_total) VALUES (1, 1000)
/
INSERT INTO orders_master (order_id, order_total) VALUES (2, 2000)
/
INSERT INTO orders_master (order_id, order_total) VALUES (3, 3000)
/
INSERT INTO orders_master (order_id, order_total) VALUES (4, NULL)
/
INSERT INTO monthly_orders (order_id, order_total) VALUES (2, 2500)
/
INSERT INTO monthly_orders (order_id, order_total) VALUES (3, NULL)
/
MERGE INTO orders_master o
USING monthly_orders m
ON (o.order_id = m.order_id)
WHEN MATCHED THEN
UPDATE SET o.order_total = m.order_total
DELETE WHERE m.order_total IS NULL
WHEN NOT MATCHED THEN
INSERT VALUES (m.order_id, m.order_total)
/
COMMIT
/
SQL> select * from orders_master
2 /
ORDER_ID ORDER_TOTAL
---------- -----------
1 1000
2 2500
4