MYSQL combine and select rows by two reference tables - mysql

I am losing my mind over this problem, research has not helped. Is this an anti-pattern and hard because of that, or is it just me...
The IDEA here is that we have a single table for every material we use, we have multiple storage shelf's in which that material is stored on. Every stocklocation has individual quantities and minimum limits.
What I want to achieve is a single SQL-query that outputs this:
+-------+------+---------------------+----------+-----------------+
| SL_Id | M_Id | TimeCreated | Quantity | MinimumQuantity |
+-------+------+---------------------+----------+-----------------+
| 1 | 2 | 2017-11-02 13:04:18 | 10 | 5 |
| 1 | 3 | NULL | NULL | 15 |
| 1 | 4 | 2017-11-02 15:56:56 | 7 | NULL |
+-------+------+---------------------+----------+-----------------+
This is where I am at the moment....
+-------+------+---------------------+----------+-----------------+
| SL_Id | M_Id | TimeCreated | Quantity | MinimumQuantity |
+-------+------+---------------------+----------+-----------------+
| 1 | 2 | 2017-11-02 13:04:18 | 10 | NULL |
| 1 | 3 | NULL | NULL | 15 |
| 1 | 4 | 2017-11-02 15:56:56 | 7 | NULL |
+-------+------+---------------------+----------+-----------------+
Here are the tables:
Material
+----+-----------+
| Id | OrderCode |
+----+-----------+
| 2 | asdf |
| 3 | 75424 |
| 4 | 45567 |
+----+-----------+
StockLocation
+----+-------+
| Id | Label |
+----+-------+
| 1 | asdf |
+----+-------+
Inventory
+----+------------------+-------------+---------------------+----------+
| Id | StockLocation_Id | Material_Id | TimeCreated | Quantity |
+----+------------------+-------------+---------------------+----------+
| 1 | 1 | 2 | 2017-11-02 13:04:18 | 10 |
| 2 | 1 | 4 | 2017-11-02 15:23:26 | 9 |
| 3 | 1 | 4 | 2017-11-02 15:56:56 | 7 |
+----+------------------+-------------+---------------------+----------+
StockLocationMaterialLimit
+----+------------------+-------------+-----------------+
| Id | StockLocation_Id | Material_Id | MinimumQuantity |
+----+------------------+-------------+-----------------+
| 1 | 1 | 2 | 5 |
| 2 | 1 | 3 | 15 |
+----+------------------+-------------+-----------------+
This is the monster behind failure,
SELECT
SL_Id,
M_Id,
TimeCreated,
Quantity,
MinimumQuantity
FROM (
SELECT
SL.Id AS SL_Id,
M.Id AS M_Id,
I.TimeCreated AS TimeCreated,
I.Quantity AS Quantity,
NULL AS MinimumQuantity
FROM StockLocation SL, Material M
JOIN Inventory I on I.Id = (
SELECT Id FROM Inventory I1 WHERE I1.StockLocation_Id=SL.Id AND I1.Material_Id=M.Id ORDER BY I1.TimeCreated DESC LIMIT 1
)
UNION
SELECT
SL.Id AS SL_Id,
M.Id AS M_Id,
NULL AS TimeCreated,
NULL AS Quantity,
SLML.MinimumQuantity AS MinimumQuantity
FROM StockLocation SL, Material M
JOIN StockLocationMaterialLimit SLML on SLML.Id = (
SELECT Id FROM StockLocationMaterialLimit SLML1 WHERE SLML1.StockLocation_Id=SL.Id AND SLML1.Material_Id=M.Id LIMIT 1
)
) tst GROUP BY SL_Id,M_Id

It turned out to be as easy as adding MAX() on those fields which produced NULL values.
SELECT
M_Id,
SL_Id,
TimeCreated,
MAX(Quantity) AS Quantity,
MAX(MinimumQuantity) AS MinimumQuantity
FROM (
SELECT
M.Id AS M_Id,
SL.Id AS SL_Id,
I.TimeCreated AS TimeCreated,
I.Quantity AS Quantity,
NULL AS MinimumQuantity
FROM StockLocation SL, Material M
INNER JOIN Inventory I on I.Id = (
SELECT Id FROM Inventory I1 WHERE I1.StockLocation_Id=SL.Id AND I1.Material_Id=M.Id ORDER BY I1.TimeCreated DESC LIMIT 1
)
UNION
SELECT
M.Id AS M_Id,
SL.Id AS SL_Id,
NULL AS TimeCreated,
NULL AS Quantity,
SLML.MinimumQuantity AS MinimumQuantity
FROM StockLocation SL, Material M
INNER JOIN StockLocationMaterialLimit SLML on SLML.Id = (
SELECT Id FROM StockLocationMaterialLimit SLML1 WHERE SLML1.StockLocation_Id=SL.Id AND SLML1.Material_Id=M.Id LIMIT 1
)
) tst GROUP BY SL_Id, M_Id;

