Group by with having clause a key not equal - mysql

I have a table structure and values as:-
used_items
id item_name qty
24 Potatoes 0
25 Potatoes 500
26 test 88
27 Wheat 3
28 abc 10
I want to group according to item_name such as query should return single item_name where qty!=0.
I have used this query-
SELECT * from used_items group by item_name having qty!='0'
This query doesn't return Potatoes, the result-
id item_name qty
26 test 88
27 Wheat 3
28 abc 10
Expected Result-
id item_name qty
25 Potatoes 500
26 test 88
27 Wheat 3
28 abc 10
It should also return the item_name Potatoes with non zero value including in the group.
How can I achieve this?
Edits-
I have used Group by because i want unique item_name and it should skip the item_name if qty is 0 for all same item_name. But the query skips if atleast one of the row qty is 0(such as Potatoes).
Also, if qty is 0, it should get the row which matches the item_name according to the id ascending. Hope i am clear, for eg;
The data:-
id item_name qty
24 Potatoes 0
25 Potatoes 500
27 Potatoes 400
28 test 88
29 Wheat 3
30 abc 10
Expected result, returning unique item_name with first inserted non-zero value
id item_name qty
25 Potatoes 500
28 test 88
29 Wheat 3
30 abc 10

The structure of your query is the problem. When you GROUP BY by you are working with aggregating data and the HAVING clause functions on an aggregate. Your query doesn't do any aggregation at the moment.
So you'd need to aggregate the qty by SUM or AVG or MAX in order to have an effective group by. I also wouldn't think about excluding the id here, because it's not useful in the aggregate.
For example:
SELECT item_name, MIN(qty)
FROM used_items
GROUP BY item_name
HAVING MIN(qty) <> '0'
You can also consider using DISTINCT if you just want to find unique combinations. Again, the id won't be useful with DISTINCT. DISTINCT does not aggregate.
For example:
SELECT DISTINCT item_name, qty
FROM used_items
WHERE qty <> '0';
Having a second look, if all you want to do is exclude all 0 qty rows, all you need is:
SELECT id, item_name, qty
WHERE qty <> '0';
Finally, just check that the data type of qty is actually a VARCHAR or CHAR like type. It really would make a lot more sense if it was an INT.
EDIT:
You can use a WHERE clause with a GROUP BY to exclude qty values that are 0. And since qty is an INT data type, you don't need to put values in quotes '.
Example:
SELECT item_name, MAX(qty)
FROM used_items
WHERE qty <> 0
GROUP BY item_name
I hope this helps.

If you want for each item_name the 1st non 0 (for the quantity) row then you can do it with a correlated subquery:
SELECT t.*
FROM tablename t
WHERE t.id = (SELECT MIN(id) FROM tablename WHERE item_name = t.item_name AND qty <> 0)
See the demo.
Results:
> id | item_name | qty
> -: | :-------- | --:
> 25 | Potatoes | 500
> 28 | test | 88
> 29 | Wheat | 3
> 30 | abc | 10

Why are you using the group by without an aggregate function? If I understood your question, to achieve your expected result you can just..
SELECT DISTINCT(item_name), qty from used_items where qty!='0

SELECT MAX(id) id, item_name, SUM(qty) qty
FROM used_items
GROUP BY item_name
HAVING SUM(qty) > 0
ORDER BY MAX(id);
should do it for you. See this. https://www.db-fiddle.com/f/9hmTPkNkhpjqGrFvUeLzjW/1
MAX(id)'s purpose is to get a valid id column value even from your GROUP BY operation.
You were misusing MySQL's notorious non-standard GROUP BY handling and therefore getting strange results.

Related

configure query to bring rows which have more than 1 entries

