MySQL COUNT query with or without HAVING clause giving same results - mysql

I am having issues with an SQL query which involves a COUNT and a HAVING clause.
The objective is to get the count of all products that have stock and are also not being 'picked' for another customer hence the HAVING clause.
However, when the query is run both with and without the HAVING clause at the end of the query below I get the same COUNT as the result.
To ensure that the COUNT results should not be the same I tried running queries to check if there were products that were out of stock since all products will need to be in stock for the results to match up and confirmed that there were definitely products out of stock.
SELECT
COUNT(DISTINCT p.product_id)
FROM product p
LEFT JOIN product_variant pv
ON pv.product_variant_id = p.product_id
LEFT JOIN depot_product_stock dps
ON dps.product_variant_id = pv.product_variant_id
LEFT JOIN (
SELECT pii.quantity, pii.product_variant_id
FROM `picklist_item` pii
WHERE pii.STATUS IN ('not picked')
) AS pickListNotPicked
ON pickListNotPicked.product_variant_id = pv.product_variant_id
LEFT JOIN (
SELECT pii.quantity, pii.product_variant_id
FROM `picklist_item` pii
LEFT JOIN `packing` packi ON packi.picklist_id = pii.picklist_id
WHERE pii.STATUS IN ('picked')
AND pii.date_picked > NOW() - INTERVAL 2 WEEK
AND packi.picklist_id IS NULL
) AS pickListPicked
ON pickListPicked.product_variant_id = pv.product_variant_id
LEFT JOIN (
SELECT pii.quantity, pii.product_variant_id
FROM `picklist_item` pii
LEFT JOIN `packing` packi ON packi.picklist_id = pii.picklist_id
WHERE packi.`status` IN ('new', 'in progress')
) AS pickListInProgress
ON pickListInProgress.product_variant_id = pv.product_variant_id
WHERE p.deleted = 0
HAVING SUM(dps.physical_stock)
- ifnull(SUM(pickListNotPicked.quantity),0)
- ifnull(SUM(pickListPicked.quantity) ,0)
- ifnull(SUM(pickListInProgress.quantity) ,0)
> 0
I don't know where I'm going wrong with the query. Kindly help. Thanks.

I don't think you want a having clause. Your query is just returning one row. That having clause might have it return zero rows instead of 1 row. And, the having clause is looking over all the products.
Perhaps you want a where clause instead:
WHERE p.deleted = 0 AND
(dps.physical_stock - ifnull(pickListNotPicked.quantity, 0) -
ifnull(pickListPicked.quantity, 0)
ifnull(pickListInProgress.quantity, 0)
) > 0
Note that I prefer replacing ifnull() with coalesce(). (When given the option, I generally prefer the ANSI standard functions.)

