Optimizing SQL Query speed - sql

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

Related

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

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

Query from multiple tables with multiple where conditions in the tables

I'm trying to get a count of all speakers who are active regarding that item as well as the total of speakers who correlate to a certain item. The first LEFT JOIN for the total speakers works, but the other for ONLY the active speakers regarding that item doesn't, any help is appreciated. The SQLFiddle is here
http://sqlfiddle.com/#!3/b579d/1
But when I try to add in the portion where you would get the number of active speakers
(LEFT JOIN (SELECT COUNT (tbl_SpeakerCard_Log.SpeakerName)
WHERE tbl_Speaker_Log.Spoken = 0)
ON tbl_AgendaList.AID = tbl_SpeakerCard_Log.AID)
under the previous LEFT JOIN I get an error. I'm 100% sure the query is wrong in some form, but I'm not sure how to approach it.
*NOTE: Spoken/Active are interchangeable, I just use different wording to clarify what I'm looking for.
EDIT: This is the desired output
http://imgur.com/yP1FKxg
You can use conditional aggregation to do this:
SELECT
AgendaList.AID,
AgendaList.Item,
COUNT(SpeakerList.SPID) as SpeakerTotal,
SUM(CASE WHEN SpeakerList.Spoken = 0 THEN 1 ELSE 0 END) as ActiveSpeakers
FROM AgendaList
LEFT JOIN SpeakerLIST ON AgendaList.AID = SpeakerList.AID
GROUP BY AgendaList.AID, AgendaList.Item;
Sample SQL Fiddle
Or you could use count instead of sum (which might be clearer):
COUNT(CASE WHEN Spoken = 0 THEN Spoken END) as ActiveSpeakers
SQL FIDDLE
WITH sTotal AS (
SELECT AgendaList.AID, AgendaList.Item, COUNT( SpeakerList.SPID) as SpeakerTotal
FROM AgendaList
LEFT JOIN SpeakerLIST ON AgendaList.AID = SpeakerList.AID
GROUP BY AgendaList.AID, AgendaList.Item
),
sActive AS (
SELECT AgendaList.AID, AgendaList.Item, COUNT( SpeakerList.SPID) as SpeakerActive
FROM AgendaList
LEFT JOIN SpeakerLIST ON AgendaList.AID = SpeakerList.AID
WHERE SpeakerLIST.Spoken = 0
GROUP BY AgendaList.AID, AgendaList.Item
)
SELECT sTotal.*, sActive.SpeakerActive
FROM sTotal left join
sActive on sTotal.AID = sActive.AID

T-SQL, possibly combining MAX and BETWEEN?