How to get those entries which have more than 1 records?
If it doesn't make sense... let me explain:
From the below table I want to access the sum of the commission of all rows where type is joining and "they have more than 1 entry with same downmem_id".
I have this query but it doesn't consider more entries scenario...
$search = "SELECT sum(commission) as income FROM `$database`.`$memcom` where type='joining'";
Here's the table:
id mem_id commission downmem_id type time
2 1 3250 2 joining 2019-09-22 13:24:40
3 45 500 2 egbvegr new time
4 32 20 2 vnsjkdv other time
5 23 2222 2 vfdvfvf some other time
6 43 42 3 joining time
7 32 353 5 joining time
8 54 35 5 vsdvsdd time
Here's the expected result: it should be the sum of the id no 2, 7 only
ie. 3250+353=whatever.
It shouldn't include id no 6 because it has only 1 row with the same downmem_id.
Please help me to make this query.
Another approach is two levels of aggregation:
select sum(t.commission) income
from (select sum(case when type = 'joining' then commission end) as commission
from t
group by downmem_id
having count(*) > 1
) t;
The main advantage to this approach is that this more readily supports more complex conditions on the other members of each group -- such as at most one "joining" record or both "joining" records and no more than two "vnsjkdv" records.
Use EXISTS:
select sum(t.commission) income
from tablename t
where t.type = 'joining'
and exists (
select 1 from tablename
where id <> t.id and downmem_id = t.downmem_id
)
See the demo.
Results:
| income |
| ----- |
| 3603 |
You can use subquery that will find all downmem_id having more than one occurrence in the table.
SELECT Sum(commission) AS income
FROM tablename
WHERE type = 'joining'
AND downmem_id IN (SELECT downmem_id
FROM tablename t
GROUP BY downmem_id
HAVING Count(id) > 1);
DEMO

mysql group by field, ignore duplicates in specific column

I have a table that records product clicks. For example:
productId ipv4 date flag
100 32.59.xx.xx 2015-11-29 1
100 32.59.xx.xx 2015-11-29 1
101 32.59.xx.xx 2015-11-29 1
100 64.34.xx.xx 2015-11-29 1
100 64.34.xx.xx 2015-11-29 1
I need to group by productId but only count one productId-ipv4 pair. I need to ignore any duplicate clicks on a product from the same user.
Expected result:
productId COUNT(*) SUM(flag)
100 2 2
101 1 1
How would I structure the query?
Something like this:
select productid, count(distinct ipv4), count(distinct ipv4, flag)
from foo
group by productId
Check sqlfiddle: http://sqlfiddle.com/#!9/ac487/1

How to get list of product having a specific property id's

I have a table having columns 'product_id' and 'property_id'. Some products having both property 20 & 21, some of them only have 20 or 21.
product_id property_id
1 20
1 21
2 20
3 21
3 20
4 21
I need to get list of product having both property_id 20 and 21. In here I needed the product list of 1 & 3 like below,
product_id
1
3
This should gives you the correct result.
select product_id
from `xy`
where property_id in (20,21)
group by product_id
having count( distinct property_id)=2
The the sqlfiddle
Use IN and COUNT(distinct property_id):
SELECT product_id
FROM TableName
WHERE property_id IN (20,21)
GROUP BY product_id
HAVING COUNT(distinct property_id) = 2
Explanation:
IN checks if property_id has values either 20 or 21.
COUNT (distinct property_id) = 2 verifies having two different values of property_id. So, when product 2 has 2 same values in property_id like (20,20), it will not be selected.
Result:
product_id
----------
1
3
Sample result in SQL Fiddle.
Make use of IN to check multiple values.
Select product_id
from products
where property_id in(20,21)
group by product_id
having count(property_id) >=2
SELECT `product_id` FROM TableName
WHERE `property_id` IN (20,21)
GROUP BY `product_id`
HAVING COUNT(*) = 2

Mysql Agregate function to select maximum and then select minimum price within that group