Related

MySQL query with sum fields from other table, with a twist

Sorry for the vague title, but I don't know how to word this type of problem better. Here is a simple example to explain it. I have to tables: OrderItemList and OrderHistoryLog.
OrderItemList:
|------------------------------|
| OrderNo | ItemNo | Loc | Qty |
|------------------------------|
| 100 | A | 1 | 1 |
| 101 | A | 1 | 2 |
| 102 | A | 1 | 1 |
| 103 | A | 2 | 1 |
| 104 | A | 2 | 1 |
OrderHistoryLog:
|------------------------------|
| OrderNo | ItemNo | Loc | Qty |
|------------------------------|
| 50 | A | 1 | 5 |
| 51 | A | 1 | 2 |
| 100 | A | 1 | 1 |
| 102 | A | 1 | 3 |
| 103 | A | 2 | 1 |
I need to show the records in the OrderItemList along with a LocHistQty field, which is the sum(Qty) from the OrderHistoryLog table for a given Item and Location, but only for the orders that are present in the OrderItemList.
For the above example, the result should be:
Result:
|------------------------------------------------------
| OrderNo | ItemNo | Loc | Qty | HistQty | LocHistQty |
|------------------------------|-----------------------
| 100 | A | 1 | 1 | 1 | 4 |
| 101 | A | 1 | 2 | 0 | 4 |
| 102 | A | 1 | 1 | 3 | 4 |
| 103 | A | 2 | 1 | 1 | 1 |
| 104 | A | 2 | 1 | 0 | 1 |
It is the last field, LocHistQty that I could use some help with. Here is what I started with (does not work):
select OI.OrderNo, OI.ItemNo, OI.Loc, OI.Qty, IFNULL(OL.Qty, 0) as HistQty, OL2.LocHistQty
from OrderItemList OI
left join OrderItemLog OL on OL.OrderNo = OI.OrderNo and OL.ItemNo = OI.ItemNo
join
(
select ItemNo, Loc, sum(qty) as LocHistQty
from zOrderItemLog
group by ItemNo, Loc
) as OL2
on OL2.ItemNo = OI.ItemNo and OL2.Loc = OI.Loc
order by OrderNo
The issue is with the above SQL is that LocHistQty contains the summary of the Qty for all orders (=11 for Loc 1 and 1 for Loc 2), not only the ones in OrderItemList.
Lastly, the real data is voluminous and query performance is important.
Help would be much appreciated.
The subquery can join with OrderItemList to restrict the order numbers that it sums.
select OI.OrderNo, OI.ItemNo, OI.Loc, OI.Qty, IFNULL(OL.Qty, 0) as HistQty, OL2.LocHistQty
from OrderItemList OI
left join OrderItemLog OL on OL.OrderNo = OI.OrderNo and OL.ItemNo = OI.ItemNo
join
(
select OL.ItemNo, OL.Loc, sum(OL.qty) as LocHistQty
from OrderItemLog AS OL
JOIN OrderItemList AS OI ON OL.OrderNo = OI.OrderNo
group by OL.ItemNo, OL.Loc
) as OL2
on OL2.ItemNo = OI.ItemNo and OL2.Loc = OI.Loc
order by OrderNo
DEMO
Option 1
SELECT
OrderNo,
ItemNo,
Loc,
Qty,
(SELECT
Qty
FROM
OrderHistoryLog AS A
WHERE
A.OrderNo = B.OrderNo AND A.Loc = B.Loc) AS HistQty,
(SELECT
SUM(Qty)
FROM
OrderHistoryLog AS D
WHERE
D.OrderNo = B.OrderNo AND D.Loc = B.Loc) AS LocHistQty
FROM
OrderItemList AS B;
Option 2
SELECT
B.OrderNo,
B.ItemNo,
B.Loc,
B.Qty,
C.Qty AS HistQty,
(SELECT
SUM(Qty)
FROM
OrderHistoryLog AS A
WHERE
A.OrderNo = B.OrderNo AND A.Loc = B.Loc) AS LocHistQty
FROM
OrderItemList AS B,
OrderHistoryLog AS C
WHERE
C.OrderNo = B.OrderNo AND C.Loc = B.Loc;