I think the issue is that you are mixing up all the quantities to check.
Your query appears to be trying to find a count of products where the physical stock quantity is not equal the the sum of the quantities in various status'.
The trouble is that you are SUMing up all the stock quanties and pick quantities for ALL products. Thus if any single product has a mismatch it will be reflected in the check in the HAVING clause.
I think you need one large sub query to get all the product ids where the quantities do not match, and then do a count based on that.
Something like this (untested).
SELECT COUNT(product_id)
FROM
(
SELECT p.product_id
FROM product p
LEFT JOIN product_variant pv
ON pv.product_variant_id = p.product_id
LEFT JOIN depot_product_stock dps
ON dps.product_variant_id = pv.product_variant_id
LEFT JOIN
(
SELECT pii.quantity, pii.product_variant_id
FROM `picklist_item` pii
WHERE pii.STATUS IN ('not picked')
) AS pickListNotPicked
ON pickListNotPicked.product_variant_id = pv.product_variant_id
LEFT JOIN
(
SELECT pii.quantity, pii.product_variant_id
FROM `picklist_item` pii
LEFT JOIN `packing` packi ON packi.picklist_id = pii.picklist_id
WHERE pii.STATUS IN ('picked')
AND pii.date_picked > NOW() - INTERVAL 2 WEEK
AND packi.picklist_id IS NULL
) AS pickListPicked
ON pickListPicked.product_variant_id = pv.product_variant_id
LEFT JOIN
(
SELECT pii.quantity, pii.product_variant_id
FROM `picklist_item` pii
LEFT JOIN `packing` packi ON packi.picklist_id = pii.picklist_id
WHERE packi.`status` IN ('new', 'in progress')
) AS pickListInProgress
ON pickListInProgress.product_variant_id = pv.product_variant_id
WHERE p.deleted = 0
GROUP BY p.product_id
HAVING SUM(dps.physical_stock)
- IFNULL(SUM(pickListNotPicked.quantity),0)
- IFNULL(SUM(pickListPicked.quantity) ,0)
- IFNULL(SUM(pickListInProgress.quantity) ,0)
> 0
) sub0
You might be able to remove some of the sub queries you already have like this:-
SELECT COUNT(DISTINCT p.product_id)
FROM
(
SELECT p.product_id
FROM product p
LEFT JOIN product_variant pv
ON pv.product_variant_id = p.product_id
LEFT JOIN depot_product_stock dps
ON dps.product_variant_id = pv.product_variant_id
LEFT JOIN
(
SELECT pii.product_variant_id,
SUM(IF(pii.STATUS IN ('not picked')), quantity, 0) AS pickListNotPicked_qty,
SUM(IF(pii.STATUS IN ('picked') AND pii.date_picked > NOW() - INTERVAL 2 WEEK AND packi.picklist_id IS NULL), quantity, 0) AS pickListPicked_qty,
SUM(IF(packi.`status` IN ('new', 'in progress')), quantity, 0) AS pickListInProgress_qty
FROM `picklist_item` pii
LEFT JOIN `packing` packi ON packi.picklist_id = pii.picklist_id
GROUP BY pii.product_variant_id
) AS quantities
ON quantities.product_variant_id = pv.product_variant_id
WHERE p.deleted = 0
GROUP BY p.product_id
HAVING SUM(dps.physical_stock)
- IFNULL(SUM(pickListNotPicked_qty),0)
- IFNULL(SUM(pickListPicked_qty) ,0)
- IFNULL(SUM(pickListInProgress_qty) ,0)
) sub0
This might be further cleaned down to :-
SELECT COUNT(DISTINCT p.product_id)
FROM
(
SELECT p.product_id
FROM product p
LEFT JOIN product_variant pv
ON pv.product_variant_id = p.product_id
LEFT JOIN depot_product_stock dps
ON dps.product_variant_id = pv.product_variant_id
LEFT JOIN
(
SELECT pii.product_variant_id,
SUM(quantities) AS relevant_qty
FROM `picklist_item` pii
LEFT JOIN `packing` packi ON packi.picklist_id = pii.picklist_id
WHERE pii.STATUS = 'not picked'
OR (pii.STATUS = 'picked' AND pii.date_picked > NOW() - INTERVAL 2 WEEK AND packi.picklist_id IS NULL)
OR packi.`status` IN ('new', 'in progress')
GROUP BY pii.product_variant_id
) AS quantities
ON quantities.product_variant_id = pv.product_variant_id
WHERE p.deleted = 0
GROUP BY p.product_id
HAVING SUM(dps.physical_stock)
- IFNULL(SUM(relevant_qty) ,0)
) sub0
Note that I am slightly unsure of your sum logic if I understand your requirement correctly. You could items that are not picked and items picked in the last week. You also count items that have a status of new or in progress, yet it would seem these items could already have been counted as picked / not picked, hence double counted for the sums and ensuring that the final sums did not match and the items were used for the count of product ids.

Related

Is there a method of counting an attribute that is in a GROUP BY clause?

