Splitting transaction order lines in two categories - mysql

I'm building a bookkeeping application for a friend that runs a shop-in-shop. Basically, every product is owned by a seller.
They want an overview where they can see the value of how much money in a transaction was paid for their products (owned by seller 1) and how much money went to other products (seller != 1).
Here's the ERD:
I tried the following query:
SELECT transaction.id, SUM(orderline.count), SUM(orderline.price), SUM(o1.price), SUM(o2.price), transaction.date
FROM transaction
JOIN orderline
ON orderline.transaction_id = transaction.id
JOIN product
ON product.barcode = orderline.product_barcode
JOIN orderline o1
ON o1.transaction_id = transaction.id AND o1.product_barcode IN (SELECT barcode FROM product WHERE seller_id = 1)
JOIN orderline o2
ON o2.transaction_id = transaction.id AND o2.product_barcode IN (SELECT barcode FROM product WHERE seller_id != 1)
GROUP BY transaction.id
Unfortunately, this only returns one transaction instead of all, and it doesn't return the correct sums of the prices either:
In for transaction 77, the value of SUM(o1.price) should be 4.5, and that of SUM(o2.price) should be 0.95. The value of SUM(orderline.price) is correct.
I would like to instead get all transactions, and then the total value, value of products owned by seller 1 and value of products owned by seller != 1 (products should be part of the transaction, of course).

SELECT TRANSACTION.id,
Sum(orderline.count),
Sum(orderline.price),
Sum( case when product.seller_id = 1 then orderline.price else 0 end ),
Sum( case when product.seller_id <> 1 then orderline.price else 0 end),
TRANSACTION.date
FROM TRANSACTION
JOIN orderline
ON orderline.transaction_id = TRANSACTION.id
JOIN product
ON product.barcode = orderline.product_barcode
GROUP BY TRANSACTION.id , TRANSACTION.date

Related

How do I get the MIN value from two columns along with corresponding id?

My query does return the lowest price from the two columns (price_base, price_special) but it is not returning the correct store_id that corresponds to the lowest price found.
My Query:
SELECT grocery_item.id, grocery_item.category,
grocery_category.name AS cat, grocery_item.name AS itemName,
MIN( if( grocery_price.price_special>0,
grocery_price.price_base)) AS price,
grocery_price.store_id,
grocery_store.name AS storeName
FROM grocery_item
LEFT JOIN grocery_category ON
grocery_category.id=grocery_item.category
LEFT JOIN grocery_price
ON grocery_price.item_id = grocery_item.id
LEFT JOIN grocery_store
ON grocery_store.id=grocery_price.store_id
WHERE grocery_price.selection='no'
AND buy='yes'
GROUP BY grocery_price.item_id
ORDER BY store_id, grocery_item.category, grocery_item.name
Returns this:
ID category cat itemName price store_id storeName
92 3 Bread/Bakery Arnold Bread 2.14 1 Food Lion
But the grocery_price table holds this info:
item_id price_base price_special store_id
92 4.29 2.14 9
92 3.99 0.00 1
so the store_id I need to be returned is 9 (the storeName returned would NOT then be Food Lion)
EDIT: WORKING QUERY based on Uueerdo's comments (thank you!)
SELECT minP.item_id, gi.category, gc.name AS cat,
gi.name as itemName, gp.store_id,
gs.name AS storeName, minP.price
FROM
(SELECT p.item_id, MIN(IF(p.price_special >0,
p.price_special,p.price_base)) AS price
FROM grocery_item AS i
INNER JOIN grocery_price AS p ON (i.id = p.item_id)
WHERE i.buy = 'yes'
GROUP BY p.item_id) AS minP
INNER JOIN grocery_item AS gi ON minP.item_id = gi.id
INNER JOIN grocery_category AS gc on gi.category = gc.id
LEFT JOIN grocery_price AS gp
ON minP.price = IF(gp.price_special > 0,
gp.price_special,gp.price_base)
AND gp.item_id = gi.id
INNER JOIN grocery_store AS gs ON gp.store_id = gs.id
GROUP BY gi.id
ORDER BY gs.id, gi.category,gi.name
The values returned for non-grouped, non-aggregated fields are an (effectively) random selection from the values encountered with the grouped fields' values. Most RDBMS do not even consider such a query valid, and even newer versions of MySQL default to disallowing such queries.
In cases like yours, where you need the non-grouped value(s) associated with the aggregate result (min in this case); the aggregating query must be converted into a subquery, that can be joined back to the aggregated tables to find the source row(s) that correspond to the aggregated value.
Edit: Basically, you need to look at the problem slightly differently. You're currently finding the lowest price for an item and a store that item is listed for; you need to find the lowest price for an item, and use that to find the store(s) that have the item at that price.
This gets you the lowest price for item's marked "buy":
SELECT p.item_id, MIN(IF(p.price_special > 0,p.price_special,p.price_base)) AS price
FROM grocery_item AS i
INNER JOIN grocery_price AS p ON (i.id = p.item_id)
WHERE i.buy = 'yes'
GROUP BY p.item_id
You can then take that to get the rest of the results:
SELECT minP.item_id
, gi.name
, gi.category, gc.category_name AS cat, gi.Name as itemName, gi.buy
, gp.store_id, gp.name AS storeName
, minP.price
FROM ([the query above]) AS minP
INNER JOIN grocery_item AS gi ON minP.item_id = gi.id
INNER JOIN grocery_category AS gc on gi.category = gc.grocery_category_id
/* Guessing on this join since grocery_category_id
was not qualified with it's table name */
INNER JOIN grocery_price AS gp
ON minP.price = IF(gp.price_special > 0,gp.price_special,gp.price_base)
/* Alternatively: ON minP.price IN (gp.price_special, gp.price_base)
... though this could cause false positives if the minP.price is 0
from one store's base price being "free"
*/
INNER JOIN grocery_store AS gs ON gp.store_id = gs.id
;

