SQL Sum Calculation Confusion - mysql

I am new with mysql and working to change a store application to make it have two stock. I created a table to store stock quantity:
Then I plan to create a view with stock quantity, per store, per SKU. I using the following query:
SELECT
`stockList`.`sku`,
SUM(A.`stockQty`) AS 'store1',
SUM(B.`stockQty`) AS 'store2',
SUM(`stockList`.`stockQty`) AS 'total'
FROM `stockList`
LEFT JOIN (
SELECT * FROM `stockList` WHERE `idStock`=1
) AS A
ON `stockList`.`sku`=A.`sku`
LEFT JOIN (
SELECT * FROM `stockList` WHERE `idStock`=2
) AS B
ON `stockList`.`sku`=B.`sku`
GROUP BY `stockList`.`sku`
Per resulting table, calculation is not proper and I could not identify the logic:
SKU 43 should show for store1 = 9 and for store2 = 10, total = 19. This is what they show if I execute the select queries alone. Please, let me know if I misunderstood how this sum logic works.

You might to use SUM on subquery to calculate Totle price by sku
LEFT JOIN may make some fields not match causing NULL so use IFNULL to preset value 0
You can try this.
SELECT
T.sku,
SUM(T.stockQty) as totle,
IFNULL(A.`store1`,0) AS `store1`,
IFNULL(B.`store2`,0) AS `store2`
FROM `stockList` AS T
LEFT JOIN
(
SELECT sku,SUM(`stockQty`) as `store1`
FROM `stockList`
WHERE `idStock`=1
GROUP BY sku
) as A ON A.sku = T.sku
LEFT JOIN
(
SELECT sku,SUM(`stockQty`) as `store2`
FROM `stockList`
WHERE `idStock`=2
GROUP BY sku
) AS B ON T.sku =B.sku
GROUP BY T.sku
sqlfiddle

Your query is much more complicated than it needs to be. You can just do this:
SELECT
sku,
SUM(stockQty) as total,
SUM(IF(idStock=1,stockQty,0)) AS `store1`,
SUM(IF(idStock=2,stockQty,0)) AS `store2`
FROM `stockList`
GROUP BY sku
Output:
sku total store1 store2
36 10 10 0
37 3 3 0
38 4 4 0
39 3 3 0
40 10 10 0
41 12 12 0
42 12 12 0
43 19 9 10

Related

Using group_concat With a Condition: Display 0 if Values are Not Equal

I just want to display column fields horizontally but also putting a condition to it. Display zero if it has not met the condition.
Example problem: Find the PAYCODE 912 and 686 and display the amount, if not available, display 0
my_table
EMPLOYEE
PAYCODE
AMOUNT
1
912
1
1
123
3
2
912
5
2
686
7
3
111
4
Output must be:
EMPLOYEE
AMOUNT
1
1,0
2
5,7
3
0,0
My code so far:
SELECT
EMPLOYEE,
GROUP_CONCAT(DISTINCT CONCAT(
IF(PAYCODE = '912', AMOUNT, '0'),
IF(PAYCODE = '686', AMOUNT, '0'))
SEPARATOR',') as AMOUNT
FROM
my_table
Note: There are no duplicate paycodes on a similar employee. e.g. two 912 paycodes
I'm thinking a cross join approach should work here:
SELECT e.EMPLOYEE,
GROUP_CONCAT(COALESCE(t.AMOUNT, 0) ORDER BY e.PAYMENTTYPE DESC) AS AMOUNT
FROM (SELECT DISTINCT EMPLOYEE FROM my_table) e
CROSS JOIN (SELECT '912' AS PAYMENTTYPE UNION ALL SELECT '686') p
LEFT JOIN my_table t
ON t.EMPLOYEE = e.EMPLOYEE AND
t.PAYMENTTYPE = p.PAYMENTTYPE
GROUP BY e.EMPLOYEE;
The cross join between the e and p subqueries generates all employee/payment type combinations of interest (only types 912 and 686). We then left join to your table to bring in the amounts, which if are missing we report 0 instead.