I need have created a select statement to list out all the customers that have been to multiple merchants below.
I want to create another statement to display how many of those customers have been to each merchant.
What is the optimal method of approaching this problem?
Lists out all customers that have been to multiple merchants.
WITH valentinesDayMerchant AS (
SELECT m.MerchantId, m.MerchantGroupId, m.WebsiteName
FROM Merchant m
INNER JOIN OpeningHours oh ON m.MerchantId = oh.MerchantId AND oh.DayOfWeek = 'TUE'
LEFT JOIN devices.DeviceConnectionState AS dcs ON dcs.MerchantId = oh.MerchantId
WHERE MerchantStatus = '-' AND (m.PrinterType IN ('V','O') OR dcs.State = 1 OR dcs.StateTransitionDateTime > '2023-01-23')
)
SELECT DISTINCT ul.UserLoginId, ul.FullName, ul.EmailAddress, ul.Mobile
FROM dbo.UserLogin AS ul
INNER JOIN dbo.Patron AS p ON p.UserLoginId = ul.UserLoginId
INNER JOIN valentinesDayMerchant AS m ON (m.MerchantId = ul.ReferringMerchantId OR m.MerchantId IN (SELECT pml.MerchantId FROM dbo.PatronMerchantLink AS pml WHERE pml.PatronId = p.PatronId AND ISNULL(pml.IsBanned, 0) = 0))
LEFT JOIN (
SELECT mg.MerchantGroupId, mg.MerchantGroupName, groupHost.HostName [GroupHostName]
FROM dbo.MerchantGroup AS mg
INNER JOIN dbo.Merchant AS parent ON parent.MerchantId = mg.ParentMerchantId
INNER JOIN dbo.HttpHostName AS groupHost ON groupHost.MerchantID = parent.MerchantId AND groupHost.Priority = 0
) mGroup ON mGroup.MerchantGroupId = m.MerchantGroupId
LEFT JOIN (
SELECT po.PatronId, MAX(po.OrderDateTime) [LastOrder]
FROM dbo.PatronsOrder AS po
GROUP BY po.PatronId
) orders ON orders.PatronId = p.PatronId
INNER JOIN dbo.HttpHostName AS hhn ON hhn.MerchantID = m.MerchantId AND hhn.Priority = 1
WHERE ul.UserLoginId NOT IN (1,2,100,372) AND ul.UserStatus <> 'D' AND (
ISNULL(orders.LastOrder, '2000-01-01') > '2020-01-01' OR ul.RegistrationDate > '2022-01-01'
)
GROUP BY ul.UserLoginId, ul.FullName, ul.EmailAddress, ul.Mobile
HAVING COUNT(m.MerchantId) > 1
Methods I have tried include adding the merchant name to a group by and displaying the count of the customers, however this does not work as I cannot have anything related to the Merchant in the GROUP BY, or I wouldn't be able to use HAVING clause to identify the customers that have been to multiple merchants. I have also tried selecting all the merchants and counting the distinct customers which doesn't work as it takes into account all the customers, not specifically the customers that have been to multiple merchants only.

sql join and sum 3

I have problem with join and sum column. My query is
SELECT
sum(IFNULL(`worker_hours`.`godziny`, 0)) as godziny,
sum(IFNULL(`worker_hours`.`wartosc`, 0)) as wartosc,
sum(IFNULL(`worker_cashes`.`kwota`, 0)) as kwota,
`workers`.*
FROM
`workers`
LEFT join `worker_hours` on `worker_hours`.`pracownik` = `workers`.`id`
LEFT join `worker_cashes` on `worker_cashes`.`pracownik` = `workers`.`id`
WHERE `workers`.`id_user` = '3'
group by
`workers`.`id`
The result sum *2 my query, what i do wrong? in i have this query is fine:
SELECT
sum(IFNULL(`worker_hours`.`godziny`, 0)) as godziny,
sum(IFNULL(`worker_hours`.`wartosc`, 0)) as wartosc,
`workers`.*
FROM
`workers`
left join `worker_hours` on `worker_hours`.`pracownik` = `workers`.`id`
WHERE `workers`.`id_user` = '3'
group by
`workers`.`id`
the problem is the second left join
You should aggregate first in the tables worker_hours and worker_cashes and then join workers to the resultsets of the aggregations so that you don't get the same row multiple times due to the multiple joins:
SELECT w.*,
COALESCE(h.godziny, 0) AS godziny,
COALESCE(h.wartosc, 0) AS wartosc,
COALESCE(c.kwota, 0) AS kwota
FROM workers AS w
LEFT JOIN (
SELECT pracownik,
SUM(godziny) AS godziny,
SUM(wartosc) AS wartosc
FROM worker_hours
GROUP BY pracownik
) AS h ON h.pracownik = w.id
LEFT JOIN (
SELECT pracownik,
SUM(kwota) AS kwota
FROM worker_cashes
GROUP BY pracownik
) AS c ON c.pracownik = w.id
WHERE w.id_user = '3';

