ORA-00934: group function is not allowed here when adding case when - sql

I have a piece of code which runs fine. However, when i am introducing a "case when" statement in the select clause, I get the "group function is not allowed here" error and I cannot fix it (the issue relates to the last Group by function in my code)
Any idea why (don't be put off by the code, it is 3 joins together, apparently the issue is caused by the last Group By statement) ?
Thank you!
SELECT
Trans_Table.MTAGRE01_NO
, (case when Cash. MTAGRE01_NO = Trans_Table. MTAGRE01_NO
then (SUM(Trans_Table.MTTRANS01_VALUENCU)*-1)
else SUM(Trans_Table.MTTRANS01_VALUENCU) END) AS MTTRANS01_VALUENCU
FROM MTTRANS01 Trans_Table
INNER JOIN RUTRANTYPE01 Trans_Type
ON Trans_Type.RUTRANTYPE01_CODE = Trans_Table.RUTRANTYPE01_CODE
LEFT JOIN(
SELECT
MTAGRE01_NO
,CASE WHEN SRAGRESTAT01_CODE = 'COLL' THEN MTAGRE01_AGRESTATDATE END AS Date_Fr
from MTAGRE01
where CASE WHEN SRAGRESTAT01_CODE = 'COLL' THEN MTAGRE01_AGRESTATDATE END is not null
) F_Date
ON F_Date.MTAGRE01_NO = Trans_Table.MTAGRE01_NO
LEFT JOIN(
SELECT
Trans_Table.MTAGRE01_NO
FROM MTTRANS01 Trans_Table
INNER JOIN RUTRANTYPE01 Trans_Type ON Trans_Type.RUTRANTYPE01_CODE = Trans_Table.RUTRANTYPE01_CODE
GROUP BY
Trans_Table.MTAGRE01_NO, Trans_Type.RUTRANTYPE01_CODE, Trans_Type.RUTRANTYPE01_DESCRIPTION
) Cash
ON Cash.MTAGRE01_NO = Trans_Table.MTAGRE01_NO
where Trans_Type.SRPROCTYPE01_CODE in ('C','D')
and Trans_Table.MTTRANS01_VALUEDATE >= F_Date.Date_Fr
GROUP BY
Trans_Table.MTAGRE01_NO
, (case when Cash. MTAGRE01_NO = Trans_Table. MTAGRE01_NO
then (SUM(Trans_Table.MTTRANS01_VALUENCU)*-1)
else SUM(Trans_Table.MTTRANS01_VALUENCU) END);

I believe it's hung up on the sum inside the case statement. 2 routes to correct that I can see, likely alot more:
This is a little hacky, but it'll work and give results fast. Change your case:
SELECT
Trans_Table.MTAGRE01_NO
, (case when Cash. MTAGRE01_NO = Trans_Table. MTAGRE01_NO
then ((Trans_Table.MTTRANS01_VALUENCU)*-1)
else (Trans_Table.MTTRANS01_VALUENCU) END) AS MTTRANS01_VALUENCU
Remove the case from the group by.
Now call your entire query a sub query
Select MTAGRE01_NO, sum(MTTRANS01_VALUENCU)
(your entire query)a
You other option that takes a bit more work...in your initial from statement:
MTTRANS01 Trans_Table
Change that to a subquery that joins to the trans table and returns
case when Cash. MTAGRE01_NO = Trans_Table. MTAGRE01_NO
then ((Trans_Table.MTTRANS01_VALUENCU)*-1)
else (Trans_Table.MTTRANS01_VALUENCU) END as MTAGRE01_NO
Then join to that subquery and do a simple sum in your main query.
Hopefully that all makes sense, ask questions if you need clarifications

Related

Optimizing SQL Query speed

I am trying to optimize my SQL query below as I am using a very old RDMS called firebird. I tried rearranging the items in my where clause and removing the order by statement but the query still seems to take forever to run. Unfortunately firebird doesn't support Explain Execution Plan Functionalities and therefore I cannot identify the code that is holding up the query.
select T.veh_reg_no,T.CON_NO, sum(T.pos_gpsunlock) as SUM_GPS_UNLOCK,
count(T.pos_gpsunlock) as SUM_REPORTS, contract.con_name
from
(
select veh_reg_no,CON_NO,
case when pos_gpsunlock = upper('T') then 1 else 0 end as pos_gpsunlock
from vehpos
where veh_reg_no in
( select regno
from fleetvehicle
where fleetno in (97)
) --DS5
and pos_timestamp > '2022-07-01'
and pos_timestamp < '2022-08-01'
) T
join contract on T.con_no = contract.con_no
group by T.veh_reg_no, T.con_no,contract.con_name
order by SUM_GPS_UNLOCK desc;
If anyone can help it would be greatly appreciated.
I'd either comment out some of the sub-queries or remove a join or aggregation and see if that improves it. Once you find the offending code maybe you can move it or re-write it. I know nothing of Firebird but I'd approach that query with the below code, wrapping the aggregation outside of the joins and removing the "Where in" clause.
If nothing works can you create an aggregation table or pre-filtered table and use that?
select
x.*
,sum(case when x.pos_gpsunlock = upper('T') then 1 else 0 end) as SUM_GPS_UNLOCK
,count(*) as SUM_REPORTS
FROM (
select
a.veh_reg_no
,a.pos_gpsunlock
,a.CON_NO
,c.con_name
FROM vehpos a
JOIN fleetvehicle b on a.veg_reg_no = b.reg_no and b.fleetno = 97 and b.pos_timestamp between '222-07-01' and '2022-08-01'
JOIN contract c on a.con_no = contract.con_no
) x
Group By....
This might help by converting subqueries to joins and reducing nesting. Also an = instead of IN() operation.
select vp.veh_reg_no,vp.con_no,c.con_name,
count(*) as SUM_REPORTS,
sum(case when pos_gpsunlock = upper('T') then 1 else 0 end) as SUM_GPS_UNLOCK
from vehpos vp
inner join fleetvehicle fv on fv.fleetno = 97 and fv.regno = vp.veh_reg_no
inner join contract c on vp.con_no = c.con_no
where vp.pos_timestamp >= '2022-07-01'
and vp.pos_timestamp < '2022-08-01'
group by vp.veh_reg_no, vp.con_no, c.con_name

Access alias in CASE statement

I am trying to create a column called DateStartedStatus that utilizes a previously aliased column to compute its value. It should use CurrentStatus to output a value and an error is showing that says "Invalid column name 'CurrentStatus'". How can I access that alias in the below case statement?
SELECT p.[ID]
,p.[Name] as 'ProcurementName'
,p.[FundingDocumentNumber] as 'FundingDocumentNumber'
,p.[Status]
,p.[Comments] as 'Comments'
,p.[isSAVE]
,p.[InWorkDate]
,p.[RoutedDate]
,p.[FundsCertifiedDate]
,p.[AwardedDate]
,p.[TransactionType]
,p.[FNMSStatus]
,p.[Closed]
,p.[Archived]
,p.[Cancelled]
,(CASE
WHEN p.[Status] = 'In Work' THEN 'Pending'
ELSE p.[Status]
END) as CurrentStatus
,(CASE
WHEN CurrentStatus = 'Awarded' THEN p.AwardedDate <-- fails here CurrentStatus not a column
END) as DateStartedStatus
,(SELECT SUM(TotalCost)
FROM ProcurementsRequestLineItems subprlis
LEFT JOIN RequestLineItems subrli ON subprlis.RequestLineItemID = subrli.ID
WHERE ProcurementID = p.ID) as TotalCost
FROM Procurements p
WHERE p.Closed = 0 AND p.Archived = 0;
Use a subquery as suggested by leftjoin, or move the CurrentStatus logic to a CTE. I prefer CTE as they are more legible to me, but I know many prefer a subquery as it is right in the middle of the code, and in a longer query or one with many CTE's that can be a more legible route.
WITH CurrentStatus
AS
(
SELECT
... -- at least one JOIN'able column back to the main query
,(CASE
WHEN p.[Status] = 'In Work' THEN 'Pending'
ELSE p.[Status]
END) as CurrentStatus
FROM ...
)
Using subqueries like this
select ... CASE WHEN CurrentStatus ....
from
( --calculate Current_status here
select ....
CASE
WHEN p.[Status] = 'In Work' THEN 'Pending'
ELSE p.[Status]
END) as CurrentStatus
...
) s
Do not worry, subquery will not add computational complexity, optimizer will remove it if possible.
Another way is nested CASE expressions (query is not readable):
case when case ... some logic here ... end = 'Awarded'
then ...
end
For SQL Server, I would use a CROSS APPLY instead of an subquery, because I prefer it for readability. For one-condition evaluation, I use IIF instead of CASE.
SELECT p.[ID], p.[Name] AS [ProcurementName], p.[FundingDocumentNumber] AS [FundingDocumentNumber],
p.[Status], p.[Comments] AS [Comments], p.[isSAVE], p.[InWorkDate], p.[RoutedDate], p.[FundsCertifiedDate],
p.[AwardedDate], p.[TransactionType], p.[FNMSStatus], p.Closed, p.[Archived], p.[Cancelled],
cur.CurrentStatus, start.DateStartedStatus, tot.TotalCost
FROM Procurements AS p
CROSS APPLY (SELECT IIF(p.[Status] = 'In Work', 'Pending', p.[Status]) AS CurrentStatus) AS cur
CROSS APPLY (SELECT IIF(cur.CurrentStatus = 'Awarded', p.AwardedDate, null) AS DateStartedStatus) AS start
CROSS APPLY (
SELECT SUM(TotalCost) AS name
FROM ProcurementsRequestLineItems AS subprlis
LEFT JOIN RequestLineItems AS subrli ON subprlis.RequestLineItemID = subrli.ID
WHERE ProcurementID = p.ID
) AS tot
WHERE p.Closed = 0 AND p.Archived = 0;
I would also avoid using the reserved word "Status" as a column identifier.