How to select rows with matching fields in MySQL?

I have 2 tables, one with price overrides and one with minute overrides, each per user and product. How can I select them so that I have a record for each user and product?
Table 1: Override Prices
+--------+-----------+---------------+
| userID | productID | overridePrice |
+--------+-----------+---------------+
| 4 | 53 | 99.99 |
| 4 | 13 | 99.99 |
| 4 | 55 | 99.99 |
+--------+-----------+---------------+
Table 2: Override Minutes
+--------+-----------+---------------+
| userID | productID | overrideMin |
+--------+-----------+---------------+
| 4 | 18 | 23 |
| 4 | 55 | 4 |
| 50 | 55 | 2 |
+--------+-----------+---------------+
The table I want it to produce:
Table 2: All overrides
+--------+-----------+-------------+---------------+
| userID | productID | overrideMin | overridePrice |
+--------+-----------+-------------+---------------+
| 4 | 13 | null | 99.99 |
| 4 | 18 | 23 | null |
| 4 | 53 | null | 99.99 |
| 4 | 55 | 4 | 99.99 |
| 50 | 55 | 2 | null |
+--------+-----------+-------------+---------------+
I've attempted to GROUP BY userID, productID, but because product IDs may exist in table 1 that don't exist in table 2, I get different results depending on which productID I group by.
Use UNION:
SELECT userID, productID,
MAX(overrideMin) AS overrideMin,
MAX(overridePrice) AS overridePrice
FROM
(
SELECT userID, productID, null AS overrideMin, overridePrice
FROM OverridePrices
UNION
SELECT userID, productID, overrideMin, null AS overridePrice
FROM OverrideMinutes
) AS t
GROUP BY userID, productID;
This will give you the exact results you are looking for:
| userID | productID | overrideMin | overridePrice |
|--------|-----------|-------------|---------------|
| 4 | 13 | (null) | 99.99 |
| 4 | 18 | 23 | (null) |
| 4 | 53 | (null) | 99.99 |
| 4 | 55 | 4 | 99.99 |
| 50 | 55 | 2 | (null) |
You have to use IF statement:
SELECT
t1.userID,
t1.productID,
if (t2.overrideMin IS NULL, NULL, t2.overrideMin) AS overrideMin,
if (t1.overridePrice IS NULL, NULL, t1.overridePrice) AS overridePrice
FROM OverridePrices AS t1
LEFT JOIN OverrideMinutes AS t2 ON t1.userID = t2.userID AND t1.productID = t2.productID;
But in case you can have different products in table1 and table2 you have to join to table which contains all products, like:
SELECT
t1.userID,
t1.productID,
if (t2.overrideMin IS NULL, NULL, t2.overrideMin) AS overrideMin,
if (t1.overridePrice IS NULL, NULL, t1.overridePrice) AS overridePrice
FROM (
SELECT userID, productID FROM OverridePrices
UNION
SELECT userID, productID FROM OverrideMinutes
) AS t0
LEFT JOIN OverridePrices AS t1 ON t0.userID = t1.userID AND t0.productID = t1.productID
LEFT JOIN OverrideMinutes AS t2 ON t0.userID = t2.userID AND t0.productID = t2.productID;
Also, now you can have GROUP, HAVING etc.
SELECT table1.userID,table1.productID,table2.overrideMin,table1.overridePrice FROM table1
LEFT JOIN table2 ON table1.userID=table2.userID AND table1.productID = table2.productID
UNION SELECT table2.userID,table2.productID,table2.overrideMin,table1.overridePrice FROM table1
RIGHT JOIN table2 ON table1.userID=table2.userID AND table1.productID = table2.productID
ORDER BY userID, productID
This is the OUTPUT

Selecting multiple MIN columns and other data from same table