How to fix MySQL query not to return nulls

Here is my query,
SELECT
p.pcode,
p.productName,
s.pcode,
MAX(s.in_stock) AS opening_stock,
SUM(s.soldQty) AS sold_qty,
MIN(s.remaining_qty) AS closing_stock,
s.date_created,
g.pcode,
g.qty,
g.received_qty,
g.received_on
FROM tbl_products p
LEFT JOIN tbl_sold_items s
ON p.pcode = s.pcode
LEFT JOIN tbl_shop_gr_items g
ON p.pcode = g.pcode
WHERE
(s.date_created = '2019-09-27') AND
(g.received_on = '2019-09-27')
GROUP BY p.pcode
stock_report_sample
When the above query is executed it return null values when one of the selected table doesn't have the item id for that particular day. I want it to show a list of all the items(products) available even though the item has not been sold or received so that in those instances it should show zero (0)
Maybe you can use ISNULL to show zero value if NULL
SELECT
p.pcode,
p.productName,
s.pcode,
ISNULL(MAX(s.in_stock),0) AS opening_stock,
ISNULL(SUM(s.soldQty),0) AS sold_qty,
ISNULL(MIN(s.remaining_qty),0) AS closing_stock,
s.date_created,
g.pcode,
g.qty,
g.received_qty,
g.received_on
FROM tbl_products p
LEFT JOIN tbl_sold_items s ON p.pcode = s.pcode
LEFT JOIN tbl_shop_gr_items g ON p.pcode = g.pcode
WHERE(s.date_created = '2019-09-27') AND (g.received_on = '2019-09-27')
GROUP BY p.pcode
SELECT
p.pcode,
p.productName,
CASE
WHEN gr_rows IS NULL AND sl_rows IS NULL THEN 0
WHEN gr_rows > 0 AND sl_rows IS NULL THEN received_stock
WHEN gr_rows > 0 AND sl_rows > 0 THEN opening_stock
ELSE 0
END AS op_stock,
IFNULL(received_stock, 0),IFNULL(sold_stock, 0)
FROM tbl_products p
LEFT JOIN (
SELECT COUNT(r.ID) AS gr_rows,r.pcode,r.received_on,SUM(r.received_qty) AS received_stock
FROM tbl_shop_gr_items r
WHERE r.received_on = '2019-10-03'
GROUP BY r.pcode
)AS r ON p.pcode = r.pcode
LEFT JOIN (
SELECT COUNT(s.ID) AS sl_rows,s.pcode,s.date_created,SUM(s.soldQty) AS sold_stock
FROM tbl_sold_items s
WHERE s.date_created = '2019-10-03'
GROUP BY s.pcode
)AS s ON p.pcode = s.pcode
LEFT JOIN (
SELECT t.ID,t.pcode,t.date_created,t.remaining_qty AS opening_stock
FROM tbl_sold_items t
WHERE t.date_created > '2019-10-03'
GROUP BY t.pcode ORDER BY t.ID
)AS t ON p.pcode = t.pcode
The above code has solved my issue

Issues with duplicated Ids in aggregated subquery

