SUM inside SUM SQL Invalid use of group function - mysql

Hi I want to perform a calculation inside a SUM with my sql, but there is one SUM field that consist of other SUM fields. I get the General error: 1111 Invalid use of group function. What is the proper way of summing other sum fields in SQL?
I can't use the alias of other sum fields to perform the calculation because it says that the alias is unidentified.
This part is my problem
SUM((SUM(transactions.payable) + SUM(transactions.discount) ) - SUM(deliveries.delivery_fee) ) AS raw_sales
Thank you
Here is my SQL.
SELECT
MONTHNAME(transactions.date_transac) AS MONTH,
SUM(transactions.payable) AS total,
SUM(transactions.discount) AS discount,
SUM(deliveries.delivery_fee) AS delivery,
SUM(
(
SUM(transactions.payable) + SUM(transactions.discount)
) - SUM(deliveries.delivery_fee)
) AS raw_sales,
MONTH(transactions.date_transac) AS monthnum
FROM
`transactions`
LEFT JOIN `requisitions` ON `transactions`.`requisition_id` = `requisitions`.`id`
LEFT JOIN `transactions` AS `ct`
ON
`transactions`.`code` = `ct`.`charge_transaction_code`
LEFT JOIN `deliveries` ON `transactions`.`delivery_id` = `deliveries`.`id`
WHERE
`transactions`.`transaction_type` = Sale AND YEAR(`transactions`.`date_transac`) = 2020
GROUP BY
`month`
ORDER BY
`monthnum` ASC
enter image description here

You can't nest aggregate functions. Here, I suspect that you could move the arithmetics within the aggregate function rather than attempting to nest:
SUM(
transactions.payable
+ transactions.discount
- COALESCE(deliveries.delivery_fee, 0)
) AS raw_sales
delivery_fee comes from a left join table so it could be null, hence we use coalesce().
That said, I am quite suspicious about the logic of your query. I am wondering, for example, why transactions appears twice in the from clause. There are also missing quotes around literal string "Sale" in the WHERE clause. If you were to ask a legitimate question, including sample data, desired results, and an explanation of the purpose of the query, one might be able to suggests optimizations.

The query just worked, I haven't realized that it is no longer necessary to calculate all Sum fields. I just removed the external sum.

Related

Using a COUNT value in an expression getting..does not include specified expression as part of an aggregate function

I am trying to display a warning if a bike station gets to over 90% full or less than 10% full. When i run this query I get "you are trying to execute query that does not include the iif statment... as part of an aggregate function.
Bike_locations table - Bicycle_id and Locations_ID
Locations table - Locations_ID, No_of_Spaces, Location_Address
SELECT Locations.Location_Address, Count(Bike_Locations.Bicycle_ID) AS CountOfBicycle_ID,
IIf(((([CountOfBicycle_ID]/[LOCATIONS]![No_Of_Spaces])*100)>90),"This Station is nearly full.
Need to move some bicycles out of here",IIf(((([CountOfBicycle_ID]/[LOCATIONS]![No_Of_Spaces])*100)
<10),"This station is nearly empty. Need to move some bicycles here","")) AS Warnings
FROM Locations INNER JOIN Bike_Locations ON Locations.[LOCATIONS_ID] = Bike_Locations.[LOCATIONS_ID]
GROUP BY Locations.Location_Address;
Anyone got a scooby
When you use a GROUP BY, you should have the exact same fields in both your SELECT and GROUP BY statements, except for the aggregate function that should only be specified in the SELECT
The aggregate function in your case is the COUNT(*)
The fields you aggregate on are:
in the SELECT : Location_Address and Warnings
in the GROUP BY : Location_Address only
The error message is telling you that you don't have the same in both statements.
2 solutions:
Remove the Warnings from the SELECT statement
Add the Warnings to the GROUP BY statement
Note that in MS Access SQL, you can't (unfortunately) use in the GROUP BY, the Aliases specified in the SELECT. So you have to copy over the whole field, which would be the long iif in your case
Edit: better solution proposal:
I would radically change your approach as you'll go no where with all those nested iff
Create the following Query and Name it (for instance) Stations_Occupation
SELECT L.Locations_ID AS ID,
L.Location_Address AS Addr,
L.No_of_Spaces AS TotSpace,
BL.cnt AS OccSpace,
ROUND((BL.cnt/L.No_of_Spaces*100),0) AS OccPourc
FROM Locations L
LEFT JOIN
(
SELECT Locations_ID, COUNT(*) AS cnt
FROM Bike_Locations
GROUP BY LOCATIONS_ID
) AS BL ON L.Locations_ID = BL.Locations_ID
This query will probably be a lot helpfull in many parts of your application, and not only here, as it calculates the occupation % of each station
Some examples:
Get all stations with >90% occupation:
SELECT Addr
FROM Stations_Occupation
WHERE OccPourc > 90
Get all stations with <10% occupation:
SELECT Addr
FROM Stations_Occupation
WHERE OccPourc < 10
Get Occupation level of a specific station:
SELECT OccPourc
FROM Stations_Occupation
WHERE ID=specific_station_ID
Get number of bikes and max on a specific station:
SELECT OccSpace & "/" & TotSpace
FROM Stations_Occupation
WHERE ID=specific_station_ID

