Ordinary taks - complex solution - sql

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.

Related

SQL Server Stored Procedure for Menu Performance Report

I have four tables in my SQL database i.e MenuItems, Categories, Invoices and InvoiceDetails. Now what I want is to show the menu performance report for a certain date i.e total Qty and total Amount for
each menu item for a specific date. It shows the desired result without the date in the where clause but excludes menu items with null values.
Here is my stored procedure:
CREATE PROCEDURE spGetMenuPerformanceByDay
#Date date,
#Terminal int
AS
BEGIN
SELECT
M.Name,
ISNULL(SUM(D.Amount), 0) AS Amount,
ISNULL(SUM(D.Qty), 0) AS Qty
FROM
MenuItems AS M
JOIN
Categories AS C ON C.Id = M.CategoryId
LEFT JOIN
InvoiceDetails AS D ON M.Id = D.ItemId
LEFT JOIN
Invoices I ON I.Id = d.InvoiceId
WHERE
#Terminal IN (I.TerminalId, C.TerminalId)
AND CONVERT(date, I.Time) = #Date
OR NULL IN (Amount, Qty)
GROUP BY
M.Name, M.Id, D.ItemId
ORDER BY
(Qty) DESC
END
The result this stored procedure returns on adding Date in where clause:
Item
Amount
Qty
KOFTA ANDA
1950
3
HOT N SOUR SOUP
550
1
CHICKEN CHOWMEIN
250
1
CHICKEN KORMA
850
1
And the result I want is but don't get it on adding Date in where clause :
Item
Amount
Qty
KOFTA ANDA
1950
3
HOT N SOUR SOUP
550
1
CHICKEN CHOWMEIN
250
1
CHICKEN KORMA
850
1
CRISPY CHICKEN
0
0
MEXICAN BURGER
0
0
What if you don't put criteria for Invoices in the WHERE clause?
Sample data
create table Categories (
Id int primary key,
Name varchar(30) not null,
TerminalId int not null
);
create table MenuItems (
Id int identity(21,1) primary key,
Name varchar(30) not null,
CategoryId int not null,
foreign key (CategoryId) references Categories(Id)
);
create table Invoices (
Id int identity(31,1) primary key,
TerminalId int not null,
ItemId int not null,
Time datetime,
foreign key (ItemId) references MenuItems(Id)
);
create table InvoiceDetails (
InvoiceDetailId int identity(41,1) primary key,
InvoiceId int,
Amount decimal(10,2),
Qty int,
foreign key (InvoiceId) references Invoices(Id)
);
insert into Categories (Id, Name, TerminalId) values
(1,'KOFTA', 1),
(2,'SOUP', 1),
(3,'CHICKEN', 1),
(4,'BURGER', 1);
insert into MenuItems (CategoryId, Name) values
(1,'KOFTA ANDA'),
(2,'HOT N SOUR SOUP'),
(3,'CHICKEN CHOWMEIN'),
(3,'CHICKEN KORMA'),
(3,'CRISPY CHICKEN'),
(4,'MEXICAN BURGER');
insert into Invoices (ItemId, TerminalId, Time)
select itm.Id, cat.TerminalId, GetDate() as Time
from MenuItems itm
join Categories cat on cat.Id = itm.CategoryId
where itm.Name in (
'KOFTA ANDA',
'HOT N SOUR SOUP',
'CHICKEN CHOWMEIN',
'CHICKEN KORMA'
);
insert into InvoiceDetails (InvoiceId, Amount, Qty) values
(31, 1950, 3),
(32, 550, 1),
(33, 250, 1),
(34, 850, 1);
Query
DECLARE #TerminalId INT = 1;
DECLARE #Date DATE = GetDate();
SELECT
V.[Date],
C.Name AS Category,
M.Name AS MenuItemName,
ISNULL(SUM(D.Amount), 0) AS Amount,
ISNULL(SUM(D.Qty), 0) AS Qty
FROM Categories AS C
CROSS JOIN (SELECT #Date AS [Date], #TerminalId AS TerminalId) V
JOIN MenuItems AS M
ON M.CategoryId = C.Id
LEFT JOIN Invoices I
ON I.ItemId = M.Id
AND I.TerminalId = V.TerminalId
AND CAST(I.Time AS DATE) = V.[Date]
LEFT JOIN InvoiceDetails AS D
ON D.InvoiceId = I.Id
WHERE C.TerminalId = V.TerminalId
GROUP BY V.[Date], C.Id, M.Id, C.Name, M.Name
ORDER BY SUM(D.Qty) DESC
Date
Category
MenuItemName
Amount
Qty
2021-12-18
KOFTA
KOFTA ANDA
1950.00
3
2021-12-18
SOUP
HOT N SOUR SOUP
550.00
1
2021-12-18
CHICKEN
CHICKEN CHOWMEIN
250.00
1
2021-12-18
CHICKEN
CHICKEN KORMA
850.00
1
2021-12-18
CHICKEN
CRISPY CHICKEN
0.00
0
2021-12-18
BURGER
MEXICAN BURGER
0.00
0
Demo on db<>fiddle here
Here's my crack at your goal. Notice the changes. I found the reference to TerminalId in Category table highly suspicious - so much that I suspect it is a model flaw. Along those lines I note that TerminalId should likely have a foreign key to a missing table for Terminals. So I ignore that.
With that out, references to Category are now irrelevant. So that was removed as well. I also changed the procedure name since I find the reference to "day" misleading. It is highly likely "menu performance" would be evaluated on a "day" basis since retail (especially food service) sales vary by day of week consistently. So let's not mislead anyone thinking that is what this procedure does.
For simplicity and clarity, I removed the ISNULL usage. Add it back if desired but such things are usually better handled by the consumer of the resultset. I left the ORDER BY clause as a stub for you to re-evaluate (and you need to).
So how does this work? Simply calculate the sums directly in the CTE and then outer join from the menu items to the CTE sums to get all menu items along with the relevant performance information for the date specified.
CREATE PROCEDURE dbo.GetMenuPerformanceByDate
#Date date,
#Terminal int
AS
BEGIN
with sales as (
select det.ItemId, SUM(det.Amount) as amt, SUM(det.Qty) as qty
from dbo.Invoices as inv
inner join dbo.InvoiceDetails as det
on inv.Id = det.InvoiceId
where cast(inv.Time as date) = #Date
and inv.TerminalId = #Terminal
group by det.ItemId
)
select menu.name, sales.amt, sales.qty
from dbo.MenuItems as menu
left join sales
on menu.Id = sles.ItemId
order by ...
;
END;
One last note. This filter:
cast(inv.Time as date) = #Date
is generally not a good method of filtering a datetime column. Far better to use inclusive lower and exclusive upper boundaries like:
inv.Time >= #date and inv.Time < dateadd(day, 1, #date)
for this reason.
My last note - there is a potential flaw regarding MenuItems. Presumably "name" is unique. It is highly unlikely that multiple rows would have the same name, but "unlikely" is not a restriction. If you generate rows based on name and name turns out to NOT be unique, your results are difficult to interpret.

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;

Frequent itemset SQL

I'm using SAS for a piece of coursework. At the moment, I have a set of Order IDs and Product IDs. I want to found out which products are most frequently ordered together. Think, milk and cereal in a grocery basket.
I am not very good at programming, so would really appreciate if anyone could spare a bit of time and write a simple few lines of SQL I can easily use. Its not a heavy dataset and there are only two columns (Order_ID and Product_ID)
For example:
Order ID Product ID
10001 64564564
10001 546456
10001 54646
10003 5464
10003 342346
I've spent three hours researching now and am a bit desperate :(
If you think about it, you can find the answer by asking the question this way: for every possible pair of products, how many times did the two products occur on the same order. Then order by the count to float the answer(s) to the top:
select
p1.product_id, p2.product_id, count(*) times_order_together
from
orders p1
inner join
orders p2
on
p1.order_id = p2.order_id
and
p1.product_id != p2.product_id
group by
p1.product_id, p2.product_id
order by
count(*) desc
Products that weren't ever ordered together don't show up at all. Also - pairs are represented twice - a row for eggs with milk and a row for milk with eggs. These duplicate pairs are removable - but it gets uglier - and simple is good.
To elaborate a bit, p1 and p2 are aliases of orders. You do that to be able to use a data source more than once - and yet distinguish between them. Also, the count(*) times_order_together is just giving the name 'times_order_together' to the calculation count(*). It's counting the number of times a product pairing occurs in an order.
how about something like:
create table order_together (order_id, product_id1, product_id2);
insert into order_together
(order_id, product_id1, product_id2)
select o1.order_id, o1.product_id, o2.product_id
from order_line o1, order_line o2
where o1.order_id = o2.order_id
/* you dont want them equal and you also dont
want to insert cereal-milk and milk-cereal on the same order*/
and o1.product_id < o2.product_id
now you have pairs of products together and you can go wild with counts and stats. Mind you, this is quite naive and would blow up in volume quite quickly.
Maybe
select count(distinct order_id), o1.product_id, o2.product_id
...
group by o1.product_id, o2.product_id
would be better.
in response to be comment
but you are grabbing pairs of ordered together products, coming from different rows of the same order's order_lines.
Try this on sqlfiddle.com
put this in left, build schema pane. it creates the tables.
create table order_line(order_no int, product_id varchar(10));
create table order_together(order_no int, product_id1 varchar(10), product_id2 varchar(10));
put this in right pane, Run SQL
insert into order_line(order_no, product_id) values(1, 'milk');
insert into order_line(order_no, product_id) values (1, 'cereal');
insert into order_line(order_no, product_id) values (1, 'rice');
insert into order_line(order_no, product_id) values (2, 'milk');
insert into order_line(order_no, product_id) values (2, 'cereal');
insert into order_line(order_no, product_id) values (3, 'milk');
insert into order_line(order_no, product_id) values (3, 'cookies');
insert into order_line(order_no, product_id) values(4, 'milk');
insert into order_line(order_no, product_id) values (4, 'cookies');
insert into order_line(order_no, product_id) values(5, 'rice');
insert into order_line(order_no, product_id) values (5, 'icecream');
select o1.order_no, o1.product_id as product_from_row1, o2.product_id as product_from_row2
from order_line o1, order_line o2
where o1.order_no = o2.order_no
and o1.product_id < o2.product_id
gives:
order_no product_from_row1 product_from_row2
1 milk rice
1 cereal milk
1 cereal rice
2 cereal milk
3 cookies milk
4 cookies milk
5 icecream rice
give it a try, then think about what the query is requesting, which is joining different order_lines of the same order. That's pretty much the definition of "ordered together".

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

SQL table normalization: Simple question about restricting record participation in a many to many relation

If you have the following tables and relations:
A Product table
A Price Point table (one or more prices for a product)
A Version table (groups of products sold in the same area)
and the constraint that only one product price can exist in any given version. How would you construct the tables to reflect the constraints?
To illustrate the restriction, I have a product priced at $1, $2, and $3.
I have media zones of A, B, C, D, E, F, G, H, I (each representing a place like Columbus OH)
product A price $1 goes to A, B, C
product A price $2 goes to D, E, F
product A price $3 goes to G, H, I
Product A price $1 when it exists in A, B, C cannot have other prices ($2, $3) in A, B, C
Would a Version M2M table to PricePoints, then put a unique index on the M2M table on all fields minus the PricePoint field work? (thought about it while typing this out) Is there a better way to represent the relationships?
I'm having a little trouble understanding your question. I don't understand the statement "Product A price $1 when it exists in A, B, C cannot exist in D, E, F, G, H, I.". I'm going to assume, for the sake of this answer, that "version" and "media zone" are the same thing.
Use an intermediate Pricing table with three fields: product_id, version_id, and price_id. The primary key of this table (or a unique index if you choose to use an incremental non-intelligent key) is (product_id, version_id).
Here's how I would construct the tables to reflect the constraints, based on the data supplied:
SQL DDL:
CREATE TABLE Products
(
product_name CHAR(1) NOT NULL UNIQUE
);
CREATE TABLE ProductPrices
(
product_name CHAR(1) NOT NULL
REFERENCES Products (product_name),
product_price DECIMAL(19, 4) NOT NULL
CHECK (product_price > 0),
UNIQUE (product_name, product_price)
);
CREATE TABLE MediaZones
(
zone_name CHAR(1) NOT NULL UNIQUE
);
CREATE TABLE Versions
(
product_name CHAR(1) NOT NULL,
product_price DECIMAL(19, 4) NOT NULL,
FOREIGN KEY (product_name, product_price)
REFERENCES ProductPrices (product_name, product_price),
zone_name CHAR(1) NOT NULL
REFERENCES MediaZones (zone_name),
UNIQUE (product_name, zone_name)
);
SQL DML (succeeds = good):
INSERT INTO Products (product_name) VALUES ('A');
INSERT INTO MediaZones (zone_name)
VALUES ('A'), ('B'), ('C'),
('D'), ('E'), ('F'),
('G'), ('H'), ('I');
INSERT INTO ProductPrices (product_name, product_price)
VALUES ('A', 1),
('A', 2),
('A', 3);
SQL DML (fails = good):
INSERT INTO Versions (product_name, product_price, zone_name)
VALUES ('A', 1, 'G');
INSERT INTO Versions (product_name, product_price, zone_name)
VALUES ('A', 1, 'A');
INSERT INTO Versions (product_name, product_price, zone_name)
VALUES ('A', 1, 'Z');
INTO Versions (product_name, product_price, zone_name)
VALUES ('A', 2, 'A');
etc etc
Unless you can definitively say that a product-price combination is only allowed in specific regions, I think it's better to forgo a data constraint and use a business rule constraint at a stored procedure or other business layer to check to see what data exists before attempting to add product-price-region combinations to the database.