I have this query: (apologies for complexity, I'm not certain what I can remove without impacting the question)
SELECT COUNT(*) AS total,
SUM(o.total) AS total_loss,
SUM((SELECT SUM(cost_price) FROM `orders_items` WHERE orders_id = o.orders_id)) AS cost_total ,
SUM((SELECT COUNT(*) FROM refunds AS r1 WHERE r1.order_id = r.order_id AND NOT r.reason IS NULL)) AS refund_count ,
SUM((SELECT COUNT(*) FROM exchanges AS e1 WHERE e1.order_id = e.order_id AND e.type = :countResend AND NOT e.reason IS NULL)) AS resend_count ,
SUM((SELECT COUNT(*) FROM exchanges AS e2 WHERE e2.order_id = e.order_id AND e.type = :countExchange AND NOT e.reason IS NULL)) AS exchange_count
FROM orders AS o
JOIN sales_channel_config AS s ON o.sales_channel = s.sales_channel AND o.sub_sales_channel = s.sub_sales_channel
JOIN courier_service AS cs ON o.courier_service = cs.code
LEFT JOIN refunds AS r ON o.orders_id = r.order_id
JOIN orders_items AS oi ON o.orders_id = oi.orders_id
JOIN third_party_config AS tc ON SUBSTRING(oi.product_id_new, 3, 2) = tc.code
LEFT JOIN exchanges AS e ON o.orders_id = e.order_id
WHERE 1 = 1
AND o.tracking_num NOT IN (:cancelStatus)
AND (o.order_date >= :startDate AND o.order_date <= :endDate)
AND o.courier_service = :courier
AND SUBSTRING(oi.product_id_new, 3, 2) = :supplier
AND (NOT r.reason IS NULL OR NOT e.reason IS NULL)
The problem I'm having is that the various SUM((query)) clauses are counting duplicate orders, which is proving difficult to resolve. For example:
SUM((SELECT COUNT(DISTINCT r1.order_id) FROM refunds AS r1 WHERE r1.order_id = r.order_id AND NOT r.reason IS NULL)) AS refund_count ,
And
SUM((SELECT COUNT(*) FROM refunds AS r1 WHERE r1.order_id = r.order_id AND NOT r.reason IS NULL GROUP BY r1.order_id)) AS refund_count ,
Do not lower the resulting SUM at all. I have confirmed that the data returned will contain duplicates via another structurally identical query that returns rows from the parent query. When the other query is run without duplicate filtering, the counts match correctly so I'm confident that my problem query is accurate aside from including duplicated order ids.
So can anyone suggest another approach I might try?
For anyone who might benefit:
I removed most of the select logic and grouped on orders_id, which gives me an entirely accurate list of relevant orders:
SELECT o.orders_id AS order_id, r.id AS refund_id, e.id AS exchange_id, e.type AS exchange_type
FROM orders AS o
JOIN sales_channel_config AS s ON o.sales_channel = s.sales_channel AND o.sub_sales_channel = s.sub_sales_channel
JOIN courier_service AS cs ON o.courier_service = cs.code
LEFT JOIN refunds AS r ON o.orders_id = r.order_id
JOIN orders_items AS oi ON o.orders_id = oi.orders_id
JOIN third_party_config AS tc ON SUBSTRING(oi.product_id_new, 3, 2) = tc.code
LEFT JOIN exchanges AS e ON o.orders_id = e.order_id
WHERE 1 = 1
AND o.tracking_num NOT IN (:cancelStatus)
AND (o.order_date >= :startDate
AND o.order_date <= :endDate)
AND o.courier_service = :courier
AND SUBSTRING(oi.product_id_new, 3, 2) = :supplier
AND (NOT r.reason IS NULL OR NOT e.reason IS NULL)
GROUP BY (o.orders_id)
I've bitten the bullet here. I'm going to do some post processing to get the counts themselves, which is at least possible for me now.
Still don't understand why getting distinct values in the sub selects failed though.

How can I make more than one select queries in single select query.

I have to write a query where, I need to fetch records for last week, last month, and for all.
For this problem I wrote 3 diffrent queries (for last week, for last month and for all)
For Weekly Info :-
SELECT bu.brand_name AS 'Brand_Name',COUNT(s.unique) AS '# Item Sold',SUM(s.price) AS 'Total_Price'
FROM item_details s
LEFT JOIN sales_order o ON s.fk_sales_order = o.id_sales_order
LEFT JOIN customer_info AS c ON o.fk_customer_id = c.id_customer
LEFT JOIN simple_details cc ON s.unique = cc.unique
LEFT JOIN config_details cf ON cc.fk_config_id = cf.config_id
LEFT JOIN brand_details cb ON cf.fk_brand_id = cb.brand_id
LEFT JOIN category_details ctc ON cf.fk_category_id = ctc.category_id
LEFT JOIN gender_details g ON cf.fk_gender_id = g.gender_id
LEFT JOIN buyers AS bu ON bu.brand_name = cb.name AND bu.category_name = ctc.name AND bu.gender = g.name
WHERE bu.buyers = 'xyz' AND DATE_FORMAT(o.created_date,'%Y-%m-%d') >= #weekstartdate AND DATE_FORMAT(o.created_date,'%Y-%m-%d') <= #weekenddate
GROUP BY bu.brand_name
For Monthly Info :-
SELECT bu.brand_name AS 'Brand_Name',COUNT(s.unique) AS '# Item Sold',SUM(s.price) AS 'Total_Price'
FROM item_details s
LEFT JOIN sales_order o ON s.fk_sales_order = o.id_sales_order
LEFT JOIN customer_info AS c ON o.fk_customer_id = c.id_customer
LEFT JOIN simple_details cc ON s.unique = cc.unique
LEFT JOIN config_details cf ON cc.fk_config_id = cf.config_id
LEFT JOIN brand_details cb ON cf.fk_brand_id = cb.brand_id
LEFT JOIN category_details ctc ON cf.fk_category_id = ctc.category_id
LEFT JOIN gender_details g ON cf.fk_gender_id = g.gender_id
LEFT JOIN buyers AS bu ON bu.brand_name = cb.name AND bu.category_name = ctc.name AND bu.gender = g.name
WHERE bu.buyers = 'xyz' AND DATE_FORMAT(o.created_date,'%Y-%m-%d') >= #monthstartdate AND DATE_FORMAT(o.created_date,'%Y-%m-%d') <= #monthenddate
GROUP BY bu.brand_name
For All Records :-
SELECT bu.brand_name AS 'Brand_Name',COUNT(s.unique) AS '# Item Sold',SUM(s.price) AS 'Total_Price'
FROM item_details s
LEFT JOIN sales_order o ON s.fk_sales_order = o.id_sales_order
LEFT JOIN customer_info AS c ON o.fk_customer_id = c.id_customer
LEFT JOIN simple_details cc ON s.unique = cc.unique
LEFT JOIN config_details cf ON cc.fk_config_id = cf.config_id
LEFT JOIN brand_details cb ON cf.fk_brand_id = cb.brand_id
LEFT JOIN category_details ctc ON cf.fk_category_id = ctc.category_id
LEFT JOIN gender_details g ON cf.fk_gender_id = g.gender_id
LEFT JOIN buyers AS bu ON bu.brand_name = cb.name AND bu.category_name = ctc.name AND bu.gender = g.name
WHERE bu.buyers = 'xyz'
GROUP BY bu.brand_name
and these are working fine (giving currect output).
But problem is that, I have to merge these three queries in single one.
Where output should be as
Brand name, item_sold(week), total_price(week),item_sold(month), total_price(month),item_sold(all), total_price(all)
How can I write this query?
Without looking deep into your code, the obvious solution would be
SELECT
all.brand_name
pw.items_sold items_sold_week
pw.total_price total_price_week
pm.items_sold items_sold_month
pm.total_price total_price_month
all.items_sold items_sold_all
all.total_price total_price_all
FROM
(your all-time select) all
JOIN (your per-month select) pm ON all.brand_name = pm.brand_name
JOIN (your per-week select) pw ON all.brand_name = pw.brand_name
Though you probably should rethink your entire approach and make sure whether you really want that kind of logic in a DB layer or it is better to be in your application.
You could use case to limit aggregates to a subset of rows:
select bu.brand_name
, count(case when date_format(o.created_date,'%Y-%m-%d') >= #weekstartdate
and date_format(o.created_date,'%Y-%m-%d') <= #weekenddate
then 1 end) as '# Item Sold Week'
, sum(case when date_format(o.created_date,'%Y-%m-%d') >= #weekstartdate
and date_format(o.created_date,'%Y-%m-%d') <= #weekenddate
then s.price end) as 'Total_Price Week'
, count(case when date_format(o.created_date,'%Y-%m-%d') >= #monthstartdate
and date_format(o.created_date,'%Y-%m-%d') <= #monthstartdate
then 1 end) as '# Item Sold Month'
, ...
If all three selects uses the same fields in the results, you can UNION them:
SELECT *
FROM (SELECT 1) AS a
UNION (SELECT 2) AS b
UNION (SELECT 3) AS c
If you need to tell week/mon/all records from each other - just add constant field containing "week" or "mon"
You cam use the UNION.keyword between the queries to bundle them.together BUT tje column types and sequence must be the same in all queries. You could add an identifier to each set