Invalid Use of a Group Function - mysql

I have been looking for a solution to this seemingly simple problem. The query only breaks when I introduce the last sum() function in the where clause. I get the error "Error Code: 1111. Invalid use of group function." I can't figure out why it won't allow me to make this one where statement.
Query
select
dIntervalStart,
weekofyear(dIntervalStart) WeekOfYear,
weekday(dIntervalStart) DayOfWeek,
cast(dIntervalStart as time) IntervalStart,
sum(nExternToInternAcdCalls) CallVolume
from
iwrkgrpqueuestats
where
SiteId = 1
and cname in ('applications' , 'employer',
'factfind',
'general',
'other')
and cReportGroup = '*'
and CAST(dIntervalStart as time) between '07:00' and '17:30'
and weekday(dIntervalStart) not in (5 , 6)
and sum(nExternToInternAcdCalls) <> 0
group by weekofyear(dIntervalStart) , weekday(dIntervalStart) , cast(dIntervalStart as time)
order by IntervalStart asc, dIntervalStart desc

Relocate the predicate on the aggregate expression to the HAVING clause, e.g.:
GROUP BY ...
HAVING sum(nExternToInternAcdCalls) <> 0
ORDER BY ...
The predicates in the WHERE clause are evaluated when rows are accessed, they pick out which rows are included. The value of the aggregate expression (e.g. SUM(foo)) isn't available when the rows are accessed. The value for that expression can't be determined until after the rows are accessed.
The predicates in the HAVING clause are applied after the rows are accessed, and after the resultset is prepared. When the HAVING predicates are evaluated, the aggregate (SUM(foo)) will be available.
NOTE: the GROUP BY clause would typically include all of the non-aggregate expressions in the SELECT list. MySQL is more relaxed than other databases, which can be both a blessing and a curse; if the GROUP BY "collapses" rows, for non-aggregates in the SELECT list, MySQL returns a value from one of the rows.
That is, if you GROUP BY weekofyear(foo), and return foo in the SELECT list, and there are multiple rows with foo values that evaluate to the same weekofyear(foo) value, MySQL will return one row for the given weekofyear(foo) and also return one of the foo values from one of the rows. Other databases (Oracle, SQL Server, et al.) would throw an error rather than returning a resultset.

You cannot use group by like this, group by is used to distinct result and and specify the element of reference of aggregate functions
Try this :
select
dIntervalStart,
weekofyear(dIntervalStart) WeekOfYear,
weekday(dIntervalStart) DayOfWeek,
cast(dIntervalStart as time) IntervalStart,
sum(nExternToInternAcdCalls) CallVolume
from
iwrkgrpqueuestats
where
SiteId = 1
and cname in ('applications' , 'employer',
'factfind',
'general',
'other')
and cReportGroup = '*'
and CAST(dIntervalStart as time) between '07:00' and '17:30'
and weekday(dIntervalStart) not in (5 , 6)
and sum(nExternToInternAcdCalls) <> 0
group by dIntervalStart,
WeekOfYear,
DayOfWeek,
IntervalStart --columns not using aggregate functions
order by IntervalStart asc, dIntervalStart desc

Related

Mysql: Sort an aggregate ascending with zeros last

I'm attempting to sort an aggregate column, which contains some zero values. I need the zero values to be last.
For non-aggregate columns I can do something like this (simplified example query):
SELECT age FROM books
ORDER BY
age = 0,
age ASC
However, for aggregate columns I'm getting an error as the column doesn't exist:
SELECT avg(age) as avg_age FROM books
GROUP BY book.type
ORDER BY
avg_age = 0,
avg_age ASC
The error is:
SQLSTATE[42S22]: Column not found: 1247 Reference 'avg_age' not supported (reference to group function)
I totally appreciate why this is happening, but I wasn't able to find a workaround, any tips?
There seams to be a (old) related bug report
[21 Mar 2016 9:22] Jiří Kavalík
Description: When using alias to aggregated column in ORDER BY only
plain alias is allowed, using it in any expression returns error.
http://sqlfiddle.com/#!9/e87bb/7
Workarounds:
- select the expression and use its alias
- use a derived table and order the outer one
How to repeat: create table t(a int);
-- these work select sum(a) x from t group by a order by x; select sum(a) x from t group by a order by sum(a); select sum(a) x from t
group by a order by -sum(a);
-- this one wrongly gives "Reference 'x' not supported (reference to group function)" select sum(a) x from t group by a order by -x;
source
You would have to write, this is better as the query is then also ANSI/ISO SQL standard valid meaning the query is most likely better portable between most databases vendor software.
SELECT
avg(books.age) as avg_age
FROM books
GROUP BY books.type
ORDER BY
avg(books.age) = 0
, avg(books.age) ASC
see demo this bug is fixed in MySQL 8.0 see demo
Try repeating the code
SELECT avg(age) as avg_age
FROM books
GROUP BY book.type
ORDER BY avg(age) = 0, avg(age) ASC

Resetting the value of the variable for every new row

