Explode components recursively to get total value - sql

TL-TR: I want to get the total value of a final item that is made of others, from which I have the buying price. Problem is semi-finished items.
Consider three types of items:
Final item: It's made from raw items and/or semifinished items. These are the ones I want to get the total value.
Semifinished item: It's made from raw items and/or semifinished items.
Raw item: I have the cost of a unit of these ones.
A final item can be made, amongst other things, of a semifinished item which can be made, amongst other things, of another semifinished item and so on... on an undeterminate number of levels, until you get to all being raw items.
I know how to do this on C# but I'm interested on a pure SQL solution, that I believe is doable.
SQL Fiddle provided here:
http://sqlfiddle.com/#!6/138c3
And this is as far as I can get with the query...
SELECT BOM.output, SUM(P.price * BOM.quantity) as Total
FROM BOM
INNER JOIN
Prices P ON P.input = BOM.Input
GROUP BY BOM.output
But of course that doesn't take semifinished prices into the sum as they don't exist in prices table.
EDIT: Another attempt, but it gives an error that grouping is not allowed in recursive queries.
WITH cte_table AS (
SELECT BOM.output, SUM(P.price * BOM.quantity) as Total
FROM BOM
INNER JOIN
Prices P ON P.input = BOM.Input
GROUP BY BOM.output
UNION ALL
SELECT BOM.output, SUM(ISNULL(P.price,T.Total) * BOM.quantity) as Total
FROM BOM
LEFT JOIN
Prices P ON P.input = BOM.Input
LEFT JOIN
cte_table T ON T.output = BOM.Input
GROUP BY BOM.output
)
SELECT *
FROM cte_table
And some example of expected output (in light grey it must be calculated, in black is data):

You need to use the recursive query:
SQL Fiddle
Query 1:
with a as(
SELECT BOM.output, BOM.input, P.price * BOM.quantity as price, 1 as level,
convert(varchar(max), BOM.input) as path
FROM BOM
INNER JOIN
Prices P ON P.input = BOM.Input
UNION ALL
SELECT BOM.output, BOM.input, a.price * BOM.quantity as price, a.level + 1 as level,
a.path + '/' + bom.input
FROM a
INNER JOIN BOM ON a.output = BOM.Input)
select output, sum(price) from a
group by output
-- select * from a
Results:
| output | |
|--------|-------------------|
| Item 3 | 64.32000000000001 |
| Semi 1 | 63 |
| Semi 2 | 60.4 |

Related

Selecting data from 3 tables to calculate inventory levels

I am currently setting up an inventory system in phpgrid. I have 3 tables - products, orders, purchases. I am trying to list all of the PartNumber field from products and the total of the NumberReceived for each PartNumber from purchases along with the total of the NumberShipped for each PartNumber from orders.
From there I believe I can use phpgrid to display a virtual/calculated column that will subtract the NumberShipped from the NumberReceived and give me the current stock level of each PartNumber.
Looking for results something like this:
PartNumber | Received | Shipped | (calculated) Qty On Hand
______________________________________________________________
12345 | 20 | 10 | 10
67890 | 40 | 5 | 35
I am having trouble wrapping my head around how to join everything together to retrieve the data that I want. This SELECT displays some data but not every PartNumber with the proper calculations.
"SELECT
p.id,
sum(p.NumberReceived) AS 'Received',
sum(o.NumberShipped) As 'Shipped'
FROM purchases p
INNER JOIN orders o on p.id = o.ProductId
GROUP BY p.id ",
"id", "purchases");
I realized that I need to have the PartNumber column in there in order to list all the data of the PartNumber field. So then I tried this - I think I'm close but this keeps giving me an error - PHPGRID_ERROR: Could not execute query. Error 102
"SELECT
prod.id,prod.PartNumber,
sum(pur.NumberReceived) AS 'Received',
sum(o.NumberShipped) As 'Shipped',
FROM products prod
LEFT JOIN orders o ON prod.PartNumber = o.ProductId
LEFT JOIN purchases pur ON prod.PartNumber = pur.ProductId",
"id", "products");
I'm hoping someone could help steer me in the right direction. Thank you all!
If you are summing you need to group by. The following should work. If not, send specific error message.
(you also had an extra comma after the last item in the select block)
SELECT
prod.id,prod.PartNumber,
sum(pur.NumberReceived) AS Received,
sum(o.NumberShipped) As Shipped
FROM products prod
LEFT JOIN orders o ON prod.PartNumber = o.ProductId
LEFT JOIN purchases pur ON prod.PartNumber = pur.ProductId"
GROUP BY prod.id, prod.PartNumber;