Arithmetic overflow error converting varchar to data type numeric CASE statement

I am trying to write a query that returns an "Estimated Annual Value", and for this, I am using a Case statement. There are two inner joins involved in the query.
So, the query and gives me result when I am running this piece:
select Users.Id, Name, PhoneNumber, Email, Currency, count(*) as TotalOrders, sum(TotalCustomerAmount) as TotalOrderValue, avg(TotalCustomerAmount) as AverageOrderValue, TsCreate as RegistrationDate, max(TsOrderPlaced) as LastOrderDate, min(TsOrderPlaced) as FirstOrderDate,
CASE
When PromotionsEnabled = 0 then 'Y'
When PromotionsEnabled = 1 then 'n'
else 'undefined'
end as Promotions,
/*CASE
When ((DATEDIFF(day, max(TsOrderPlaced), min(TsOrderPlaced)) >= 6) AND (count(*) > 3)) then ((sum(TotalCustomerAmount)/(DATEDIFF(day, max(TsOrderPlaced), min(TsOrderPlaced))))*365)
Else '-'
end as EstimatedAnnualValue*/
from AspNetUsers with (nolock)
inner join Orders with (nolock) on Orders.UserId = AspNetUsers.Id and Orders.WhiteLabelConfigId = #WhiteLabelConfigId
and Orders.OrderState in (2,3,4,12)
inner join UserWhiteLabelConfigs with (nolock) on UserWhiteLabelConfigs.UserId = AspNetUsers.Id and Orders.WhiteLabelConfigId = #WhiteLabelConfigId
where AspNetUsers.Discriminator = 'ApplicationUser'
group by Users.Id, Name, Number, Currency, Email, TsCreate, PromotionsEnabled
But the problem comes when I am running this with the second CSAE statement, is it because I cannot use the aggregate function before GROUP BY? I am also thinking of using a Subquery
Looking fr some help!!
You need to use aggregation functions. For instance, if you want 'Y' only when all values are 0 or NULL:
(case when max(PromotionsEnabled) = 0 then 'Y'
when max(PromotionsEnabled) = 1 then 'N'
else 'undefined'
end) as Promotions,
I'm not sure if this is the logic you want (because that detail is not in the question). However, this shows that you can use aggregation functions in a case expression.

