Show the average value of a group in MySQL - mysql

My MySQL table contains a field that stores the user’s weight and a date field. I am trying to make a query that shows the average weight for a week.
My query so far looks like this:
SELECT Table.`id`,
Table.`weight`,
Table.`date`
FROM Table
WHERE id=%CURRENT_USER_ID%
ORDER BY date ASC
GROUP BY week(date);
This query generates a list where the results are grouped by week.
But I also need the average weight for each week. Right now it only shows the weight for one entry.
How can I make this query?

Be careful. Your SQL is not valid, even though MySQL can be configured to allow it.
This is an issue of functional dependence. The SELECT list terms must be functionally dependent on the GROUP BY terms.
You've selected id, which is not functionally dependent on week(date). In other words, your schema/logic does not guarantee that there is at most one id value per week(date) group. The same is true of trying to select date, which is also not guaranteed to resolve to just one value per week.
Your WHERE clause is also a problem. % can be used as a pattern in a LIKE expression, but not with the = operator, and was not properly quoted as a literal. I'll leave 'CURRENT_USER_ID' to represent your value, assuming that's the correct type. The table definition wasn't shown in the question.
A corrected version of your original query is:
SELECT week(`date`) AS the_week
, MIN(`weight`) AS min_weight
, MIN(`id`) AS min_id
FROM Table
WHERE id='CURRENT_USER_ID'
GROUP BY week(date)
ORDER BY the_week ASC
;
Note: The above uses the aggregate function MIN in the SELECT list. These expressions are functionally dependent on the GROUP BY terms. The AVG function can also be used, like this:
SELECT week(`date`) AS the_week
, AVG(`weight`) AS avg_weight
FROM Table
WHERE id='CURRENT_USER_ID'
GROUP BY week(date)
ORDER BY the_week ASC
;
and in MySQL, we can use the derived column name / alias in the GROUP BY terms.
SELECT week(`date`) AS the_week
, AVG(`weight`) AS avg_weight
FROM Table
WHERE id='CURRENT_USER_ID'
GROUP BY the_week
ORDER BY the_week ASC
;
That's a start.

You can make a little changing and get your desired output.
use this
SELECT Table.`id`,
Table.`weight`,
Table.`date`,
Avg('Table.weight')'Average Weight'
FROM Table
WHERE id=%CURRENT_USER_ID%
ORDER BY date ASC
GROUP BY week(date);

Related

How to get one output for avg function? [duplicate]

My MySQL table contains a field that stores the user’s weight and a date field. I am trying to make a query that shows the average weight for a week.
My query so far looks like this:
SELECT Table.`id`,
Table.`weight`,
Table.`date`
FROM Table
WHERE id=%CURRENT_USER_ID%
ORDER BY date ASC
GROUP BY week(date);
This query generates a list where the results are grouped by week.
But I also need the average weight for each week. Right now it only shows the weight for one entry.
How can I make this query?
Be careful. Your SQL is not valid, even though MySQL can be configured to allow it.
This is an issue of functional dependence. The SELECT list terms must be functionally dependent on the GROUP BY terms.
You've selected id, which is not functionally dependent on week(date). In other words, your schema/logic does not guarantee that there is at most one id value per week(date) group. The same is true of trying to select date, which is also not guaranteed to resolve to just one value per week.
Your WHERE clause is also a problem. % can be used as a pattern in a LIKE expression, but not with the = operator, and was not properly quoted as a literal. I'll leave 'CURRENT_USER_ID' to represent your value, assuming that's the correct type. The table definition wasn't shown in the question.
A corrected version of your original query is:
SELECT week(`date`) AS the_week
, MIN(`weight`) AS min_weight
, MIN(`id`) AS min_id
FROM Table
WHERE id='CURRENT_USER_ID'
GROUP BY week(date)
ORDER BY the_week ASC
;
Note: The above uses the aggregate function MIN in the SELECT list. These expressions are functionally dependent on the GROUP BY terms. The AVG function can also be used, like this:
SELECT week(`date`) AS the_week
, AVG(`weight`) AS avg_weight
FROM Table
WHERE id='CURRENT_USER_ID'
GROUP BY week(date)
ORDER BY the_week ASC
;
and in MySQL, we can use the derived column name / alias in the GROUP BY terms.
SELECT week(`date`) AS the_week
, AVG(`weight`) AS avg_weight
FROM Table
WHERE id='CURRENT_USER_ID'
GROUP BY the_week
ORDER BY the_week ASC
;
That's a start.
You can make a little changing and get your desired output.
use this
SELECT Table.`id`,
Table.`weight`,
Table.`date`,
Avg('Table.weight')'Average Weight'
FROM Table
WHERE id=%CURRENT_USER_ID%
ORDER BY date ASC
GROUP BY week(date);