Get the total sum of 2 multiplied columns SQL

I have these 3 tables:
Bill:
idBill
Products:
idProduct
price
BillProducts (that connects the tables above):
idBill
idProduct
quantity
Now let's say I wish to get the total price of a certain bill identified by its ID
I would need to multiply the columns of Products.Price by BillProducts.quantity, get its result and sum all the others products in that idBill
Can you guys help me writing that query?
SELECT SUM(QUANTxPRICE) AS SUMED, IDBILL FROM (
SELECT (A.QUANTITY * B.PRICE) AS QUANTxPRICE,a.IDBILL
FROM BILLPRODUCTS AS A
JOIN PRODUCTS AS B ON
A.IDPRODUCT = B.IDPRODUCT
JOIN BILL AS C
ON A.IDBILL = C.IDBILL
) AS X
GROUP BY IDBILL
You would do:
select sum(p.price * bp.product)
from billproducts bp join
products p
on bp.idproduct = p.idproduct
where idbill = <idbill>;
You can get this for all bills using group by:
select idbill, sum(p.price * bp.product)
from billproducts bp join
products p
on bp.idproduct = p.idproduct
group by idbill;
Notes:
You do not need to join the bills table. All the information you need is in the other two tables.
You do not need a subquery.
When you define table aliases, they should be abbreviations for the table names, so the query is easier to follow.

SQL Server: query multiple tables to create a pre-formated "template" csv for export into Excel

I am trying to create the SQL necessary to accomplish the following. As an aside, I am using server side Report Builder against a hosted SQL Server database so am limited in what I can do. There are 3 tables. A salesperson table (sales), an items table (items), and a transaction table (tx).
Here is an example:
Sales Person table (sales)
Person A
Person B
Items ID table (items)
10
20
30
100
200
300
1000
2000
3000
Transaction table (tx)
100 (person A)
300 (person B)
300 (person A)
200 (person B)
Desired result in Report Builder:
Person A
Item 100: 1
Item 200: 0 (NULL)
Item 300: 1
-- NEW PAGE --
Person B:
Item 100: 0 (NULL)
Item 200: 1
Item 300: 1
My problem: here is the SQL I came up with. I need to be able to generate a consistent result set, regardless of whether an item was sold or not by a particular salesperson for easier import into Excel. In addition, I am only looking for items whose code is between 100 and 300 and within a specified date range. My SQL is ignoring the date range and item code range. I originally had these instructions in a WHERE clause but it returned only those lines that were in both tables and I lost the placeholder for any itemcode where the value was null (acting as an INNER join). Within Report Builder I will be counting how many of each items were sold by salesperson.
SELECT
tx.date, sales.salesperson, items.itemcode
FROM
tx
LEFT OUTER JOIN
itemcode ON (tx.itemcode = items.itemcode)
AND (date BETWEEN "10/1/2017" AND "12/31/2017")
AND (itemcode BETWEEN "100" AND "300")
INNER JOIN
sales ON (tx.salesID = sales.salesID)
ORDER BY
itemcode ASC
Many thanks for any and all insight into my challenge!
If you want all sales people and all items, then you can generate the rows using a cross join. You can bring in the available data using left join or exists:
select s.person, i.itemcode,
(case when exists (select 1
from tx
where tx.salesid = s.salesid and tx.itemcode = i.itemcode
)
then 1 else 0
end) as has_sold
from sales s cross join
items i
where i.itemcode between 100 and 300
order by s.saledid, i.itemcode;
If you want the count of items, use left join and group by:
select s.person, i.itemcode, count(tx.salesid) as num_sold
from sales s cross join
items i left join
tx
on tx.salesid = s.salesid and tx.itemcode = i.itemcode
where i.itemcode between 100 and 300
order by s.saledid, i.itemcode;
Here's an example that uses both cross join (to get all combinations of sales and items) and left join (to get the transactions for given dates)
SELECT
tx.date, sales.salesperson, items.itemcode
FROM
Items
CROSS JOIN
sales
LEFT OUTER JOIN
tx ON (items.itemcode = tx.itemcode)
AND date BETWEEN '10/1/2017' AND '12/31/2017'
AND (tx.salesID = sales.salesID)
WHERE
(items.itemcode BETWEEN '100' AND '300')
ORDER BY
itemcode ASC