SQL Join table with calculated fields

I want to add another column to this query to show backorders that need to be processed.
Currently the table is showing all orders that have outstanding items and the in stock amount for each stock ref included.
I want to to add a field for suggested invoice amount.
I.e if the order qty is 5 , the invoice qty is 2 and the in stock qty is 2 then suggest to deliver 2.
if the instock amount was 100 then suggest to deliver 3.
My current attempt is below but I cant work out how to do the last bit.
I know i need to do (qtyOrdered-qtyinvoiced) but literally hit a brick wall.
SELECT
c.cus_name,
ol.orderLines_stockRef,
ol.orderLines_qtyOrdered,
ol.orderlines_qtyInvoiced,
pr.products_instock
FROM
cus c
JOIN
orderheader oh ON oh.orderHeader_customer = c.cus_id
JOIN
orderlines ol ON ol.orderLines_orderId = oh.orderHeader_id
JOIN
products pr ON ol.orderlines_stockref = pr.products_reference
WHERE
ol.orderLines_qtyOrdered <> ol.orderlines_qtyInvoiced
Current output :
Try this:
SELECT
c.cus_name,
ol.orderLines_stockRef,
ol.orderLines_qtyOrdered,
ol.orderlines_qtyInvoiced,
pr.products_instock,
(CASE WHEN ol.orderLines_qtyOrdered - ol.orderlines_qtyInvoiced > pr.products_instock
THEN pr.products_instock
ELSE ol.orderLines_qtyOrdered - ol.orderlines_qtyInvoiced) AS qtyToSend,
FROM
cus c
JOIN
orderheader oh ON oh.orderHeader_customer = c.cus_id
JOIN
orderlines ol ON ol.orderLines_orderId = oh.orderHeader_id
JOIN
products pr ON ol.orderlines_stockref = pr.products_reference
WHERE
ol.orderLines_qtyOrdered <> ol.orderlines_qtyInvoiced

Mysql LEFT JOIN in combination with optional relation parent<->child table not working

I am having some troubles when combining left joins with optional values to group by.
In short we have
table child products: catalog
table parent relations - if it exists! contains parent child relations : catalog_product_relation
table warehouses : warehouses
table products and stock : wh_products
and we are trying to get the stock total qty, where
stock is stored by child products and needs to be summed per that
if a parent relation exists then we should group by and show that data
even simpler
for products without a parent: just sum qty (already works)
for products with a parent: then rollup and sum by the parent identifier
and preferably also get the data for the parent identifier from the catalog (ie the right name and sku)
I hope it is clear ... the query below is not working. Step 1 to me looked like just getting the parent ID in a column
and step 2 is to sum the qty's for simple products per simple product and for parent products sum it up for all child simple product but show the parent data
select c.sku as sku,
IFNULL(pr.parent_id, c.entity_id) as main_id,
c.type_id as type,
SUM(CASE WHEN (wh.code='LR') THEN FLOOR(wp.qty) ELSE 0 END) AS 'Loc1',
SUM(CASE WHEN (wh.code='LS') THEN FLOOR(wp.qty) ELSE 0 END) AS 'Loc2'
FROM catalog c,
wh_products wp,
warehouses wh
LEFT JOIN catalog_product_relation pr ON pr.parent_id = c.entity_id
WHERE c.entity_id = wp.product_id and wp.warehouse_id = wh.warehouse_id and wp.qty > 0
GROUP BY main_id
I would start by writing the JOINs correctly and aggregating by the columns in the SELECT:
SELECT c.sku as sku,
COALESCE(pr.parent_id, c.entity_id) as main_id, c.type_id as type,
SUM(CASE WHEN wh.code = 'LR' THEN FLOOR(wp.qty) ELSE 0 END) AS Loc1,
SUM(CASE WHEN wh.code = 'LS' THEN FLOOR(wp.qty) ELSE 0 END) AS Loc2
FROM catalog c LEFT JOIN
wh_products wp
ON c.entity_id = wp.product_id LEFT JOIN
warehouses wh
ON wp.warehouse_id = wh.warehouse_id AND wp.qty > 0 LEFT JOIN
catalog_product_relation pr
ON pr.parent_id = c.entity_id
GROUP BY c.sku, COALESCE(pr.parent_id, c.entity_id), c.type_id;
I'm not sure if this fixes your problems. Without sample data and desired results, it is a bit hard to follow the logic.