Get values from first sorted member of grouped(?) sql query

I feel like this is obvious but i'm struggling. Must be because it's a monday.
I have a licenses table in MySQL which has fields id (int), start_date (date), licensable_id (int), licensable_type (string) and fixed_end_point (boolean).
I want to get all licenses where the start date is equal to or less than today, group them by licensable_id and licensable_type, and then get the most recently starting one so I can get the fixed_end_point field out of it, along with licensable_id and licensable_type.
This is what i'm trying:
SELECT licensable_id, licensable_type, fixed_end_point
FROM licenses
WHERE start_date <= "2016-08-01"
GROUP BY licensable_id, licensable_type
ORDER BY start_date desc;
At the moment, the ORDER BY field seems to be being ignored, and it's just returning the values from the first license for each group, rather than the most recent. Can anyone see what I'm doing wrong? Do I need to make a nested query?
You shouldn't be thinking about this as a group by. You want to select the most recent start_date for each license, given the constraints in the question. One method uses a correlated subquery:
select l.*
from licenses l
where l.start_date = (select max(l2.start_date)
from licenses l2
where l2.licensable_id = l.licensable_id and
l2.licensable_type = l.licensable_type and
l2.start_date <= '2016-08-01'
);
You don't use aggregation function so you should use distinct
SELECT DISTINCT licensable_id, licensable_type, fixed_end_point
FROM licenses
WHERE date(start_date) <= date(now())
ORDER BY start_date desc
limit 1;
The reason this doesn't give you the results you want is how GROUP CONCAT works.
With standard SQL any field in the SELECT must either be also mentioned in the GROUP BY clause or must be an aggregate field (there is an exception for fields 100% related to a field that is returned, but many flavours of SQL do not support this).
MySQL does allow a field to be in the SELECT clause which is not an aggregate value and is not mentioned in the GROUP BY clause, and allowing this was the default until recently. However for these fields there could be multiple values for the GROUP BY fields, and in this case which one is chosen is not defined. As this is worked out prior to the ORDER BY statement being processed, the ORDER BY clause has no effect on which one is chosen.
There are a few normal ways to do this. You can use a as Gordon has suggested, or similarly (and possibly more efficiently depending on records and indexes) you can use a sub query to get the latest rows date for each of your important rows, and then join that back to your main table:-
SELECT l.licensable_id,
l.licensable_type,
l.fixed_end_point
FROM licenses l
INNER JOIN
(
SELECT licensable_id,
licensable_type,
MAX(l2.start_date) AS max_start_date
FROM licenses
GROUP BY licensable_id,
licensable_type
) sub0
ON l.licensable_id = sub0.licensable_id
AND l.licensable_type = sub0.licensable_type
AND l.start_date = sub0.max_start_date
In some situations another option is to (ab)use the GROUP_CONCAT and SUBSTRING_INDEX functions. This way you can GROUP BY the fields you want to, but do a GROUP_CONCAT or the other fields in the descending order of the date. Then use SUBSTRING_INDEX to get everything up to the first comma (the default delimiter for GROUP_CONCAT):-
SELECT licensable_id,
licensable_type,
SUBSTRING_INDEX(GROUP_CONCAT(COALESCE(fixed_end_point, '') ORDER BY start_date DESC), ',', 1)
FROM licenses
WHERE start_date <= "2016-08-01"
GROUP BY licensable_id, licensable_type
Obviously this has issues if the latest row has a null value, hence I have used COALESCE to fudge in non null values. Also if the field contains commas you will need to use an alternative delimiter. And if the field is large then you might have issues with the max field length for GROUP_CONCAT (default is 1024 I think).