Syntax errors in sybase query

I have never used sybase before until today. I have a query in Access that is using First() and has multiple iif statements. I hoped for the best and pasted my query in and I am getting errors on the First() section. I removed the First's from the query, and I am getting errors on the iif statements.
Here is my query:
SELECT Client.ClientId, First(Trans.SysId) AS FirstOfSysId, Client.Name1,
Relation.Name1, Debtor.Name1, Account.AcctId, First(Trans.PostDate) AS FirstOfPostDate,
Sum(IIf([TransType]="uc",[Amount],IIf([TransType]="CA",[Amount],IIf([TransType]="RAC",
[Amount],IIf([TransType]="UCBR",[Amount],[amount]*-1))))) AS TransAmount,
Trans.CheckId,Relation.RelId
FROM (((Trans INNER JOIN Account ON Trans.SysAcctId = Account.SysId)
INNER JOIN Debtor ON Account.SysDtrId = Debtor.SysId)
INNER JOIN Relation ON Account.SysRelId = Relation.SysId) INNER JOIN Client ON
Relation.SysClientId = Client.SysId
WHERE (((Trans.TransType)="CA" Or (Trans.TransType)="NC" Or (Trans.TransType)="UC" Or
(Trans.TransType)="AC"
Or (Trans.TransType)="UCB" Or (Trans.TransType)="RAC" Or (Trans.TransType)="UCB" Or
(Trans.TransType)="UCBR"))
GROUP BY Client.ClientId, Client.Name1, Relation.Name1, Debtor.Name1, Account.AcctId,
Trans.CheckId, Relation.RelId
HAVING (((Client.ClientId)=[Forms]![frmCredit]![Combo112])
AND ((Sum(IIf([TransType]="uc",[Amount],IIf([TransType]="CA",[Amount],
IIf([TransType]="RAC", [Amount],IIf([TransType]="UCBR",[Amount],[amount]*-1))))))>0
Or (Sum(IIf([TransType]="uc",[Amount],IIf([TransType]="CA",[Amount],
IIf([TransType]="RAC", [Amount],IIf([TransType]="UCBR",[Amount],[amount]*-1))))))<0)
AND ((Relation.RelId) Not Like "281099"))
ORDER BY Relation.Name1, Account.AcctId, Trans.CheckId
[Edit] A more readable query has been added to http://sqlfiddle.com/#!2/4b98e0/1 [/Edit]
I can't find documentation on converting the first function, but with the IIF should I convert that to a Case when statement? I saw an article here, but I am not sure if it is the most efficient, since it is from 1999...
The error I am getting is Syntax error near 'FIRST' on line 1. Which based on the article here I am using it in the right syntax.
Any help on using First in the right syntax and if I should use a Case When instead of the iif is greatly appreciated.
Based on what I think the query does I think you can rewrite it as below. I don't have access to any Sybase server to validate the query, so I tried it on MS SQL but I commented out some stuff with /* */ that might not be needed with Sybase.
It might be completely wrong, or it might be correct. If it's wrong, please let me know in a comment and I'll remove my answer.
SELECT
Client.ClientId,
FIRST_VALUE(Trans.SysId) /* OVER (ORDER BY Trans.Sysid) */ AS FirstOfSysId,
Client.Name1,
Relation.Name1,
Debtor.Name1,
Account.AcctId,
FIRST_VALUE(Trans.PostDate) /* OVER (ORDER BY Trans.postdate) */ AS FirstOfPostDate,
SUM(CASE
WHEN [TransType] IN ('uc', 'CA', 'RAC', 'UCBR') THEN AMOUNT
ELSE AMOUNT * -1
END
) AS TransAmount,
Trans.CheckId,
Relation.RelId
FROM Trans
INNER JOIN Account ON Trans.SysAcctId = Account.SysId
INNER JOIN Debtor ON Account.SysDtrId = Debtor.SysId
INNER JOIN Relation ON Account.SysRelId = Relation.SysId
INNER JOIN Client ON Relation.SysClientId = Client.SysId
WHERE Trans.TransType IN ('CA','NC','UC','AC','UCB','RAC','UCB','UCBR')
GROUP BY
Client.ClientId, Client.Name1,
Relation.Name1, Debtor.Name1,
Account.AcctId, Trans.CheckId,
Relation.RelId /* , trans.sysid, trans.postdate */
HAVING
1=1 --AND (((Client.ClientId)=[Forms]![frmCredit]![Combo112])
AND SUM(
CASE WHEN [TransType] IN ('uc', 'CA', 'RAC', 'UCBR') THEN AMOUNT
ELSE AMOUNT * -1 END
) <> 0
AND Relation.RelId <> '281099'
ORDER BY Relation.Name1, Account.AcctId, Trans.CheckId
My reasoning here is that
Sum(IIf([TransType]="uc",[Amount],IIf([TransType]="CA",[Amount],IIf([TransType]="RAC",
[Amount],IIf([TransType]="UCBR",[Amount],[amount]*-1))))) AS TransAmount,
just takes theamountifTransTypeis any of'uc', 'CA', 'RAC', 'UCBR'elseAmount*-1 and this should be equivalent with:
SUM(CASE WHEN [TransType] IN ('uc', 'CA', 'RAC', 'UCBR') THEN AMOUNT
ELSE AMOUNT*-1 END) AS TransAmount,
and in thehavingclause this:
((Sum(IIf([TransType]="uc",[Amount],IIf([TransType]="CA",[Amount],
IIf([TransType]="RAC", [Amount],IIf([TransType]="UCBR",[Amount],[amount]*-1))))))>0
Or (Sum(IIf([TransType]="uc",[Amount],IIf([TransType]="CA",[Amount],
IIf([TransType]="RAC", [Amount],IIf([TransType]="UCBR",[Amount],[amount]*-1))))))<0)
just checks if the expression (sum(amount)) is either> 0or< 0and this should be equivalent with:
SUM(CASE WHEN [TransType] IN ('uc', 'CA', 'RAC', 'UCBR') THEN AMOUNT
ELSE AMOUNT*-1 END) <> 0

Case Statement with IS NULL not acting as required

Good Evening,
I have the following sql code and need to replace the NULL values within a sub query. As you can tell from the code I have tried using the ISNULL function and case where = NULL.
Could someone please help?
Select Student_Details.STU_ID ,
( Select case ISNULL( s1stu_disability_type.DISABILITY_TYPE_CD , '' )
when '' then 'NO'
else 'YES'
end
from s1stu_disability_type
Where Student_Details.STU_ID = s1stu_disability_type.STU_ID
and DISABILITY_TYPE_CD = '$HEAR'
) as 'Hearing Disability'
from S1STU_DET as Student_Details
Your where clause in your subquery rejects all rows but those where the column DISABILITY_TYPE_CD is '$HEAR'. Consequently, the case statement will always take the else route, as that column will never, ever be null or empty ('').
What exactly are you trying to do?
You query can better be written as
select sd.STU_ID ,
dt.DISABILITY_TYPE_CD
from S1STU_DET sd
join s1stu_disability_type dt on dt.STU_ID = sd.STU_D
and dt.DISABILITY_TYPE_CD = '$HEAR'
It's almost certian that relationship between student and student disability has a zero-to-many cardinalilty, which is to say that each student has zero or more disabilities.
As a result, your original query, with its correlated subquery, will return 1 row per student, but per the SQL standard, it's luck of the draw as to which matching disability gets selected by the subquery.
My query above will return one row per student with a matching disability. Students without a matching disability are excluded. To change that to include all students, you want to change the [inner] join to a left [outer] join. Each student will then be represented in the result set at least once. If the student has no matching disabilities, all columns for the student disability table will be 'null'.
If, as I suspect, what you're trying to do is identify students as to whether or not they have a hearing disability (or some particular type of disability), you need to summarize things. A query like this will likely do you:
select sd.STU_ID ,
case sign(coalesce(hd.cnt,0))
when 1 then 'YES'
else 'NO'
end as HAS_HEARING_DISABILITY
from S1STU_DET sd
left join ( select STU_ID ,
count(*) as cnt
from s1stu_disability
where DISABILITY_TYPE_CD = '$HEAR'
group by STU_ID
) hd on hd.STU_ID = sd.STU_ID
Try this:
Select Student_Details.STU_ID ,
IsNull(( Select case ISNULL( s1stu_disability_type.DISABILITY_TYPE_CD , '' )
when '' then 'NO'
else 'YES'
end
from s1stu_disability_type
Where Student_Details.STU_ID = s1stu_disability_type.STU_ID
and DISABILITY_TYPE_CD = '$HEAR'
), 'No') as 'Hearing Disability'
from S1STU_DET as Student_Details
Basically, the ISNULL function has to be outside the subquery for it to work the way you want it to. Think of it this way, if the subquery does not return any rows, the output will be null whether you have an isnull check inside the subquery or not.
You shouldn't need a subquery (assuming your subquery only returns one record):
Select
Student_Details.STU_ID,
case WHEN Student_Disability.DISABILITY_TYPE_CD IS NULL
THEN 'NO'
ELSE 'YES'
END
as 'Hearing Disability'
from S1STU_DET as Student_Details
LEFT JOIN s1stu_disability_type Student_Disability
ON Student_Details.STU_ID = Student_Disability.STU_ID
and Student_Disability.DISABILITY_TYPE_CD = '$HEAR'