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

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

Related

Updating fields based on results of a query

So in this scenario a person can order products. I have a query that returns the result of an order ID and give the products that person ordered.
My issue is adapting this to an update query so the quantity from the OrderProduct table/query is then subtracted from the amount of stock in the Product Table.
SQL to create the table supplied below and the query to get the results
CREATE DATABASE StockControl;
CREATE TABLE Membership (
MemberID int NOT Null,
LastName varchar(255),
FirstName varchar(255),
Primary Key(MemberID)
);
CREATE TABLE Orders (
OrderID int NOT Null,
MemberID int,
Primary Key(OrderID)
);
CREATE TABLE Product (
ProductID int NOT Null,
Price int,
Stock int,
Primary Key(ProductID)
);
CREATE TABLE OrderProduct (
ProductID int,
OrderID int,
Quantity int
);
INSERT INTO Membership(MemberID,LastName,FirstName)
VALUES (2,'Me','Too'),(33,'Darren','Kelly');
INSERT INTO Orders(OrderID,MemberID)
VALUES (1,33),(5,2);
INSERT INTO Product(ProductID,Price,Stock)
VALUES (1,30,12),(2,25,12),(3,25,12),(4,25,12),(5,25,12),(6,25,12),(7,25,12),(8,25,12),(9,25,12),(10,25,12);
INSERT INTO OrderProduct(ProductID,OrderID,Quantity)
VALUES (1,5,1),(3,1,4),(9,1,6),(10,1,2);
This is the query to return all products as part of orderID 1, in the full version this will be able to be variable.
SELECT OrderProduct.ProductID, OrderProduct.OrderID, OrderProduct.Quantity, Product.Price, [Quantity]*[Price] AS Cost
FROM Product INNER JOIN OrderProduct ON Product.ProductID = OrderProduct.ProductID
GROUP BY OrderProduct.ProductID, OrderProduct.OrderID, OrderProduct.Quantity, Product.Price, [Quantity]*[Price]
HAVING (((OrderProduct.OrderID)=1))
ORDER BY OrderProduct.OrderID;
This returns the correct result but when trying to implement this to an update I get stuck. I want it so it looks through the 3 results and will subtract 4 from product 3, 6 from product 9 and 2 from product 10
Attempt so far
UPDATE SingleOrder INNER JOIN Product ON SingleOrder.ProductID = Product.ProductID SET Product.Stock = Product.Stock-SingleOrder.Quantity WHERE ((Product.ProductID=SingleOrder.ProductID));
If this is not clear I am happy to update the question etc.
Thanks

Joining tables to create a total sum of two seperate orders