Evening. Hope someone can help. Been stumped with this for a few days now...
I have the following table....
| ID | Supplier | Item_1 | Cost_1 | Item_2 | Cost_2 | Item_3 | Cost_3 |
+----+----------+--------+--------+--------+--------+--------+--------+
| 1 | 1 | 732w | 3.99 | 314d | 7.58 | 399p | 15.44 |
| 1 | 2 | SyYh33 | 3.78 | GjuUh4 | 7.60 | 2su7js | 15.45 |
| 1 | 3 | 5443 | 4.01 | 9833 | 7.63 | 7433 | 15.22 |
| 2 | 1 | 596q | 15.42 | 933k | 28.56 | 732c | 69.99 |
| 2 | 2 | hyjs9k | 15.86 | ka7snf | 28.99 | h23nfs | 68.99 |
| 2 | 3 | 5477 | 14.99 | 5658 | 28.49 | 8153 | 70.15 |
+----+----------+--------+--------+--------+--------+--------+--------+
Now what i would like to do is return the cheapest price from columns Cost_1, Cost_2, Cost_3 with it's corresponding Item column and Supplier for each ID....
So basically would like the following result
| ID | Supplier_1 | Item_1 | Cost_1 | Supplier_2 | Item_2 | Cost_2 | Supplier_3 | Item_3 | Cost_3 |
+----+------------+--------+--------+------------+--------+--------+------------+--------+--------+
| 1 | 2 | SyYh33 | 3.78 | 1 | 314d | 7.58 | 3 | 7433 | 15.22 |
| 2 | 3 | 5477 | 14.99 | 3 | 5658 | 28.49 | 2 | h23nfs | 68.99 |
Any pointers would be great. Tried with joins and MIN() but i haven't been able to return the desired results. Hopefully there is a MySQL guru out there that can put me out of my misery. Thanks in advance
Normalize your schema - Otherwise you will have to live with queries like this:
select f.ID,
i1.Supplier as Supplier_1,
i1.Item_1,
i1.Cost_1,
i2.Supplier as Supplier_2,
i2.Item_2,
i2.Cost_2,
i3.Supplier as Supplier_3,
i3.Item_3,
i3.Cost_3
from (select distinct ID from following_table) f
join following_table i1 on i1.ID = f.ID and i1.Supplier = (
select t.Supplier
from following_table t
where t.ID = i1.ID
order by Cost_1 asc
limit 1
)
join following_table i2 on i2.ID = f.ID and i2.Supplier = (
select t.Supplier
from following_table t
where t.ID = i2.ID
order by Cost_2 asc
limit 1
)
join following_table i3 on i3.ID = f.ID and i3.Supplier = (
select t.Supplier
from following_table t
where t.ID = i3.ID
order by Cost_3 asc
limit 1
)
http://sqlfiddle.com/#!9/04933/2
I think what you are looking for is a UNION to combine your results.
SELECT *
FROM your_table
WHERE Cost_1 = (SELECT MIN(Cost_1) FROM your_table)
UNION
SELECT *
FROM your_table
WHERE Cost_2 = (SELECT MIN(Cost_2) FROM your_table)
UNION
SELECT *
FROM your_table
WHERE Cost_3 = (SELECT MIN(Cost_3) FROM your_table)

Combine multiple select statement in one result table

I have two tables, one for sales and another for stock.
I want to select location id, item id, size id and sales qty from sales table, while I want just to select stock qty from stock table for the same location id and size id from sales table, like this:
Sales table:
------------------------------------
| loc_id | item_id | size_id | qty |
------------------------------------
| 5 | 11321 | 1 | 5 |
| 5 | 11321 | 2 | 8 |
| 5 | 11321 | 3 | 4 |
| 5 | 11321 | 2 | 1 |
Stock table:
------------------------------------
| loc_id | item_id | size_id | qty |
------------------------------------
| 5 | 11321 | 1 | 3 |
| 5 | 11321 | 2 | 7 |
| 5 | 11321 | 3 | 9 |
So the result after select should be like this:
------------------------------------------------------
| loc_id | item_id | size_id | sales_qty | stock_qty |
------------------------------------------------------
| 5 | 11321 | 1 | 5 | 3 |
| 5 | 11321 | 2 | 9 | 7 |
| 5 | 11321 | 3 | 4 | 9 |
Here's what I tried to do:
SELECT SUM(T1.qty) AS `salesQty`, SUM(T2.qty) AS `stockQty`, T1.size_id,
T1.loc_id
FROM sales T1
INNER JOIN stock T2 ON T2.item_id = T1.item_id AND T2.size_id = T1.size_id
WHERE T1.item_id = '11321'
AND T1.size_id IN (1,2,3)
AND T1.loc_id IN (5)
GROUP BY T1.size_id, T1.loc_id
But stock qty always wrong!
select
q1.loc_id
,q1.item_id
,q1.size_id
,sum(case when q1.Type='Sales' then q1.Qty else 0 end) as sales_qty
,sum(case when q1.Type='Stock' then q1.Qty else 0 end) as stock_qty
from (
select
T1.loc_id
,T1.item_id
,T1.size_id
,'Sales' as Type
,SUM(T1.qty) AS Qty
from sales T1
group by
T1.loc_id
,T1.item_id
,T1.size_id
union all
select
T2.loc_id
,T2.item_id
,T2.size_id
,'Stock' as Type
,SUM(T2.qty) AS Qty
from stock T2
group by
T2.loc_id
,T2.item_id
,T2.size_id) q1
group by
q1.loc_id
,q1.item_id
,q1.size_id