MySQL Select within another select

I have a query as follows
select
Sum(If(departments.vat, If(weeklytransactions.weekendingdate Between
'2011-01-04' And '2099-12-31', weeklytransactions.takings / 1.2,
If(weeklytransactions.weekendingdate Between '2008-11-30' And '2010-01-01',
weeklytransactions.takings / 1.15, weeklytransactions.takings / 1.175)),
weeklytransactions.takings)) As Total,
weeklytransactions.weekendingdate,......
and another that returns a vat rate as follows
select format(Max(Distinct vat_rates.Vat_Rate),3) From vat_rates Where
vat_rates.Vat_From <= '2011-01-03'
I want to replace the hard coded if statement with the lower query, replacing the date in the lower query with weeklytransactions.weekendingdate.
After Kevin's comments, here is the full query I'm trying to get to work;
Select Max(vat_rates.vat_rate) As r,
If(departments.vat, weeklytransactions.takings / r, weeklytransactions.takings) As Total,
weeklytransactions.weekendingdate,
Week(weeklytransactions.weekendingdate),
round(datediff(weekendingdate, (if(month(weekendingdate)>5,concat(year(weekendingdate),'-06-01'),concat(year(weekendingdate)-1,'-06-01'))))/7,0)+1 as fyweek,
cast((Case When Month(weeklytransactions.weekendingdate) >5 Then Concat(Year(weeklytransactions.weekendingdate), '-',Year(weeklytransactions.weekendingdate) + 1) Else Concat(Year(weeklytransactions.weekendingdate) - 1, '-',Year(weeklytransactions.weekendingdate)) End) as char) As fy,
business_units.business_unit
From departments Inner Join (business_units Inner Join weeklytransactions On business_units.buID = weeklytransactions.businessUnit) On departments.deptid = weeklytransactions.departmentId
Where (vat_rates.vat_from <= weeklytransactions.weekendingdate and business_units.Active = true and business_units.sales=1)
Group By weeklytransactions.weekendingdate, business_units.business_unit Order By fy desc, business_unit, fyweek
Regards
Pete
Assuming I read your question correctly, your problem is about having the result of another SELECT used to be returned by the result of your main query (plus depending on how acquainted you are with SQL, maybe you haven't had the occasion to learn about JOINs?).
You can have subqueries you extract data from within a SELECT, provided you define it within the FROMclause. The following query will work, for example:
SELECT A.a, B.b
FROM A
JOIN (SELECT aggregate(c) FROM C) AS B
Notice that there is no reference to table A within the subquery. Thing is, you cannot just add it like that to the query, as the subquery doesn't know it is a subquery. So the following won't work:
SELECT A.a, B.b
FROM A
JOIN (SELECT aggregate(c) FROM C WHERE C.someValue = A.someValue) AS B
Back to basics. What you want to do here visibly, is to aggregate some data associated to each of the records of another table. For that, you will need merge your SELECT queries and use GROUP BY:
SELECT A.a, aggregate(C.c)
FROM A, C
WHERE C.someValue = A.someValue
GROUP BY A.a
Back to your tables, the following should work:
SELECT w.weekendingdate, FORMAT(MAX(v.Vat_Rate, 3)
FROM weeklytransactions AS w, vat_rates AS v
WHERE v.Vat_From <= w.weekendingdate
GROUP BY w.weekendingdate
Feel free to add and remove fields and conditions as you see fit (I wouldn't be surprised that you'd also want to use a lower bound when filtering the records from vat_rates, since the way I have written it above, for a given weekendingdate, you get records from that week + the weeks before!).
So it looks like my first try did not address the actual problem. With the additional information provided in the comments, as well as the new complete query, let's see how this goes.
We are still missing error messages, but normally the query as written should result in MySQL having the following complaint:
ERROR 1109 (42S02): Unknown table 'vat_rates' in field list
Why? Because the vat_rates table does not appear in the FROM clause, whereas it should. Let's make that more obvious by simplifying the query, removing all references to the business_units table as well as the fields, calculations and order that do not add or remove anything to the problem, leaving us with the following:
SELECT MAX(vat_rates.vat_rate) AS r,
IF(d.vat, w.takings / r, w.takings) AS Total
FROM departments AS d
INNER JOIN weeklytransactions AS w ON w.departmentId = d.deptid
WHERE vat_rates.vat_from <= w.weekendingdate
GROUP BY w.weekendingdate
That cannot work, and will produce the error mentioned above. It looks like there is no FOREIGN ID between the weeklytransactions and vat_rates tables, so we have no choice but to do a CROSS JOIN for the moment, hoping that the condition in the WHERE clause and the aggregate function used to get r are enough to fit the business logic at hand here. The following query should return the expected data instead of an error message (I also remove r since that seems to be an intermediate value judging by the comments that were written):
SELECT IF(d.vat, w.takings / MAX(v.vat_rate), w.takings) AS Total
FROM vat_rates AS v, departments AS d
INNER JOIN weeklytransactions AS w ON w.departmentId = d.deptid
WHERE v.vat_from <= w.weekendingdate
GROUP BY w.weekendingdate
From there, assuming it works, you will only need to put back all the parts I removed to get your final query. I am a tad doubtful about the way the VAT rate is gotten here, but I have no idea what your requirements are in that regard so I leave it up to you to make sure that works as expected.

Complex group by function for normalizing data

I have a table which has dstipv4 and pkts fields. I want to calculate STDEV of normalized pkts for each dstipv4. I write a query like this it but does not work — generating the error:
#1111 - Invalid use of group function
How to write a correct query?
SELECT dstipv4, STDEV((pkts-min(pkts))/(max(pkts)-min(pkts))) FROM flow
group by dstipv4
I'm not sure how best to explain it, but your calculation as shown requires prescience on the part of the DBMS, and the error message is the DBMS telling you that it doesn't have the requisite foresight.
I think you need to calculate the MAX and MIN values in a sub-query:
SELECT f.dstipv4, STDEV((f.pkts - m.min_pkts) / (m.max_pkts - m.min_pkts))
FROM flow AS f
JOIN (SELECT dstipv4, MIN(pkts) AS min_pkts, MAX(pkts) AS max_pkts
FROM flow
GROUP BY dstipv4
) AS m
ON f.dstipv4 = m.dstipv4
GROUP BY f.dstipv4

mysql reference not supported to group function

I have a MySQL procedure that takes in 2 parameters par_DateFrom and par_DateTo
I'm getting a nasty error. I'm pretty sure that reusing alias TotalDaysOut to calculate TotalIncome is the culprit. How can I fix this elegantly?
Error 1247:
reference TotalDaysOutnot supported reference to group function
BEGIN
SELECT t.LicencePlate
,f.Make
,f.Model
,f.Year
,COUNT(t.LicencePlate) AS TotalTrx
,SUM(DATEDIFF(IF(checkedIn >par_DateTo, par_DateTo, checkedIn) ,IF(checkedOut <par_DateFrom, par_DateFrom, checkedOut))) AS TotalDaysOut
,SUM(t.Price* (SELECT TotalDaysOut)) AS TotalIncome
FROM TRANSACTIONS t
INNER JOIN FLEET f
ON t.LicencePlate = f.LicencePlate
WHERE t.CheckedOut < par_DateTo AND t.CheckedIn > par_DateFrom
GROUP BY t.LicencePlate
,f.Make
,f.Model
,f.Year;
END
Since your predicates are already verifying that all of the columns referenced in the expression are not null:
checkedIn
par_DateTo
checkedOut
par_DateFrom
(The predicates in the WHERE clause require all of those to be non-NULL), you could simplify the expression a bit, to reference each column once, rather than twice:
DATEDIFF(LEAST(t.checkedIn, par_DateTo),GREATEST(t.checkedOut, par_DateFrom))
And (as Gordon already suggested) just repeat that expression where the result is needed.
When we absolutely, positively have to have reference to an alias from a query, the only real option in MySQL is to use an inline view, though this approach has significant performance consequences for large sets.
SELECT v.LicencePlate
, f.Make
, f.Model
, f.Year
, COUNT(v.LicencePlate) AS TotalTrx
, SUM(v.DaysOut) AS TotalDaysOut
, SUM(v.DaysOut)*v.Price AS TotalIncome
FROM ( SELECT t.LicencePlate
, t.Price
, DATEDIFF(
LEAST(t.checkedIn, par_DateTo),
GREATEST(t.checkedOut, par_DateFrom)
) AS DaysOut
FROM TRANSACTIONS t
WHERE t.CheckedOut < par_DateTo
AND t.CheckedIn > par_DateFrom
) v
JOIN FLEET f
ON f.LicencePlate = v.LicencePlate
GROUP
BY v.LicencePlate
, f.Make
, f.Model
, f.Year
That's less performant, and less elegant, than just simplifying and repeating the expression.
You can't do that. You cannot reference a column alias at the same level of the select as where it is defined. I'm not sure what the exact error is but the (select TotalDaysOut) doesn't make sense.
So, repeat the expression with the additional multiplication:
SUM(DATEDIFF(IF(checkedIn >par_DateTo, par_DateTo, checkedIn) ,IF(checkedOut <par_DateFrom, par_DateFrom, checkedOut))) AS TotalDaysOut,
SUM(t.Price * DATEDIFF(IF(checkedIn >par_DateTo, par_DateTo, checkedIn) ,IF(checkedOut <par_DateFrom, par_DateFrom, checkedOut))) AS TotalIncome

MS Access query multiple criteria

I am trying to build an access query with multiple criteria. The table to be queried is "tblVendor" which has information about vendor shipment data as shown below:
The second table is "tblSchedule" which has the schedule for each Vendor cutoff date. This table has cutoff dates for data analysis.
For each vendor, I need to select records which have the ShipDate >= CutoffDate. Although not shown in the data here, it may be possible that multiple vendors have same CutoffDate.
For small number of records in "tblCutoffdate", I can write a query which looks like:
SELECT tblVendors.ShipmentId, tblVendors.VendorNumber, tblVendors.VendorName,
tblVendors.Units, tblVendors.ShipDate
FROM tblVendors INNER JOIN tblCutoffDate ON tblVendors.VendorNumber =
tblCutoffDate.VendorNumber
WHERE (((tblVendors.VendorNumber) In (SELECT VendorNumber FROM [tblCutoffDate] WHERE
[tblCutoffDate].[CutoffDate] = #2/1/2014#)) AND ((tblVendors.ShipDate)>=#2/1/2014#)) OR
(((tblVendors.VendorNumber) In (SELECT VendorNumber FROM [tblCutoffDate] WHERE
[tblCutoffDate].[CutoffDate] = #4/1/2014#)) AND ((tblVendors.ShipDate)>=#4/1/2014#));
As desired, the query gives me a result which looks like:
What concerns me now is that I have a lot of records being added to the "tblCutoffDate" which makes it difficult for me to hardcode the dates in the query. Is there a better way to write the above SQL statement without any hardcoding?
You might try something like -- this should handle vendors having no past cutoff,
or those having no future cutoff
"today" needs a suitable conversion to just date w/o time
comparison "=" may go on both, or one, or none Max/Min
"null" may be replaced by 1/1/1900 and 12/31/3999 in Max/Min
SELECT tblvendors.shipmentid,
tblvendors.vendornumber,
tblvendors.vendorname,
tblvendors.units,
tblvendors.shipdate
FROM tblvendors
LEFT JOIN
( SELECT vendornum,
Max( iif cutoffdate < today, cutoffdate, null) as PriorCutoff,
Min( iif cutoffdate >= today, cutoffdate, null) as NextCutoff
FROM tblcutoffdate
GROUP BY vendornum
) as VDates
ON vendornumber = vendornum
WHERE tblvendors.shipdate BETWEEN PriorCutoff and NextCutoff
ORDER BY vendornumber, shipdate, shipmentid
A simpler WHERE clause should give you what you want.
SELECT
v.ShipmentId,
v.VendorNumber,
v.VendorName,
v.Units,
v.ShipDate
FROM
tblVendors AS v
INNER JOIN tblCutoffDate AS cd
ON v.VendorNumber = cd.VendorNumber
WHERE v.ShipDate >= cd.CutoffDate;