I am trying to get the maximum value out of a aggregate function, and then also get the min value out of a Price column which comes back in results.
id | discount | price
1 | 60 | 656
2 | 60 | 454
3 | 60 | 222
4 | 30 | 335
5 | 30 | 333
6 | 10 | 232
So in above table, I would like to separate Minimum Price vs Highest Discount.
This is the result I should be seeing:
id | discount | price
3 | 60 | 222
5 | 30 | 333
6 | 10 | 232
As you can see, its taken discount=60 group and separated the lowest price - 222, and the same for all other discount groups.
Could someone give me the SQL for this please, something like this -
SELECT MAX(discount) AS Maxdisc
, MIN(price) as MinPrice
,
FROM mytable
GROUP
BY discount
However, this doesnt separate the minimum price for each group. I think i need to join this table to itself to achieve that. Also, the table contains milions of rows, so the sql needs to be fast. One flat table.
This question is asked and answered with tedious regularity in SO. If only the algorithm was better at spotting duplicates. Anyway...
SELECT x.*
FROM my_table x
JOIN
( SELECT discount,MIN(price) min_price FROM my_table GROUP BY discount) y
ON y.discount = x.discount
AND y.min_price = x.price;
In your query, you cannot group by discount and then maximize the discount value.
This should get you the result you are looking for..
SELECT Max(ID) AS ID, discount, MIN(price) as MinPrice, FROM mytable GROUP BY discount
If you do not need the id, yo would do:
select discount, min(price) as minprice
from table t
group by discount;
If you want other columns in the row, you can either join back to the original table or use the substring_index()/group_concat() trick:
select substring_index(group_concat(id order by price), ',', 1) as id,
discount, min(price)
from table t
group by discount;
This will not always work because the intermediate result for group_concat() can overflow if there are too many matches within a column. This is controlled by a system parameter, which could be made bigger if necessary.

MySql order by date after DATE_FORMAT

I have two tables, fbpost and fbalbum, which each have a DATETIME column called createdTime. I'm finding the number of albums per month, and the number of posts per month, and adding them together.
Here's my query(which works as described):
SELECT createdTime, itemCount FROM
(SELECT DATE_FORMAT(createdTime, '%m-%Y') AS createdTime, COUNT(*) AS itemCount FROM fbpost WHERE page_id =2
GROUP BY YEAR(createdTime), MONTH(createdTime)
UNION ALL
SELECT DATE_FORMAT(createdTime, '%m-%Y') AS createdTime, COUNT(*) AS itemCount FROM fbalbum WHERE page_id=2
GROUP BY YEAR(createdTime), MONTH(createdTime)) AS foo
GROUP BY createdTime
This gives the results:
01-2009 | 173
01-2010 | 21
01-2011 | 521
01-2012 | 776
02-2009 | 117
02-2010 | 158
02-2011 | 678
...
But I would like the results to be ordered like this:
01-2009 | 173
02-2009 | 56
03-2009 | 543
04-2009 | 211
05-2009 | 723
06-2009 | 55
07-2009 | 521
...
How can I achieve this?
Note: DATE_FORMAT() gives a string, not a DATETIME, so you can't sort by date. But, if I take out the DATE_FORMAT() in the two nested select statements, I get 2 rows for most months, since that would leave the day. Though there would be only one row per month for each nested select, they day would usually differ, since the last item in a month may be on any day.
Don't use DATE_FORMAT until your outer query:
SELECT DATE_FORMAT(a.createdTime, '%m-%Y') AS createdTime, SUM(a.itemCount) AS itemCount
FROM
(SELECT DATE(createdTime) AS createdTime, COUNT(*) AS itemCount
FROM fbpost WHERE page_id = 2
GROUP BY DATE(createdTime)
UNION ALL
SELECT DATE(createdTime) AS createdTime, COUNT(*) AS itemCount
FROM fbalbum WHERE page_id = 2
GROUP BY DATE(createdTime)) a
GROUP BY YEAR(a.createdTime), MONTH(a.createdTime)
See it in action *
*Demo does not have page_id