I am trying to execute a query which would do the below options for me. I have been given a table(testdata1) say:
YearS CountS($)
2015 360
2016 1000
2017 2000
2018 3500
From this table I have to create another table(testdata2) as listed below:
YearS NewCountS($)
2015 360
2016 640(i.e 1000 - 360)
2017 1000(i.e. 2000 - 1000)
2018 1500(i.e. 3500 - 2000)
There is one more thing which I need to keep in mind is that any number of row can be given to me in the testdata1 therefore what I tried is:
set #diffr := 0;
update testdata1
set cd = (#diffr := CountS - #diffr)
order by yearS;
=================================
insert into testdata2(yearS, NewCountS)
Select yearS, cd
from testdata1;
This query works but generates this output:
YearS NewCountS($)
2015 360
2016 640
2017 1360
2018 2140
I found this link to help but did not understood what the answer explains since I am tyro in mysql.
My query:
How to achieve the desired result?
Is there a better way to get the data from testdata1 table to testdata2 table(i.e. without creating cd column in testdata1)?
Any help will be heartily welcomed...
There's no need to perform an UPDATE of testdata1. Just write a query that gets the rows you need to insert into testdata2.
If we want to use a user-defined variable, test the statement first to verify that it returns the result we expect.
SELECT r.years
, r.counts
FROM (
SELECT s.years
, s.counts - #prev_counts AS `counts`
, #prev_counts := s.counts AS `prev_counts`
FROM ( SELECT #prev_counts := 0 ) i
CROSS
JOIN testdata1 s
ORDER BY s.years
) r
ORDER BY r.years
Note that the inline view i initializes the user-defined variable at the beginning of the statement, essentially equivalent to running a separated SET #prev_counts = 0; statement.
For each row, the value of the user-defined variable is set to the value of counts, so that it will be available when we process the next row. Note that we are expecting MySQL to do this assignment operation after the udv is referenced in the preceding expression.
Note that this behavior with user-defined variables used like this (the order of evaluation) is documented (in the MySQL Reference Manual) to be "undefined". We do observe consistent behavior with carefully constructed SQL, but we should make note that officially this behavior is not guaranteed.
Once the SELECT is tested, we can turn it into an INSERT ... SELECT.
An alternative that doesn't use a user-defined variable, we could use a correlated subquery in an expression on the SELECT list. (gain, write this is a a SELECT statement and test it first, before we turn it into an iNSERT ... SELECT)
SELECT q.years
, q.counts
- IFNULL(
( SELECT r.counts
FROM testdata1 r
WHERE r.years < q.years
ORDER BY r.years DESC
LIMIT 1
)
,0) AS `counts`
FROM testdata1 q
ORDER BY q.years
EDIT to add explanation of the query above
starts out as a simple query like this:
SELECT q.years
, q.counts
FROM testdata1 q
ORDER BY q.years
For each row returned from testdata1, the expressions in the SELECT list are evaluated, and a value is returned. In this case, the two expressions are simple simple column references, we get the value stored in the column.
We could use a more complex expression in the SELECT list, for examples:
SELECT q.years
, q.years - 2000 AS `yy`
, REVERSE(q.years) AS `sraey`
, CONCAT('for ',s.years,' the count is ',s.counts) AS `cs`
, ...
The same thing happens for those expressions in the SELECT list, for every row returned, those expressions are evaluated, and a value is returned.
It's also possible to use a query as an expression, but with some restrictions. The query must return a single column (a single expression), and can return at most one row.
When the query is working on the row years-2017, the expressions in the SELECT list are evaluated.
The specification is that we want to get the value of counts for the preceding year, years=2016. We could get that row by executing a query like this:
SELECT r.years
, r.counts
FROM testdata1 r
WHERE r.years = '2016'
To use a query like this as an expression in the SELECT list, we need to make sure it doesn't return more than one row. We can add LIMIT 1 clause to ensure that it doesn't. And we need to return only one column ...
SELECT r.counts
FROM testdata1 r
WHERE r.years = 2016
LIMIT 1
But that always gets the 2016 row. What we can do now is change that to reference values from the row in the outer query, instead of the literal 2016.
SELECT (
SELECT r.counts
FROM testdata1 r
WHERE r.years = ( q.years - 1 )
LIMIT 1
) AS `prev_years_counts`
, q.counts
, q.years
FROM testdata1 q
ORDER BY q.years
Note that q.years in the subquery is a reference to the row from the outer query. When a row from q is being processed, MySQL executes the subquery, using the value of q.years in the WHERE clause. For each row processed by the outer query, the subquery is executed. And because of that q.years reference in the outer query, we say that its a correlated subquery.
In the event no row is returned (as will be the case for q.years=2015, the subquery returns a NULL value. We wrap that whole subquery in a IFNULL function, so if the subquery returns NULL, we will return a 0.
And the end result is a value. We can write expressions in the SELECT list that do subtraction, e.g.
SELECT q.counts - 540 AS `counts_minus_540`
In place a literal 540, we can use expressions or column references ...
SELECT q.counts - foo AS `counts_minus_foo`
We can use the correlated subquery expression in place of foo, just like we did in that second query in this answer, of the form:
SELECT q.years
, q.counts - IFNULL( crsq ,0) AS `counts`
FROM ...
where crsq is the correlated subquery
SELECT q.years
, q.counts - IFNULL(
SELECT r.counts
FROM testdata1 r
WHERE r.years = ( q.years - 1 )
LIMIT 1
,0) AS `counts`
FROM testdata1 q
ORDER BY q.years
With the given example data, this query is equivalent to the second one in the answer. If there is z gap in the years values (for example, there is no years=2016 row. the query result will be different, because the correlated subquery will return something different for q.years-2017.

Access: Use scalar correlated subquery result for sorting

In Access, is it possible to use the result of a scalar correlated subquery to sort the resultant rows? This didn't work (returned a sql error):
SELECT (SELECT MIN(DateTime) FROM Appt WHERE (PatientID = XXX.ID)) AS minDT, XXX.FullName,
FROM Patient AS XXX
ORDER BY minDT;
Replacing the "minDT" in the ORDER BY clause with the entire expression didn't work either.
Access won't allow you to use a field expression alias in the ORDER BY, but you can use a column's ordinal number ...
SELECT *
FROM
(
SELECT
(
SELECT MIN([DateTime])
FROM Appt
WHERE (PatientID = XXX.ID)
) AS minDT,
XXX.FullName
FROM Patient AS XXX
) AS sub
ORDER BY 1;
Note I bracketed the DateTime field name because I suspect it's a reserved word.
And don't include a comma after the last item in the SELECT field expression list.

MySQL INSERT... SELECT column count and virtual/aliased columns

I'm trying to insert using a select statement. However, I need to order the sub-select results using a ranking equation. If I create an alias, it throws off the column count. Can I somehow order my results using an equation?
INSERT INTO draft
( fk_contrib_id , end_time )
SELECT pk_contrib_id, UNIX_TIMESTAMP(), (X+Y+Z) AS ranking
FROM contrib
ORDER BY ranking DESC
LIMIT 1
I need the 'ranking' column for sorting, but if I do, the column count is off for the insert. Do I have to use two queries for this?
You could simply change your query to directly use the expression in the ORDER BY clause, like so:
INSERT INTO draft
( fk_contrib_id , end_time )
SELECT pk_contrib_id, UNIX_TIMESTAMP()
FROM contrib
ORDER BY (X+Y+Z) DESC
LIMIT 1
Remove the expression from the SELECT list. And use the expression in the ORDER BY clause.
ORDER BY X+Y+Z
It's perfectly valid to ORDER BY expressions that are not in the SELECT list.

MySQL GROUP BY doesn't work when migrated to SQL Server 2012

I'm moving my Delphi application from MySQL to SQL server 2012. In MySQL I had this query:
SELECT *,(XS+S+M+L+XL+XXL+[1Size]+Custom) as Total FROM StockData
GROUP BY StyleNr,Customer,Color
ORDER BY StyleNr,Customer,Color
And it worked perfectly. But in Microsoft SQL Server 2012 this query says
Msg 8120, Level 16, State 1, Line 1
Column 'StockData.ID' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
If I change my query to:
SELECT *,([XS]+[S]+[M]+[L]+[XL]+[XXL]+[1Size]+[Custom]) total
FROM [dbo].[stockdata]
GROUP BY ID,StyleNr,Customer,Color
ORDER BY StyleNr,Customer,Color
Then I get this error:
Msg 8120, Level 16, State 1, Line 1
Column 'dbo.stockdata.XS' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
Any ideas?
Here is the table's design view:
SQL Server is working as expected. You must include all items in your SELECT list in either a GROUP BY or in an aggregate function:
SELECT *,(XS+S+M+L+XL+XXL+[1Size]+Custom) as Total
FROM StockData
-- GROUP BY ID,StyleNr,Customer,Color, XS,S,M,L,XL,XXL,[1Size],Custom
ORDER BY StyleNr,Customer,Color
Or you might be able to use:
SELECT StyleNr,Customer,Color, SUM(XS+S+M+L+XL+XXL+[1Size]+Custom) as Total
FROM StockData
GROUP BY StyleNr,Customer,Color
ORDER BY StyleNr,Customer,Color;
All columns in an aggregate query must either be used by an aggregate function or a group by. Try only selecting the columns you require rather than * I.e. select stylenr, customer, color, ([...] ) as Total from.
This is a SQL standard way of dealing with aggregates, you'd get a similar error in Oracle.
You can also use this approach:
with OrdinalOnGroup
(
SELECT
Ordinal = rank() over(partition by StyleNr, Customer, Color order by id)
, *, (XS+S+M+L+XL+XXL+[1Size]+Custom) as Total
FROM StockData
)
select *
from OrdinalOnGroup
where Ordinal = 1;
PARTITION BY denotes the grouping of related information, this is called windowing