Multiple Filter inside SELECT SQL Statement

I'm writing a SQL Statement to get some values in a Recordset, which I'll use to transfer the result into TextBoxes on a Form in Excel. The tables involved are:
Customers -> CustomerId, FirstName, LastName, TelNumber
Invoice -> InvoiceId, CustomerId, CarModel, CarColor, CarPlate
Repairs -> RepairId, InvoiceId, TypeOfRepair, PartOfCar, Price
Services -> ServiceId, InvoiceId, Date, Status
When a Customer comes to the Garage, an Invoice is created, which is associated with this customer. An invoice can have many Repairs. The customer goes away without repairing the car, but the invoice is there. If the customer decides to repair the car, then a Service is created, which starts with the Status "working on it...". When the service is done, the status change to "Waiting for Check Out..."
I want to use a SQL Statement to retrieve the following Values (columns) for a specific InvoiceId:
CarModel, Color, Plate, CustomerName (FirstName LastName), PaintingTotalValue (where 'Painting' is one type in the column 'Type'), OtherTotalValue (total price of all the other types of repairs in this invoice), total price (total price, that is, painting + other ones).
I wrote the following, to get the values, but I don't know how to get the PaintingTotalValue and OtherTotalVAlue.
SELECT i.CarModel, i.Color, i.Plate, CONCAT(c.FirstName,' ',c.LastName) AS Name, FORMAT(SUM(r.Price),2) AS TotalPrice
FROM Services AS s INNER JOIN Invoices AS i ON s.invoiceId=i.invoiceId
INNER JOIN Repairs AS r ON s.invoiceId=r.invoiceId
INNER JOIN Customers AS c ON i.customerId=c.customerId
WHERE s.invoiceId = 15
Use CASE WHEN in your SELECT clause, to select the value that's conditional to the type:
SELECT
...
CASE WHEN r.Type = 'Painting' THEN r.Price ELSE 0 END PaintWorkPrice,
CASE WHEN r.Type <> 'Painting' THEN r.Price ELSE 0 END OtherWorkPrice,
FROM ...
That's one thing.
The other thing is that you're not selecting anything from the Services table, and making your query much more complicated than it needs to be.
If you can modify the schema, remove the ServiceId primary key field, and use Services.InvoiceId as a primary key instead: that will enforce the 1:1 relationship naturally.
FROM Repairs r
INNER JOIN Invoices i ON r.InvoiceId = i.InvoiceId
INNER JOIN Customers c ON i.CustomerId = c.CustomerId
The data you want to aggregate is granular to Repairs, so you select FROM that, and then move your way through the foreign keys up to Customers.
SELECT
i.CarModel
,i.Color
,i.Plate
,CONCAT(c.FirstName,' ',c.LastName) Name
,CASE WHEN r.Type = 'Painting' THEN r.Price ELSE 0 END PaintWorkPrice
,CASE WHEN r.Type <> 'Painting' THEN r.Price ELSE 0 END OtherWorkPrice
,r.Price
FROM Repairs r
INNER JOIN Invoices i ON r.InvoiceId = i.InvoiceId
INNER JOIN Customers c ON i.CustomerId = c.CustomerId
That's not aggregated yet: there's a record for each repair, for every invoice, under every customer that has an invoice. That part is the sub-query. If you have a parameter, that's where you use it.
WHERE i.InvoiceId = pInvoiceId
If you're just hard-coding an ID, that's where you do it too.
Now type SELECT q.* FROM ( on the line above, and ) q under the WHERE clause, then replace the q.* with the fields you're not aggregating - and aggregate the others. The result should be something like this:
SELECT
q.CarModel
,q.Color
,q.Plate
,q.Name
,SUM(q.PaintWorkPrice) PaintAmount
,SUM(q.OtherWorkPrice) OtherAmount
,SUM(q.Price) TotalAmount
FROM (
SELECT
i.CarModel
,i.Color
,i.Plate
,CONCAT(c.FirstName,' ',c.LastName) Name
,CASE WHEN r.Type = 'Painting' THEN r.Price ELSE 0 END PaintWorkPrice
,CASE WHEN r.Type <> 'Painting' THEN r.Price ELSE 0 END OtherWorkPrice
,r.Price
FROM Repairs r
INNER JOIN Invoices i ON r.InvoiceId = i.InvoiceId
INNER JOIN Customers c ON i.CustomerId = c.CustomerId
WHERE i.InvoiceId = 15
) q
GROUP BY
q.CarModel
,q.Color
,q.Plate
,q.Name