Mysql, 3 tables 2 group by

I have 3 tables, student, family and fee. Some families have more than one kid. Fees are recorded in fee table. Families makes many payments through the year.
I want to group the number of kids by family and get the total amount paid by each family.
My Qyery:
SELECT student.Family_ID, family.Family_Name, count(*) as kids_numb, sum (Amount) as Paid_Amount
FROM student, family, fee
WHERE student.Family_ID = family.Family_ID
AND fee.Family_ID = family.Family_ID
AND student.Status ='1'
GROUP BY student.Family_ID;
I need to sort Something like:
Family Name | # of kids | Fees | Paid | Balance
----------------------------------------------------
Lebon | 1 | 425 | 200 | 225
Lavoix | 2 | 700 | 150 | 550
Napper | 1 | 425 | 0 | 425
Major | 3 | 900 | 300 | 600
UPDATED based on your comments.
Try it this way
SELECT m.family_id, m.family_name, s.kids_numb, COALESCE(f.paid_amount, 0) paid_amount
FROM family m LEFT JOIN
(
SELECT family_id, COUNT(*) kids_numb
FROM student
WHERE status = 1
GROUP BY family_id
) s
ON m.family_id = s.family_id LEFT JOIN
(
SELECT family_id, SUM(amount) paid_amount
FROM fee
GROUP BY family_id
) f
ON m.family_id = f.family_id
WHERE s.family_id IS NOT NULL
Sample output:
| FAMILY_ID | FAMILY_NAME | KIDS_NUMB | PAID_AMOUNT |
|-----------|-------------|-----------|-------------|
| 1 | Lebon | 1 | 200 |
| 2 | Lavoix | 2 | 150 |
| 3 | Napper | 1 | 0 |
| 4 | Major | 3 | 300 |
Here is SQLFiddle demo
UPDATE2: to add a grand total
SELECT family_id, family_name, SUM(kids_numb) kids_numb, SUM(paid_amount) paid_amount
FROM
(
SELECT m.family_id, m.family_name, s.kids_numb, COALESCE(f.paid_amount, 0) paid_amount
FROM family m LEFT JOIN
(
SELECT family_id, COUNT(*) kids_numb
FROM student
WHERE status = 1
GROUP BY family_id
) s
ON m.family_id = s.family_id LEFT JOIN
(
SELECT family_id, SUM(amount) paid_amount
FROM fee
GROUP BY family_id
) f
ON m.family_id = f.family_id
WHERE s.family_id IS NOT NULL
) q
GROUP BY family_id, family_name WITH ROLLUP
HAVING (family_id IS NOT NULL AND
family_name IS NOT NULL)
OR (family_id IS NULL AND
family_name IS NULL)
Sample output:
| FAMILY_ID | FAMILY_NAME | KIDS_NUMB | PAID_AMOUNT |
|-----------|-------------|-----------|-------------|
| 1 | Lebon | 1 | 200 |
| 2 | Lavoix | 2 | 150 |
| 3 | Napper | 1 | 0 |
| 4 | Major | 3 | 300 |
| (null) | (null) | 7 | 650 |
MySQL GROUP BY non-standard extension will allow you to change this whole part
GROUP BY family_id, family_name WITH ROLLUP
HAVING (family_id IS NOT NULL AND
family_name IS NOT NULL)
OR (family_id IS NULL AND
family_name IS NULL)
with just
GROUP BY family_id WITH ROLLUP
but then instead of NULL in family_name column for a total row you'll have a value of last family name.
Sample output:
| FAMILY_ID | FAMILY_NAME | KIDS_NUMB | PAID_AMOUNT |
|-----------|-------------|-----------|-------------|
| 1 | Lebon | 1 | 200 |
| 2 | Lavoix | 2 | 150 |
| 3 | Napper | 1 | 0 |
| 4 | Major | 3 | 300 |
| (null) | Major | 7 | 650 |
Here is SQLFiddle demo