I have the following excerpt from an SQL Query in SQL Server:
LEFT JOIN tbl_StuAssess SA (NOLOCK)
ON CA.ASMT_PK = SA.ASMT_PK
AND SA.DELT_FLAG = 0
AND CA.SCRE_MTOD_PK = SA.SCRE_MTOD_PK
AND CVS.STUD_PK = SA.STUD_PK
AND(SA.ASMT_DATE BETWEEN #YearStartDate AND #YearEndDate)
Here is my issue, If the first 4 conditions I met, I want the last condition (SA.ASMT_DATE BETWEEN #YearStartDate AND #YearEndDate) to only return the Latest date that is in between #YearStartDate and #YearEndDate and that meets all the other conditions. Can anyone tell me how I can do that?
Thank you.
You can do possibly do this with a subquery:
LEFT JOIN
(select sa.*,
row_number() over (partition by asmt_pk, scre_mtod_pk, stud_pk order by asmt_dt desc) as seqnum
from tbl_StuAssess SA (NOLOCK)
where SA.DELT_FLAG = 0 and (SA.ASMT_DATE BETWEEN #YearStartDate AND #YearEndDate))
) sa
ON CA.ASMT_PK = SA.ASMT_PK and
CA.SCRE_MTOD_PK = SA.SCRE_MTOD_PK and
CVS.STUD_PK = SA.STUD_PK and
sa.seqnum = 1
This calculates the last date before the join. This works, if the join conditions are not filtering out some but not all of the records for a given group of asmt_pk, scre_mtod_pk, stud_pk.
The alternative is to use row_number() at the next higher level. Same idea, but without the rest of the code, I can't provide a code sample.

Error: Subquery returned more than 1 value [closed]

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 10 years ago.
The full error I get is:
Msg 512, Level 16, State 1, Line 1
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
I was passed by this SQL code that used to work in the past and I need to fix it, but I get the error above, but even if I comment out some parts the error stays the same
Here is the sql code:
SELECT
OrderId = OrdNameAdd.ORDERS_ID,
LTRIM(ISNULL(OrdNameAdd.OBY_FirstName, '') + ' ' + ISNULL(OrdNameAdd.OBY_LASTNAME, '')) AS OrderedByName,
ObyVar1 = (SELECT varfld_value FROM MAILERVBL WHERE OBYMAILER = MAILERVBL.MAILER_SEQID AND VARDEF_SEQUENCE = 1),
ObyVar2 = (SELECT varfld_value FROM MAILERVBL WHERE OBYMAILER = MAILERVBL.MAILER_SEQID AND VARDEF_SEQUENCE = 2),
ObyVar3 = (SELECT varfld_value FROM MAILERVBL WHERE OBYMAILER = MAILERVBL.MAILER_SEQID AND VARDEF_SEQUENCE = 3),
ExtendedDefaultValue = (SELECT sum(isnull(p.prduct_value,0) * (isnull(pickdt.prdord_toshipqty,1)))
FROM PICKDT
LEFT OUTER JOIN
locVerBals ON PICKDT.PRVERS_SEQID = locVerBals.PRVERS_Seqid
LEFT OUTER JOIN
LOCPRDSUM p ON locVerBals.PRDUCT_Seqid = p.PRDUCT_SEQID
AND p.system_id = PICPAK.SYSTEM_ID
WHERE
PICKDT.PICPAK_Seqid = PICPAK.PICPAK_Seqid
AND p.PRDUCT_INACTIVEDATE IS NULL),
FulfCharges = (rpt_BD.Linesshipped * ACCDEF_ChargePerLine) + ACCDEF_ChargePerShipment,
PubFreight = (rpt_BD.PubFreight),
TotalValue =
(SELECT
sum(isnull(p.prduct_value, 0) * (isnull(pickdt.prdord_toshipqty, 1)))
from PICKDT
LEFT OUTER JOIN locVerBals ON PICKDT.PRVERS_SEQID = locVerBals.PRVERS_Seqid
LEFT OUTER JOIN LOCPRDSUM p ON locVerBals.PRDUCT_Seqid = p.PRDUCT_SEQID
AND p.system_id = PICPAK.SYSTEM_ID
WHERE
PICKDT.PICPAK_Seqid = PICPAK.PICPAK_Seqid AND p.PRDUCT_INACTIVEDATE IS NULL)
+ isnull((rpt_BD.Linesshipped * ACCDEF_ChargePerLine), 0)
+ isnull(ACCDEF_ChargePerShipment,0) + isnull((rpt_BD.PubFreight), 0)
FROM
PICPAK
LEFT OUTER JOIN
ORDSTO ON PICPAK.ORDSTO_Seqid = ORDSTO.ORDSTO_Seqid
LEFT OUTER JOIN
OrdNameAdd ON ORDSTO.ORDERS_Seqid = OrdNameAdd.ORDERS_SEQID
LEFT OUTER JOIN
Rpt_BillingDetail rpt_BD ON PICPAK.PICPAK_Seqid = rpt_BD.PICPAK_Seqid
---------------hERE
WHERE
(convert(datetime, DateShipped, 1) >= '4/17/2012'
AND convert(datetime, DateShipped, 1) <= '12/17/2012')
---------------HERE
AND (PICPAK.PICPAK_Status = 'Complete' OR PICPAK.PICPAK_Status = 'Shipped'
OR picpak_Status = 'Voided')
/*AND (p.PRDUCT_INACTIVEDATE IS NULL)*/
AND ISNULL(PICPAK.SUPPLR_SEQID,0) = 0
GROUP BY
OrdNameAdd.ORDERS_ID,
OrdNameAdd.OBY_FirstName,
OrdNameAdd.OBY_LASTNAME,
OrdNameAdd.obymailer,
PICPAK.PICPAK_Seqid,
PICPAK.System_Id,
rpt_BD.PubFreight,
ACCDEF_ChargePerLine,
ACCDEF_ChargePerShipment,
rpt_BD.Linesshipped,
rpt_BD.Numpackages,
ordsto.orders_seqid
ORDER BY
ordsto.orders_seqid ASC
In your query, you have five subqueries for ObyVar1, ObyVar2, ObyVar3, ExtendedDefaultValue, and TotalValue.
The two value subqueries are using aggregations with no group by, so they should be returning one value.
Your problemis in the three ObyVars.
There are simple two ways to get rid of this:
Aggregate the values. So do max(varfld_value).
Choose one value. In SQL Server, this would be a top 1 (in other databases, it might be rownum = 1 or limit 1).
However, overall, I find select statements within select statements to be undesirable. I would replace those three variables with a subquery in the from clause:
(select mailer_seqid,
max(case when vardef_sequence = 1 then varfld_value end) as vv_1,
max(case when vardef_sequence = 2 then varfld_value end) as vv_2,
max(case when vardef_sequence = 3 then varfld_value end) as vv_3
from mailervbl
group by mailer_seqid
)
In most cases, this should be as or more efficient than the three subqueries.
I would suggest you to execute each sub query separately to find out which one is returning more than one record
If you have sub queries to SELECT columns Ex: (1) or with WHERE col = cluses Ex:(2) you should make sure only a single value is returned.
--Ex:(1) make sure only one row returned by the sub query,
--this case can be voided using TOP 1 from the sub query
SELECT col1, (SELECT colX from Table2 )
FROM Table1
--Ex:(2) this case can be avoided using IN instead =
SELECT col1, col2
FROM Table1
WHERE col3 = (SELECT colX from Table2 )
There seems no way we can identify them without having access your database, it's not advisable to write any where statement like this, not just talking about performance but also the problem you now facing.
To fix this you need either to go through the full logic and rewrite it with no sub-queries, or see if you can filter out some data via DISTINCT/TOP 1 in sub query without broke the logic itself. I suggest you rewrite it, sometimes it sounds complex but make things simpler.
You can't get a duplicate from the sub-queries where you're doing a single SUM statement so the problem is against the MAILERVBL table.
If you're ever looking for duplicates, run the following type of code to identify your duplicates:
select MAILER_SEQID, VARDEF_SEQUENCE, count(*) as numentries
from MAILERVBL
group by MAILER_SEQID, VARDEF_SEQUENCE
having count(*) > 1
order by numentries desc -- not really needed but useful to see where most of your duplicates are
Put whichever columns you are expecting to be unique in both your select and group by clauses.

Why grouping in a subquery causes problems

When I include the 2 commented out lines in the following subquery, seems that it takes forever until my Sybase 12.5 ASE server gets any results. Without these 2 lines the query runs ok. What is so wrong with that grouping?
select days_played.day_played, count(distinct days_played.user_id) as OLD_users
from days_played inner join days_received
on days_played.day_played = days_received.day_received
and days_played.user_id = days_received.user_id
where days_received.min_bulk_MT > days_played.min_MO
and days_played.user_id in
(select sgia.user_id
from days_played as sgia
where sgia.day_played < days_played.day_played
--group by sgia.user_id
--having sum(sgia.B_first_msg) = 0
)
group by days_played.day_played
Find out what the query does by using showplan to show the explanation.
In this case Can't you eliminate the subquery by making it part of the main query?
Could you try rewriting the query as follows?
select days_played.day_played,
count(distinct days_played.user_id) as OLD_users
from days_played
inner join days_received on days_played.day_played = days_received.day_received
and days_played.user_id = days_received.user_id
where days_received.min_bulk_MT > days_played.min_MO
and 0 = (select sum(sgia.B_first_msg)
from days_played as sgia
where sgia.user_id = days_played.user_id
and sgia.day_played < days_played.day_played
)
group by days_played.day_played
I guess this should give you better performance...
ok I found out what the problem was
I had to include user id in the subquery: "where days_played.user_id = sgia.user_id
and sgia.day_played < days_played.day_played"