MySQL SELECT all people matching X and then all in each person's household

I have two tables I need to query to print a list - PURCHASE HISTORY and PEOPLE
PURCHASE HISTORY
----------------
purchase_txnid...purchase_date...purchase_userid...purchase_productid
PEOPLE
------
people_householdid...people_userid...people_street...people_city...people_state...(etc)
I need to get everyone in householdid where someone has purchased productid = "X" (basically a list of everyone at any house where SOMEONE has purchased the product) and then display each of their purchase histories.
PURCHASE HISTORY has seven distinct products and 320,000 records.
Right now, I'm querying all people, RIGHT JOINing the purchase history to select people who purchased "X", and then iterating through the results and, with each result, querying to SELECT everyone in that household and each of their purchase histories. It's extremely slow as there are 45,000 people and 320,000 purchases.
Any ideas as to how I can merge this into a single query or optimize it?
UPDATE
Here are the queries:
$buyers = $db->get_results( " SELECT people.*, TIMESTAMPDIFF( YEAR, birth_date, CURDATE() ) AS age FROM people
RIGHT JOIN purchase_history ON purchase_history.purchase_userid = people.userid
WHERE people.region=$region AND purchase_history.purchase_productid = 'D'
GROUP BY people.userid
ORDER BY street_name ASC, street_suffix ASC, street_num ASC, street_unit ASC, household_id DESC, birth_date ASC
" );
foreach( $buyers as $buyer ){
$in_household = $db->get_results( "SELECT *, TIMESTAMPDIFF( YEAR, birth_date, CURDATE() ) AS person_age FROM people WHERE household_id = '$buyer->household_id' ORDER BY birth_date ASC" );
foreach( $in_household as $person ){
$purchases = $db->get_results( "SELECT * FROM purchase_history WHERE purchase_userid='$person->userid'" );
}
}
SELECT DISTINCT peopleB.userid, peopleB.* FROM purchase
JOIN people AS peopleA ON peopleA.people_userid = purchase_userid
JOIN people AS peopleB ON peopleB.people_householdid = peopleA.householdid
WHERE purchase.purchase_productid = "X"
You can speed up this query by adding these indices to your tables:
CREATE INDEX productid ON purchase (purchase_productid)
CREATE INDEX householdid ON people (people_householdid)
I assume people_userid is already your primary key in people, otherwise you should make it unique:
CREATE UNIQUE INDEX userid ON people (people_userid)
EDIT - You asked for the complete purchase history:
SELECT peopleB.*, phB.* FROM purchase_history AS phA
JOIN people AS peopleA ON peopleA.people_userid = phA.purchase_userid
JOIN people AS peopleB ON peopleB.people_householdid = peopleA.householdid
LEFT JOIN purchase_history AS phB ON phB.purchase_userid = peopleB.userID
GROUP BY peopleB.id, phB.purchase_txnid
WHERE purchase.purchase_productid = "X"
Note that this will give you people's data once for every purchase they made.
Also, I don't know if that query really works the way I want it to or how fast/slow it will be. You could also try reversing the colums in the GROUP BY line and see if that is faster.
You can do this with a subquery and a join:
select p.*
from people p join
(select distinct p.people_householdid
from PurchaseHistory ph join
People p
on ph.purchase_userid = p.people_userid
where ph.purchase_productid = 'X'
) ph
on ph.people_userid = p.people_userid;
EDIT:
If you want to flag the member(s) that made the purchase:
select p.*,
max(p.people_userid = ph.people_userid) as IsPurchaser
from people p join
(select p.people_householdid, p.people_userid
from PurchaseHistory ph join
People p
on ph.purchase_userid = p.people_userid
where ph.purchase_productid = 'X'
) ph
on ph.people_userid = p.people_userid
group by p.people_userid