Filter rows in a query using HAVING in MySQL

HAVING is usually used with GROUP BY, but in my query I need it so that I can filter a derived column
sample query:
SELECT
id,
NOW() < expiration_date
OR expiration_date IS NULL AS is_active
FROM
products
HAVING is_active = 1 ;
I could also use a temp table and just use WHERE instead of HAVING,
example:
SELECT id
FROM
(SELECT
id,
NOW() < expiration_date
OR expiration_date IS NULL AS is_active
FROM
products)
WHERE is_active = 1 ;
Either way, I'm getting the desired results but is it really appropriate to use HAVING even if you have no GROUP BY and just for filtering derived rows. Which one is better?
The second query is better.
BTW, as you limit your results to the expression you can even shorten it to:
SELECT
id,
1 AS is_active
FROM
products
WHERE NOW() < expiration_date OR expiration_date IS NULL;
Your first query is not good. Mainly because it's not standard SQL and may thus confuse its reader. The query is not valid in most other dbms. HAVING is for aggregated records.
The typical thing is to aggregate and GROUP BY and then filter the results with HAVING. Omitting GROUP BY would usually give you one record (as in select max(col) from mytable). HAVING would in this case filter the one result record, so you get that one or none. Example: select max(col) as maxc from mytable having maxc > 100).
In MySQL you are allowed to omit GROUP BY expressions. For instance select id, name from mytable group by id would give you the id plus a name matching that ID (and as there is usually one record per ID, you get that one name). In another dbms you would have to use an aggregate function on name, such as MIN or MAX, or have name in the GROUP BY clause. In MySQL you don't have to. Omitting it means: get one of the values in the (group's) records found.
So your first query looks a bit like: Aggregate my data (because you are using HAVING) to one record (as there is no GROUP BY clause), so you'd get one record with a random id. This is obviously not what the query does, but to tell the truth I wouldn't have been able to tell just from the look at it. I am no MySQL guy, so I would have had to try to know how it is working, had you not told us it is working as expected by you.

Conditional Distinct in MYSQL with respect o another column

I have query as follow
SELECT * FROM content_type_product cp
JOIN content_field_product_manufacturer cf ON cf.nid = cp.nid group by cp.nid
ORDER
BY field(cf.field_product_manufacturer_value,'12') DESC,
cp.field_product_price_value DESC
This is working perfect just a small flaw, there are two records having the same id (one is for cf.field_product_manufacturer_value='12' and other is for cf.field_product_manufacturer_value = '57') which I eliminated using group by clause. But the problem is that I want to get that particular id which has greater "field_product_price_value" but somehow it gives me the value which is lesser. If I query it for '57' then it gives me the id with greater field_product_price_value but when I query it for '12' it gives me id for lesser "field_product_price_value". Is there any way where I can specify to pick the id with greater "field_product_price_value"
You should use max(field_product_price_value) combined with appropriate GROUP BY-clause.
In general, you should use GROUP BY-clause only when you select both normal columns and aggregate functions (MIN, MAX, COUNT, AVG) in the query.
You query is using a (mis)feature of MySQL called Hidden Columns. This is only advised when all the unaggregated columns in the SELECT and not in the GROUP BY have the same value. This is not the case, so you need to select the correct records yourself:
SELECT cp.*, cf.*
FROM content_type_product cp JOIN
content_field_product_manufacturer cf
ON cf.nid = cp.nid join
(select cf.nid, max(field_product_price_value) as maxprice
from content_field_product_manufacturer
group by cf.nid
) cfmax
on cf.nid = cfmax.nid and cf.field_product_price_value = cfmax.maxprice
ORDER BY field(cf.field_product_manufacturer_value,'12') DESC,
cp.field_product_price_value DESC
Unless you really know what you are doing, when you use a GROUP BY, be sure all unaggregated columns in the SELECT are in the GROUP BY.
'2' > '12'
if we are talking about varchars. I believe you should convert your field to number type and your sort will work fine. Read this article for more information.