MYSQL JOIN and SUM - What am I doing wrong? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I have exhausted all of my options. I cannot understand where I am going wrong with this. Please help me. I can not take much more headbanging. Here is where I am at...
Below is an explanation of my table structure.
locations_management table
contains data linking the locations_management_id, season_id, location_id and other unneeded information together
orders table
contains data about the order including the location_management_id and the order_id as well as other unneeded information
orders_products table
product data linked to the orders by order_id. This table only uses these columns: order_product_id, order_id, product_id, piece_qty
orders_adjustments table
used to track any adjustments to the inv_shipped. This table uses the order_id, product_id, piece_qty columns
Here is where I am at today. The query below pulls data from the tables above.
Basically I am asking for the location_management_id(s) from the locations_management table WHERE season_id = 12 AND location_id = 35. There can be more than one possible location_management_id that fits both the season_id and location_id. I then need to find the orders that match these location_management_id(s). Once the orders are found, I need to use the order_id(s) to find the products associated to them in the orders_products table.
This query does exactly that but when I take it a step further to combine/SUM the
piece_qty for a total inv_shipped, crazy things happen to the numbers.
SELECT
locations_management.season_id,
locations_management.location_id,
orders.order_id,
orders_products.product_id,
IFNULL((orders_products.piece_qty), 0) AS inv_shipped,
IFNULL((orders_adjustments.piece_qty), 0) AS inv_adjustments
FROM
locations_management
JOIN orders USING (location_management_id)
LEFT JOIN orders_products USING (order_id)
LEFT JOIN orders_adjustments ON (orders_adjustments.order_id = orders_products.order_id) AND (orders_adjustments.product_id = orders_products.product_id)
WHERE
locations_management.season_id = 12 AND locations_management.location_id = 35
GROUP BY
product_id, orders_products.order_id
When I run the query above, this is what I get...
season_id location_id order_id product_id inv_shipped inv_adjustments
12 35 2127 1 220 0
12 35 2194 1 160 0
12 35 2127 3 312 0
12 35 2127 4 24 0
12 35 2127 5 180 0
12 35 2194 5 24 0
12 35 2127 7 144 0
12 35 2127 7 24 0
This is exactly what I would expect to get. Multiple order_id's grouped by the product_id and all the data is accurate. So now here becomes the problem. I want to add/SUM the product_id's together when they match and have a combined inv_shipped. So product_id 1 would now total 380 for inv_shipped.
When I take the same query from above and I add SUM to the inv_shipped and inv_adjustments (as seen below), I get this data output below. Notice how some of the values have doubled but also the matching product_id rows are not combined.
IFNULL(SUM(orders_products.piece_qty), 0) AS inv_shipped,
IFNULL(SUM(orders_adjustments.piece_qty), 0) AS inv_adjustments
season_id location_id order_id product_id inv_shipped inv_adjustments
12 35 2127 1 440 0
12 35 2194 1 160 0
12 35 2127 3 624 0
12 35 2127 4 48 0
12 35 2127 5 360 0
12 35 2194 5 24 0
12 35 2127 7 288 0
12 35 2127 7 24 0
If I change the GROUP BY to product_id only, I get the follow data:
GROUP BY product_id
season_id location_id order_id product_id inv_shipped inv_adjustments
12 35 2127 1 600 0
12 35 2127 3 624 0
12 35 2127 4 48 0
12 35 2127 5 384 0
12 35 2127 7 312 0
Again these inv_shipped totals are not correct. So where am I going wrong?
------------------------------------ Suggestions ------------------------------------
This query below was suggested but the data output for the inv_shipped is not added correctly either.
SELECT
locations_management.season_id,
locations_management.location_id,
orders.order_id,
products.product_id,
products.inv_shipped
FROM
locations_management
JOIN (SELECT location_management_id, order_id FROM orders group by order_id) AS orders ON orders.location_management_id = locations_management.location_management_id
JOIN (SELECT order_id, product_id, IFNULL(SUM(piece_qty), 0) AS inv_shipped FROM orders_products GROUP BY order_id, product_id) AS products ON products.order_id = orders.order_id
WHERE
locations_management.season_id = 12 AND locations_management.location_id = 35
ORDER BY
product_id, order_id
season_id location_id order_id product_id inv_shipped inv_adjustments
12 35 2127 1 440 0
12 35 2194 1 160 0
12 35 2127 3 624 0
12 35 2127 4 48 0
12 35 2127 5 360 0
12 35 2194 5 24 0
12 35 2127 7 288 0
12 35 2127 7 24 0
First, find the root of the issue. What changes in between the correct and incorrect information?
Let's take a look at your second query. From what I can see, there are three things that changes:
You've joined with another sub-query.
You've added a SUM operation.
You've added a GROUP BY.
Nest step is to try with removing SUM and GROUP BY, like this:
SELECT
locations_management.season_id AS season_id,
locations_management.location_id AS location_id,
orders.order_id AS order_id,
products.product_id AS product_id,
products.piece_qty
FROM
locations_management
JOIN (SELECT location_management_id, order_id FROM orders group by order_id) AS orders ON orders.location_management_id = locations_management.location_management_id
JOIN (SELECT order_id, product_id, piece_qty FROM orders_products) AS products ON products.order_id = orders.order_id
WHERE
locations_management.season_id = 12 AND locations_management.location_id = 35
I assume that each product_id will return two (or more) rows. That's probably because of your second JOIN have two (or more) rows for order_id in orders_products table; it seems obvious because the first sub-query for table orders have group by order_id. So, now to quickly fix this, you need to do the SUM inside the second sub-query instead. Something like this:
SELECT
locations_management.season_id AS season_id,
locations_management.location_id AS location_id,
orders.order_id AS order_id,
products.product_id AS product_id,
products.inv_shipped
FROM
locations_management
JOIN (SELECT location_management_id, order_id FROM orders group by order_id) AS orders ON orders.location_management_id = locations_management.location_management_id
JOIN (SELECT order_id, product_id, IFNULL(SUM(products.piece_qty), 0) AS inv_shipped FROM orders_products GROUP BY order_id, product_id) AS products ON products.order_id = orders.order_id
WHERE
locations_management.season_id = 12 AND locations_management.location_id = 35;
This might return you the correct result however I personally will write the query like this:
SELECT lm.season_id, lm.location_id, o.order_id , p.product_id, p.inv_shipped
FROM locations_management AS lm
JOIN (SELECT location_management_id, order_id
FROM orders
GROUP BY location_management_id,order_id) AS o
ON o.location_management_id = lm.location_management_id
JOIN (SELECT order_id, product_id, IFNULL(SUM(products.piece_qty), 0) AS inv_shipped
FROM orders_products
GROUP BY order_id, product_id) AS p
ON p.order_id = o.order_id
WHERE
lm.season_id = 12 AND lm.location_id = 35;
You don't need to set alias if your alias is the same as column name; for example lm.season_id AS season_id. If you remove .. AS season_id, the column will be recognized as season_id nonetheless. You won't see it as lm.season_id.. well at least for most tools that I know of. Also, I personally think aliases are meant to shorten long table or column names but "to each their own".
GROUP BY should include all the non-aggregated column(s) in SELECT. Of course, if the sql_mode=only_full_group_by is turned off, you can run the query but the correct setting should be ON. You can read more of the reason why here.
With the additional columns added in GROUP BY, this query might not return the result you once had. That's depending on you data and if that happen, I suggest you edit your question and add a Minimal, reproducible example. At the moment, we're only seeing queries and no example table/data to work with. Its better if you can create a fiddle with a few rows of data.
I don't see the need for those nested subqueries. And if you want one row per order and product, then aggregation may not be necessary.
You seem to want Doesn't this do what you want?
select lm.season_id, lm.location_id,
op.order_id, op.product_id, op.piece_qty as inv_shipped
from locations_management lm
inner join orders o on o.location_management_id = lm.location_management_id
inner join order_products op on op.order_id = o.order_id
where lm.season_id = 12 and lm.location_id = 35
Or if you want one row per product:
select lm.season_id, lm.location_id,
op.product_id, coalesce(sum(op.piece_qty), 0) as inv_shipped
from locations_management lm
inner join orders o on o.location_management_id = lm.location_management_id
left join order_products op on op.order_id = o.order_id
where lm.season_id = 12 and lm.location_id = 35
group by lm.season_id, lm.location_id, op.product_id