Select SUM from multiple tables

I keep getting the wrong sum value when I join 3 tables.
Here is a pic of the ERD of the table:
(Original here: http://dl.dropbox.com/u/18794525/AUG%207%20DUMP%20STAN.png )
Here is the query:
select SUM(gpCutBody.actualQty) as cutQty , SUM(gpSewBody.quantity) as sewQty
from jobOrder
inner join gpCutHead on gpCutHead.joNum = jobOrder.joNum
inner join gpSewHead on gpSewHead.joNum = jobOrder.joNum
inner join gpCutBody on gpCutBody.gpCutID = gpCutHead.gpCutID
inner join gpSewBody on gpSewBody.gpSewID = gpSewHead.gpSewID
If you are only interested in the quantities of cuts and sews for all orders, the simplest way to do it would be like this:
select (select SUM(gpCutBody.actualQty) from gpCutBody) as cutQty,
(select SUM(gpSewBody.quantity) from gpSewBody) as sewQty
(This assumes that cuts and sews will always have associated job orders.)
If you want to see a breakdown of cuts and sews by job order, something like this might be preferable:
select joNum, SUM(actualQty) as cutQty, SUM(quantity) as sewQty
from (select joNum, actualQty, 0 as quantity
from gpCutBody
union all
select joNum, 0 as actualQty, quantity
from gpSewBody) sc
group by joNum
Mark's approach is a good one. I want to suggest the alternative of doing the group by's before the union, simply because this can be a more general approach for summing along multiple dimensions.
Your problem is that you have two dimensions that you want to sum along, and you are getting a cross product of the values in the join.
select joNum, act.quantity as ActualQty, q.quantity as Quantity
from (select joNum, sum(actualQty) as quantity
from gpCutBody
group by joNum
) act full outer join
(select joNum, sum(quantity) as quantity
from gpSewBody
group by joNum
) q
on act.joNum = q.joNum
(I have kept Mark's assumption that doing this by joNum is the desired output.)

Selecting records in SQL that have the minimum value for that record based on another field

I have a set of data, and while the number of fields and tables it joins with is quite complex, I believe I can distill my problem down using the required fields/tables here for illustration regarding this particular problem.
I have three tables: ClientData, Sources, Prices
Here is what my current query looks like before selecting the minimum value:
select c.RecordID, c.Description, s.Source, p.Price, p.Type, p.Weight
from ClientData c
inner join Sources s ON c.RecordID = s.RecordID
inner join Prices p ON s.SourceID = p.SourceID
This produces the following result:
RecordID Description Source Price Type Weight
=============================================================
001002003 ABC Common Stock Vendor 1 104.5 Close 1
001002003 ABC Common Stock Vendor 1 103 Bid 2
001002003 ABC Common Stock Vendor 2 106 Close 1
001002003 ABC Common Stock Vendor 2 100 Unknwn 0
111222333 DEF Preferred Stk Vendor 3 80 Bid 2
111222333 DEF Preferred Stk Vendor 3 82 Mid 3
111222333 DEF Preferred Stk Vendor 2 81 Ask 4
What I am trying to do is display prices that belong to the same record which have the minimum non-zero weight for that record (so the weight must be greater than 0, but it has to be the minimum from amongst the remaining weights). So in the above example, for record 001002003 I would want to show the close prices from Vendor 1 and Vendor 2 because they both have a weight of 1 (the minimum weight for that record). But for 111222333 I would want to show just the bid price from Vendor 3 because its weight of 2 is the minimum, non-zero for that record. The result that I'm after would like like:
RecordID Description Source Price Type Weight
=============================================================
001002003 ABC Common Stock Vendor 1 104.5 Close 1
001002003 ABC Common Stock Vendor 2 106 Close 1
111222333 DEF Preferred Stk Vendor 3 80 Bid 2
Any ideas on how to achieve this?
EDIT: This is for SQL Server Compact Edition.
I was able to come up with the solution so I thought I would share it:
SELECT x.RecordID, VendorSource, VendorPrice
FROM ClientData x
INNER JOIN Sources s ON x.RecordID = s.RecordID
INNER JOIN Prices p ON s.SourceID = p.SourceID
INNER JOIN (SELECT c.RecordID, MIN(Weight) min_weight
FROM ClientData c
INNER JOIN Sources s ON c.RecordID = s.RecordID
INNER JOIN Prices p ON s.SourceID = p.SourceID
WHERE Weight != 0
GROUP BY c.RecordID) w ON x.RecordID = w.RecordID
WHERE p.Weight = w.min_weight
This allows the minimum weight to be populated on a RecordID level in the derived table, so there is 1 weight per RecordID.
For all those who gave answers, thank you; I appreciate the help and any guidance that was offered.
You can use RANK() with a Partition over RecordId with increasing weights to 'rate' each row (after excluding zero weights entirely), and then simply filter out the top ranked rows. The CTE used just to keep the second query simple + clear
;WITH MyRecords AS
(
-- Your source query goes here
select c.RecordID, c.Description, s.Source, p.Price, p.Type, p.Weight
from ClientData c
inner join Sources s ON c.RecordID = s.RecordID
inner join Prices p ON s.SourceID = p.SourceID
)
SELECT RecordID, [Description], [Source], [Price], [Type], [Weight]
FROM
(
SELECT RecordID, [Description], [Source], [Price], [Type], [Weight],
-- With ranking, the lower the weight the better
Rnk = RANK() OVER (PARTITION BY RecordId ORDER BY [Weight] ASC)
FROM MyRecords
-- But exclude Weight 0 entirely
WHERE [Weight] > 0
) RankedRecords
-- We just want the top ranked records, with ties
WHERE Rnk = 1
Edit CE constraint added after the post. See How would I duplicate the Rank function in a Sql Server Compact Edition SELECT statement? on how to simulate RANK() over in CE.
I think you need to change your structure up a little bit to actually make this work as you would like it to. Basically the way you have it a price record is set up against a Source rather than against the Item which seems to be in the ClientData table. By removing the c.Record number column from the Sources table and putting it into the Prices table you should get the correct One(ClientData) to many (Prices), and One(ClientData) to many(Sources) relationships that I think you need.
select c.RecordID, c.Description, s.Source, p.Price, p.Type, p.Weight
from ClientData c
inner join Prices p ON c.RecordID = p.RecordID
inner join Sources s ON s.SourceID = p.SourceID
AND p.Weight> 0
LEFT OUTER JOIN #Prices p2 ON c.RecordID = p2.RecordID
AND p2.PriceID <> p.priceID
AND p2.Weight > 0
AND p2.Weight < p.Weight
WHERE p2.SourceID IS NULL
If you make the change specified above then this query will returns the exact data that you are looking for.