mysql ORDER BY MIN() not matching up with id

I have a database that has the following columns:
-------------------
id|domain|hit_count
-------------------
And I would like to perform this query on it:
SELECT id,MIN(hit_count)
FROM table WHERE domain='$domain'
GROUP BY domain ORDER BY MIN(hit_count)
I would like this query to give me the id of the row that had the smallest hit_count for $domain. The only problem is that if I have two rows that have the same domain, say www.bestbuy.com, the query will just group by whichever one came first, and then although I will get the correct lowest hit_count, the id may or may not be the id of the row that has the lowest hit_count.
Does anyone know of a way for me to perform this query and to get the id that matches up with MIN(hit_count)? Thanks!
Try this:
SELECT id,MIN(hit_count),domain FROM table GROUP BY domain HAVING domain='$domain'
See, when you're using aggregates, either via aggregate functions (and min() is such a function) or via GROUP BY or HAVING operators, your data is being grouped. In your case it is grouped by domain. You have 2 fields in your select list, id and min(hit_count).
Now, for each group database knows which hit_count to pick, as you've specified this explicitly via the aggregate function. But what about id — which one should be included?
MySQL internally wraps such fields into max() aggregate function, which I find an error prone approach. In all other RDBMSes you will get an error for such a query.
The rule is: if you use aggregates, then all columns should be either arguments of aggregate functions or arguments of GROUP BY operator.
To achieve the desired result, you need a subquery:
SELECT id, domain, hit_count
FROM `table`
WHERE domain = '$domain'
AND hit_count = (SELECT min(hit_count) FROM `table` WHERE domain = '$domain');
I've used backticks, as table is a reserved word in SQL.
SELECT
id,
hit_count
FROM
table
WHERE
domain='$domain'
AND hit_count = (SELECT MIN(hit_count) FROM table WHERE domain='$domain')
Try this:
SELECT id,hit_count
FROM table WHERE domain='$domain'
GROUP BY domain ORDER BY hit_count ASC;
This should also work:
select id, MIN(hit_count) from table where domain="$domain";
I had same question. Please see that question below.
min(column) is not returning me correct data of other columns
You are using a GROPU BY. Which means each row in result represents a group of values.
One of those values is the group name (the value of the field you grouped by). The rest are arbitrary values from within that group.
For example the following table:
F1 | F2
1 aa
1 bb
1 cc
2 gg
2 hh
If u will group by F1: SELECT F1,F2 from T GROUP BY F1
You will get two rows:
1 and one value from (aa,bb,cc)
2 and one value from (gg,hh)
If u want a deterministic result set, you need to tell the software what algorithem to apply to the group. Several for example:
MIN
MAX
COUNT
SUM
etc etc
There is a most simplist way your query is OK just modify it with DESC keyword after GROUP BY domain
SELECT
id,
MIN(hit_count)
FROM table
WHERE domain = '$domain'
GROUP BY domain DESC
ORDER BY MIN(hit_count)
Explanation:
When you use group by with aggregate function it always selects the first record but if you restrict it with desc keyword it will select the lowest or last record of that group.
For testing puspose use this query that has only group_concat added.
SELECT
group_concat(id),
MIN(hit_count)
FROM table
WHERE domain = '$domain'
GROUP BY domain DESC
ORDER BY MIN(hit_count)
If you can have duplicated domains group by id:
SELECT id,MIN(hit_count)
FROM domain WHERE domain='$domain'
GROUP BY id ORDER BY MIN(hit_count)