I am trying to get the cost of each item over to a final table where it only shows the final ORDER_ID and the ORDER_TOTAL. I am having issues getting my two tables to join and am struggling with JOIN as a whole. These are the two tables that I am trying to join.
ORDER COST
CREATE TABLE `order cost` (
`ORDER_ID` int(10) NOT NULL,
`MENU_COST` double(5,2) NOT NULL,
`ORDER_COST` double(5,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `order cost` (`ORDER_ID`, `MENU_COST`, `ORDER_COST`) VALUES
(1, 7.00, 7.63),
(3, 8.00, 8.72),
(1, 13.00, 14.17),
(3, 25.00, 27.25);
ALTER TABLE `order cost`
ADD PRIMARY KEY (`ORDER_COST`),
ADD KEY `ORDER_ID` (`ORDER_ID`),
ADD KEY `MENU_COST` (`MENU_COST`);
ORDER TOTAL
CREATE TABLE `order total` (
`ORDER_ID` int(11) NOT NULL,
`ORDER_TOTAL` double(6,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `order total` (`ORDER_ID`, `ORDER_TOTAL`) VALUES
(1, 0.00),
(3, 0.00);
ALTER TABLE `order total`
ADD KEY `ORDER_ID` (`ORDER_ID`);
My join code attempt:
SELECT ORDER_ID,
SUM(MENU_COST) = ORDER_TOTAL
FROM `order cost`
INNER JOIN `order total`
GROUP BY ORDER_ID;
I have tried multiple other join ideas, and in the main order cost table I was able to get the SUMS(ORDER_COST) to create its own cell after I ran a
SELECT ORDER_ID, SUM(MENU_COST) FROM `order cost` GROUP BY ORDER_ID
But this creates its own table after the run, whereas I am looking to get the table to auto produce in the 'order total' based on changes to the database.
See comments above for my thoughts on not doing this. You'll have to run this query every time you make a change to the ordercost table:
UPDATE
ordertotal
SET
order_total = (
SELECT SUM(MENU_COST) FROM ordercost WHERE ORDER_ID = x GROUP BY ORDER_ID
)
WHERE order_id = x
There is no joining here.. you insert some new item to ordercost and then run the query to sync the table. On the whole it's a terrible idea. You should consider instead creating a VIEW and selecting from it when needed:
CREATE VIEW ordertotal AS
SELECT
ORDER_ID,
SUM(MENU_COST) as ORDER_TOTAL
FROM ordercost
GROUP BY ORDER_ID
It will behave like a table:
SELECT * FROM ordertotal where order_id = 3

SQL create a full part list out of a given part list (iteration)

I have a problem with a given task from my SQL lecture.
In the task there is a database given with a list of parts of three robots and another list, where these parts are linked to parts from the first list, that they are made of, if they are made of other parts.
The databasess can be generated with:
CREATE TABLE part (
part_id BIGINT PRIMARY KEY,
part_namevarchar(64)NOTNULL
);
CREATE TABLE part_part (
object_id BIGINT,
part_id BIGINT,
quantity INT NOT NULL,
PRIMARY KEY(object_id, part_id)
);
ALTER TABLE part_part
ADD CONSTRAINT part_id_fkey FOREIGNKEY (part_id) REFERENCES part(part_id)
ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE part_part
ADD CONSTRAINT object_id_fkey FOREIGN KEY(object_id)REFERENCES part(part_id)
ON UPDATE CASCADE ONDELETE CASCADE;
INSERT INTO part(part_id, part_name)
VALUES
(0,'CPU A'),
(1,'Cables'),
(2,'Motherboard 3xy'),
(3,'Motor ayX'),
(4,'Arm'),
(5,'Body'),
(6,'Leg'),
(7,'Wheel'),
(8,'Motherboard 7ax'),
(9,'Joint'),
(10,'Motor Z1238'),
(11,'Hammer'),
(12,'Screw A'),
(13,'Screw B'),
(14,'Screw C'),
(15,'Robo 1000'),
(16,'CPU B'),
(17,'CPU C'),
(18,'Robo 2000'),
(19,'Screwdriver'),
(20,'Robo 3000');
INSERT INTO part_part
(object_id, part_id, quantity)
VALUES
(5,2,1),
(5,0,1),
(5,3,2),
(5,1,5),
(5,12,3),
(4,9,3),
(4,10,3),
(4,13,13),
(6,3,2),
(6,7,4),
(15,4,2),
(15,11,2),
(15,5,1),
(15,6,1),
(18,4,2),
(18,11,2),
(18,5,1),
(18,6,2),
(18,16,1),
(20,4,3),
(20,11,1),
(20,19,1),
(20,5,1),
(20,6,1),
(20,16,1),
(20,17,1);
Now the task is to get the list of all parts and subparts needed for the "Robo 3000" and their quantity.
I got as far as:
WITH part2(part_name1, subpart_id, top_quantity, top_part_id, part_name) AS(
WITH part1(part_name, subpart_id, quantity) AS(
WITH subpart1(object_id, subpart_id, quantity) AS(SELECT * FROM part_part)
SELECT part_name, subpart_id, quantity FROM subpart1
JOIN part ON part.part_id = subpart1.object_id
WHERE part_name = 'Robo 3000'
)
SELECT * FROM part1
JOIN part ON part1.subpart_id = part.part_id
)
SELECT * FROM part2
JOIN part_part ON part2.top_part_id = part_part.object_id
ORDER BY top_part_id;
Which gives me a list of only subparts (the parts of all the parts from the robot, that need parts themselves) and also it doesn't consider, if parts are used multiple times, here the arm is used 3 times but its parts aren't multiplied with the quantity.
Also this is limited, since it only looks at the given part and the supp parts but not deeper if needed.
Is there a way to iterate through all the parts and make them into a big list in SQL?
The same way for example a java method would with a self calling method?
PostgreSQL supports recursive sQL which may be one way solve your problem. Below is a example using your data.
with recursive part_list as
(
(
select object_id as unit, object_id, part_id, quantity, quantity as totqty
from part_part
where object_id = 20
)
union
select pl.unit, pp.object_id, pp.part_id, pp.quantity, pp.quantity * pl.quantity
from part_part pp
join part_list pl
on pp.object_id = pl.part_id
)
select u.part_name as unit,
part.part_name,
sum(part_list.totqty) as total_parts
from part_list
join part u
on u.part_id = part_list.unit
join part
on part.part_id = part_list.part_id
group by u.part_name, part.part_name
order by 1,3;

How do I do a deep copy with a single PostgreSQL query?

I have three tables:
CREATE TABLE offers
(
id serial NOT NULL PRIMARY KEY,
title character varying(1000) NOT NULL DEFAULT ''::character varying
);
CREATE TABLE items
(
id serial NOT NULL PRIMARY KEY,
offer_id integer NOT NULL,
title character varying(1000) NOT NULL DEFAULT ''::character varying,
CONSTRAINT items_offer_id_fkey FOREIGN KEY (offer_id)
REFERENCES offers (id)
);
CREATE TABLE sizes
(
id serial NOT NULL PRIMARY KEY,
item_id integer NOT NULL,
title character varying(1000) NOT NULL DEFAULT ''::character varying,
CONSTRAINT sizes_item_id_fkey FOREIGN KEY (item_id)
REFERENCES items (id)
);
I have 1 offer that has 2 items. Each item has 2 sizes:
INSERT INTO offers (title) VALUES ('My Offer');
INSERT INTO items (offer_id, title) VALUES (1, 'First Item');
INSERT INTO items (offer_id, title) VALUES (1, 'Second Item');
INSERT INTO sizes (item_id, title) VALUES (1, 'First Size of Item #1');
INSERT INTO sizes (item_id, title) VALUES (1, 'Second Size of Item #1');
INSERT INTO sizes (item_id, title) VALUES (2, 'First Size of Item #2');
INSERT INTO sizes (item_id, title) VALUES (2, 'Second Size of Item #2');
Is there a way to clone an offer with all its items and sizes with a single query?
I tried to solve it with CTE, here is my SQL:
WITH tmp_offers AS (
INSERT INTO offers (title)
SELECT title FROM offers WHERE id = 1
RETURNING id
), tmp_items AS (
INSERT INTO items (offer_id, title)
(SELECT (SELECT id FROM tmp_offers), title FROM items WHERE offer_id = 1)
RETURNING id
)
INSERT INTO sizes (item_id, title)
(SELECT (SELECT id FROM tmp_items), title FROM sizes WHERE id IN (
SELECT sizes.id FROM sizes
JOIN items ON items.id = sizes.item_id
WHERE items.offer_id = 1
));
But this SQL results to an error, that I can't resolve:
ERROR: more than one row returned by a subquery used as an expression
Your help is greatly appreciated.
P.S. I use PostgreSQL 9.5
This should work:
WITH tmp_offers AS (
INSERT INTO offers (title)
SELECT title
FROM offers
WHERE id = 1
RETURNING id
), tmp_items AS (
INSERT INTO items (offer_id, title)
SELECT o.id, i.title
FROM items i
cross join tmp_offers o
WHERE i.offer_id = 1
order by i.id
RETURNING items.id
), numbered_new as (
select ti.id,
row_number() over (order by ti.id) as rn
from tmp_items ti
), numbered_old as (
select i.id,
row_number() over (order by i.id) as rn
from items i
WHERE i.offer_id = 1
), item_mapper as (
select n.id as new_item_id,
o.id as old_item_id
from numbered_new n
join numbered_old o on n.rn = o.rn
)
INSERT INTO sizes (item_id, title)
select im.new_item_id, s.title
from sizes s
join item_mapper im on im.old_item_id = s.item_id;
Online example: http://rextester.com/RYQUS11008
You are quite close. It is the final query that needs work:
WITH tmp_offers AS (
INSERT INTO offers (title)
SELECT title FROM offers WHERE id = 1
RETURNING id
),
tmp_items AS (
INSERT INTO items (offer_id, title)
SELECT o.id, i.title
FROM items i CROSS JOIN
(SELECT id FROM tmp_offers) o
WHERE i.offer_id = 1
RETURNING id, title
)
INSERT INTO sizes (item_id, title)
SELECT i.id, i.title
FROM tmp_items i;
The major difference here is that tmp_items now has two columns -- and they appear to be the columns that you want for this purpose.

Ordinary taks - complex solution

I have some users (C1, C2, C3, etc.) who handles products (aa, bb, cc, dd, ee, ff, gg, hh, etc.) in differents stores (St1, St2, St3, St4, etc.).
Every user in the list wants to know in which store which products they can handle cheaper.
How looks out the tables and how looks out the queries if user want get at least the following 3 things (one at the time):
1- Get own list of products. Exemple:
Pr. St1 St2 St3 St4
aa $20 $12 $19 $22
bb $31 $44 $38 $44
cc $18 $12 $19 $22
dd $36 $44 $38 $44
ee $15 $12 $19 $22
2- Get a list of lowest prices (but greather than 0) and see how much he/she save if he/she handles the same products on others stores. Exemple:
Pr. St4 St1 St3 St2
bb $23 $27 $26 $28
ee $14 $15 $15 $20
hh $36 $38 $40 $37
Sum $73 $80 $81 $85
Count products 3.
Pr. St2 St1 St3 St4
aa $32 $33 $38 $36
cc $21 $29 $27 $25
ff $13 $14 $17 $20
Sum $66 $76 $82 $81
Count products 3.
3- Get a list of products which has cero price in each store. Exemple:
Pr. St1 St2 St3 St4
kk $00 $12 $19 $22
ii $00 $44 $38 $44
Pr. St2 St1 St3 St4
ll $00 $21 $52 $20
mm $00 $13 $17 $15
A primitive not good solution:
CREATE TABLE usrs (
idc INT NOT NULL,
name VARCHAR(50) NOT NULL,
stores VARCHAR(50) NOT NULL,
pwd VARBINARY(72) NOT NULL,
PRIMARY KEY (idc)
)
COMMENT='Customers'
COLLATE='utf8_general_ci'
ENGINE=MyISAM;
CREATE TABLE stores (
ids INT NOT NULL,
nm VARCHAR(50) NOT NULL,
PRIMARY KEY (ids)
)
COLLATE='utf8_general_ci'
ENGINE=MyISAM;
CREATE TABLE products (
idp INT(11) NOT NULL AUTO_INCREMENT,
prod VARCHAR(50) NOT NULL,
st1 MEDIUMINT(9) NULL DEFAULT '0',
st2 MEDIUMINT(9) NULL DEFAULT '0',
st3 MEDIUMINT(9) NULL DEFAULT '0',
st4 MEDIUMINT(9) NULL DEFAULT '0',
PRIMARY KEY (idp)
)
COLLATE='utf8_general_ci'
ENGINE=MyISAM;
INSERT INTO products (prod,st1,st2,st3,st4) VALUES (aa,14,20,13,17);
INSERT INTO products (prod,st1,st2,st3,st4) VALUES (bb,33,29,38,33);
INSERT INTO products (prod,st1,st2,st3,st4) VALUES (cc,19,20,00,21);
INSERT INTO products (prod,st1,st2,st3,st4) VALUES (dd,22,29,25,33);
INSERT INTO products (prod,st1,st2,st3,st4) VALUES (ee,30,00,35,29);
INSERT INTO products (prod,st1,st2,st3,st4) VALUES (ff,10,14,11,13);
INSERT INTO products (prod,st1,st2,st3,st4) VALUES (gg,00,00,00,00);
INSERT INTO products (prod,st1,st2,st3,st4) VALUES (hh,16,22,30,10);
INSERT INTO products (prod,st1,st2,st3,st4) VALUES (ii,23,34,34,26);
INSERT INTO products (prod,st1,st2,st3,st4) VALUES (jj,41,32,39,41);
INSERT INTO products (prod,st1,st2,st3,st4) VALUES (kk,25,29,26,19);
INSERT INTO products (prod,st1,st2,st3,st4) VALUES (ll,24,27,10,24);
INSERT INTO products (prod,st1,st2,st3,st4) VALUES (mm,29,41,37,36);
I don't know how all the tables can looks like when count of stores are more than 2 and count of users are more than 1.
In my solution I have to manually change the tables when users or stores increases. I understand here needs a relationship between tables, but I don't find out it. Bad, very bad ...
Thanks in advance!
You could change your solution such that you had a list of prices connecting stores to products.
A price would be a tuple of Product, Store, and Price.
Then, you could select with a left join on this table. The where clause could have a specified store id, a price requirement, and/or product id.
Example:
I have modified the names of your tables for better clarity. Examples tested on MariaDB.
First, the models I will use:
CREATE TABLE `stores` (
`id` INT NOT NULL,
`name` VARCHAR(50) NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `products` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `prices` (
`product_id` INT(11) NOT NULL,
`store_id` INT NOT NULL,
`price` DOUBLE NOT NULL,
CONSTRAINT productIdToStoreID UNIQUE (product_id, store_id)
);
Here is how I would populate the database
INSERT INTO stores (id, name) VALUES (10, "Foo's Fun");
INSERT INTO stores (id, name) VALUES (20, "Bar's Barn");
INSERT INTO products (id, name) VALUES (10, "0xC0C0C01A");
INSERT INTO products (id, name) VALUES (20, "0xDEADBEEF");
INSERT INTO prices (product_id, store_id, price) VALUES (10, 10, 1.50);
INSERT INTO prices (product_id, store_id, price) VALUES (10, 20, 1.60);
INSERT INTO prices (product_id, store_id, price) VALUES (20, 10, 40);
INSERT INTO prices (product_id, store_id, price) VALUES (20, 20, 35);
Here are some starting points are queries you could write:
SELECT store_id, price FROM prices WHERE product_id = 10; -- All prices for product with id 10
SELECT store_id, price FROM prices WHERE product_id = 10 ORDER BY price ASC LIMIT 1; -- Get Cheapest Price and Store for product 10
SELECT stores.name, price FROM prices LEFT JOIN stores ON (stores.id = store_id) WHERE product_id = 10 ORDER BY price ASC LIMIT 1; -- Get the Store name and price.
Motivation:
We need a way that can relate n stores, m products. (Not sure where users go here). We need a data structure that can hold:
If a store has a item (conversely, if a product is in a store). We can see that this is a many to many relationship. SQL databases tend to be really optimized for this type of relationship.
The price of a product at a store.
Thus, we create a table that links a product to a store uniquely, with the extra metadata of the price.
As you pointed out, the alternative to this is a table that contains all of the stores as columns. However, this does not scale well at all as in order to add, remove, or change the name of a store would be a O(n) update. In a production environment, this would be detrimental as this would likely lock the database. but I digress.
This solution solves this issue by allowing the store list and product list to be decoupled from the actual products. If a price exists at a store for a product, there is a row. This means that the space complexity is at worst O(n), but in reality would be much less as not all stores would have all products.