get 1st and 2nd highest vlaue rows in case of similar values

I have a table with the columns : id, status, value.
id status value
-- ------ -----
1 10 100
2 10 100
3 10 60
4 11 20
5 11 15
6 12 100
7 12 50
8 12 50
I would like to get the id and value of the first and second highest valued rows, from each status group. My table should have the following columns:
status, id of the first highest value, first highest value, id of second highest value, second highest value.
I should get:
status 1stID 1stValue 2ndID 2ndValue
------ ----- -------- ----- --------
10 1/2 100 2/1 100
11 4 20 5 15
12 6 100 7/8 50
I tried all kinds of solutions, but I couldn't find a solution for same-value 1st s (two rows with the same value, which happened to be the highest in that status group) or same-value seconds.
For example, in case of two rows sharing the highest value in their status group, this not-so-elegant query will return two rows with the same status, different 1sts and same 2nd:
SELECT 2nds.status, 1sts.id AS "1stID",1sts.value AS "1stValue",
2nds.id AS "2ndID",2nds.value AS "2ndValue"
FROM
(SELECT v.* FROM
(SELECT status, MAX(value) AS "SecMaxValue" FROM table o
WHERE value < (SELECT MAX(value) FROM table
WHERE status = o.status
GROUP BY status) AS m
INNER JOIN table v
ON v.status = m.status AND v.value = m.SecMaxValue) AS 2nds
INNER JOIN
(SELECT v.* FROM
(SELECT status, MAX(value) AS maxValue FROM table
GROUP BY status) AS m
INNER JOIN table v
ON v.status = m.status AND v.value = m.MaxValue) AS 1sts
ON 1sts.status = 2nds.status ;
This query will give me:
status 1stID 1stValue 2ndID 2ndValue
------ ----- -------- ----- --------
10 1 100 3 60
10 2 100 3 60
11 4 20 5 15
12 6 100 7 50
12 6 100 8 50
To conclude, I would like to find a solution in which:
a. if there are two rows with the highest value the query puts the details one of them in the column of the 1st and the details of other in 2nd (no mather which)
b. if there are two rows with the second highst value it puts the highest in its place and one of the seconds in the second place.
Is there a way to change the query above? someone has a nicer solution?
I came across several 1st and 2nd queries but they had the same problem - for example this solution: Finding the highest n values of each group in MySQL. it does not deliver 1st and 2nd in the same row, but the main problem it provides only one of the firsts.
Thanks
After spent a lot of time, finally I found a solution for above problem. Please try it out:
select 1st.status as Status,
SUBSTRING_INDEX(1st.id,'/',1) as 1stID,
1st.value as 1stValue,
(case when locate('/',1st.id) > 0 then SUBSTRING_INDEX(1st.id,'/',-1)
else 2nd.id
end) as 2ndID,
(case when locate('/',1st.id) > 0 then 1st.value
else 2nd.value
end) as 2ndValue
from
(
(select status, SUBSTRING_INDEX(Group_concat(id separator '/'),'/',2) as id,value
from t1
where (status,value) in (select status,value
from t1
group by status
having max(value))
group by status) 1st
inner join
(select status,id,value
from t1
where (status,value) not in (select status,value
from t1
group by status
having max(value))
group by status,value
order by status,value desc) 2nd
on 1st.status = 2nd.status)
group by 1st.status;
Just replace t1 with your tablename and it should work like a charm.
Click here for Updated Demo
If you have any doubt(s), feel free to ask.
Hope it helps!

Get product total sales per moth, with 0 in the gaps

I have been stuck in a recent problem with a SQL Query. What I'm trying to archieve is to get each product in the store and show how many of them has been sold each month. However, sometimes there are some months where these products were not sold, which means they won't be displayed.
For instance, this is the result I'm getting right now
Article Month Sold
CN140027 6 312
CN140027 7 293
CN140027 12 122
CN140186 1 10
CN140186 4 2
While I want to get something more like this
Article Month Sold
CN140027 6 312
CN140027 7 293
CN140027 8 0
CN140027 9 0
CN140027 10 0
CN140027 11 0
CN140027 12 122
CN140186 1 10
CN140186 2 0
CN140186 3 0
CN140186 4 2
And here is the query I'm using at the moment
SELECT k.artikelnr, Months.datefield as `Months`, IFNULL(SUM(k.menge),0) as `Quantity`
FROM store_shop_korb as k LEFT OUTER JOIN office_calendar AS Months
ON Months.datefield = month(k.date_insert)
WHERE k.date_insert BETWEEN "2014-12-01" AND "2015-12-31"
group by k.artikelnr, Months.datefield
What am I missing? Or what am I doing wrong? Any help is really appreciated.
Thanks in advance.
EDIT:
Additional information:
office_calendar is the calendar table. It only contains the months as registry, from 1 to 12.
Additionally, I'm taking the article/product ID from a table called 'store_shop_korb', which contains all the lines of a made order (so it contains the article ID, its price, the quantity for each order..)
This works for me:
SELECT k.artikelnr, c.datefield AS `Month`, COALESCE(s.Quantity, 0) AS Sold
FROM (
SELECT artikelnr
FROM store_shop_korb
GROUP BY artikelnr
) k
JOIN office_calendar c
LEFT JOIN (
SELECT artikelnr, MONTH(date_insert) AS monthfield, SUM(menge) AS Quantity
FROM store_shop_korb
GROUP BY artikelnr, MONTH(date_insert)
) s ON k.artikelnr = s.artikelnr AND c.datefield = s.monthfield
ORDER BY k.artikelnr, c.datefield
If you have a table of articles, you can use it in the place of subquery k. I'm basically normalizing on the fly.
Explanation:
There's basically 3 sets of data that get joined. The first is a distinct set of articles (k), the second is a distinct set of months (c). These two are joined without restriction, meaning you get the cartesian product (every article x every month). This result is then left-joined to the sales per month (s) so that we don't lose 0 entries.
Add another where condition , i think it will solve your problem
SELECT k.artikelnr, Months.datefield as `Months`, IFNULL(SUM(k.menge),0) as `Quantity`
FROM store_shop_korb as k LEFT OUTER JOIN office_calendar AS Months
ON Months.datefield = month(k.date_insert)
WHERE IFNULL(SUM(k.menge),0)>0 AND k.date_insert BETWEEN "2014-12-01" AND "2015-12-31"
group by k.artikelnr, Months.datefield
I have tried this in MSAccess and it seems to work OK
SELECT PRODUCT, CALENDAR.MONTH, A
FROM CALENDAR LEFT JOIN (
SELECT PRODUCT, MONTH(SALEDTE) AS M, SUM(SALEAMOUNT) AS A
FROM SALES
WHERE SALEDTE BETWEEN #1/1/2015# AND #12/31/2015#
GROUP BY PRODUCT, MONTH(SALEDTE) ) AS X
ON X.M = CALENDAR.MONTH
If you already have a calender table then use this.
SELECT B.Article,
A.Month,
COALESCE(c.Sold, 0)
FROM (SELECT DISTINCT Months.datefield --Considering this as months feild
FROM office_calendar AS Months) A
CROSS JOIN (SELECT DISTINCT article
FROM Yourtable) B
LEFT OUTER JOIN Yourtable C
ON a.month = c.Month
AND b.Article = c.Article
Else you need a months table. Try this.
SELECT *
FROM (SELECT 1 AS month UNION
SELECT 2 UNION
SELECT 3 UNION
SELECT 4 UNION
SELECT 5 UNION
SELECT 6 UNION
SELECT 7 UNION
SELECT 8 UNION
SELECT 9 UNION
SELECT 10 UNION
SELECT 11 UNION
SELECT 12) A
CROSS JOIN (SELECT DISTINCT article
FROM Yourtable) B
LEFT OUTER JOIN Yourtable C
ON a.month = c.Month
AND b.Article = c.Article

Multiple Select to insert 1 colum with 1 result

SELECT
C.CompanyId,
CompanyName,
Server,
ServerUsers,
ServerUptime,
ServerHostName,
ServerType
FROM
CUSTOMERS AS C
INNER JOIN
USERS ON C.CompanyId = USERS.CompanyId
WHERE
USERS.UserEmail='matt' AND
USERS.UserPin='5153' AND
(SELECT Status FROM 4321_BlackBerryServices LIMIT 0,1)
LIMIT 0, 8
Currently my table is below
4321 T1 Solutions EXCH-01 392 47 days, 17 min exch01.myCorp.com ExchangeServices
4321 T1 Solutions EXCH-02 685 47 days, 17 min exch02.myCorp.com ExchangeServices
4321 T1 Solutions Lync-01 368 47 days, 17 min lync01.myCorp.com LyncServices
4321 T1 Solutions Lync-02 890 458 days, 58 min lync02.myCorp.com LyncServices
What i would like to do is add the last result from Status FROM 4321_BlackBerryServices so i would want to add the latest status for each server in my table
how is this possible ?
If I understand what you need, try this:
SELECT
C.CompanyId, CompanyName, Server, ServerUsers,
ServerUptime, ServerHostName, ServerType,
(SELECT Status FROM 4321_BlackBerryServices
ORDER BY field_you_know DESC LIMIT 0,1) AS BBS_Status
FROM CUSTOMERS AS C
INNER JOIN USERS
ON C.CompanyId = USERS.CompanyId
AND USERS.UserEmail = 'matt'
AND USERS.UserPin = '5153'
LIMIT 0, 8
In my query field_you_know field is the one you know you can sort the table to take the last one.