MySQL function execution time increases everytime I SELECT it - mysql

Bit of a strange one. I have a function that runs perfectly fine at first, say 15ms execution and 10ms fetching when selecting the returned value plus some other columns. But if keep refreshing the same query, over and over, the execution of the query goes up. So first it's 15ms, then 17, then... I got it all the way to 900ms. It's mostly the fetching that goes up in time, but the execution too. So at the end it'll be 600ms for fetching and 300ms for execution. Any ideas what's going on?
Function. I experimented with just a simple IF/ELSEIF but it gives the same exact result in terms of performance.
create function get_table(var_account_id int unsigned) returns varchar(20)
reads sql data
BEGIN
RETURN IF(
(SELECT EXISTS(SELECT TRUE
FROM TableA
WHERE account_id = var_account_id
AND expiring_at > CURRENT_TIMESTAMP)), 'TableA',
IF((SELECT EXISTS(SELECT TRUE
FROM TableB
WHERE account_id = var_account_id
AND expiring_at > CURRENT_TIMESTAMP)), 'TableB',
IF((SELECT EXISTS(SELECT TRUE
FROM TableC
WHERE account_id = var_account_id
AND expiring_at > CURRENT_TIMESTAMP)), 'TableC',
IF((SELECT EXISTS(SELECT TRUE
FROM TableD
WHERE account_id = var_account_id
AND expiring_at > CURRENT_TIMESTAMP)), 'TableD',
NULL)
)));
END;
Explain of function after running it once with var_account_id = 1
9,SUBQUERY,TableD,,ref,"TableD_expiring_at_index,TableD_account_id_index",TableD_account_id_index,4,const,1,100,Using where
7,SUBQUERY,TableC,,ref,"TableC_account_id_index,TableC_expiring_at_index",TableC_account_id_index,4,const,1,5,Using where
5,SUBQUERY,TableB,,ref,"TableB_expiring_at_index,TableB_account_id_index",TableB_account_id_index,4,const,1,9.26,Using where
3,SUBQUERY,TableA,,ref,"TableA_expiring_at_index,TableA_account_id_index",TableA_account_id_index,4,const,1,100,Using where
Putting a compound index on account_id and expiring_at has no effect at all
And I run a simple query like
SELECT TableXYZ.*, get_table(TableXYZ.account_id) AS some_value FROM TableXYZ LIMIT 500;
I've run it on more complicated queries but the result is always the same, fast at first, slow after rerunning the same SELECT let's say 5 times a second for 30 secs straight. Even after I let MySQL cool off for a bit, come back, and the first run is still 900ms. And I'm sure it can keep going up. The only way to fix this is restarting the mysql service in windows.
Explain of the SELECT:
1,SIMPLE,TableXYZ,,ALL,,,,,695598,100,
I'm running these on Windows 10 if it matters, localy.

Sounds crazy. Maybe you can avoid the subqueries, which often cause performance problems
SELECT X.tabName
FROM (
SELECT 'TableA' as tabName, 1 as tabNr, account_id, expiring_at FROM TableA WHERE account_id = var_account_id AND expiring_at > CURRENT_TIMESTAMP
UNION
SELECT 'TableB' as tabName, 2 as tabNr, account_id, expiring_at FROM TableB WHERE account_id = var_account_id AND expiring_at > CURRENT_TIMESTAMP
UNION
...
) X ORDER BY tabNr LIMIT 1
If you want to avoid a union because you have very big tables, then why not use control flow in the function?
DECLARE tabName VARCHAR(50);
SET tabName := SELECT 'TableA' FROM TableA WHERE account_id = var_account_id AND expiring_at > CURRENT_TIMESTAMP;
IF (tabName IS NULL) THEN
SET tabName := SELECT 'TableB' FROM TableB WHERE account_id = var_account_id AND expiring_at > CURRENT_TIMESTAMP;
END IF;
and so on...
RETURN tabName;

RETURN COALESCE(
IF (EXISTS(...), "A", NULL),
IF (EXISTS(...), "B", NULL),
IF (EXISTS(...), "C", NULL),
IF (EXISTS(...), "D", NULL),
IF (EXISTS(...), "E", NULL)
)
where ... is, for example:
SELECT 1
FROM TableA
WHERE account_id = var_account_id
AND expiring_at > CURRENT_TIMESTAMP
Notes:
Be sure to have this index in table*: INDEX(account_id, expired_at) (in that order).
EXISTS() stops where a matching row is found. (One proposed solution failed to stop, hence took longer.)
COALESCE() does not need to evaluate all of its arguments. (UNION does.)
(No, I don't see why it would get slower and slower. Hopefully, my formulation will be consistently faster.)

Related

Subquery returned more than 1 value.4...Different query

Hello I have this query that i am trying to execute and i keep getting this error "Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.", Kindly help please.
DECLARE #NUMCOUNT BIT
Select #NUMCOUNT = (SELECT
CASE WHEN
(SELECT COUNT(R5REQUISLINES.RQL_REQ)
WHERE R5REQUISLINES.RQL_STATUS IN ('A')
) IN
(SELECT COUNT(R5REQUISLINES.RQL_REQ)
WHERE R5REQUISLINES.RQL_STATUS IN ( 'A','C') ) THEN 1 else 0 END AS NUMCOUNT1
FROM R5REQUISLINES JOIN
R5REQUISITIONS ON R5REQUISLINES.RQL_REQ = R5REQUISITIONS.REQ_CODE
GROUP BY R5REQUISLINES.RQL_REQ, R5REQUISITIONS.REQ_CODE,R5REQUISLINES.RQL_STATUS
)
IF #NUMCOUNT = '1'
begin
UPDATE R5REQUISITIONS
SET R5REQUISITIONS.REQ_STATUS = 'CP'
end
Ok, it sounds like what you actually want to do is update R5REQUISITIONS when there is no RQL_STATUS = 'C' in R5REQUISLINES, since you said you want to count the records where the RQL_STATUS is A and where it's A or C, and then do the update if the counts are the same.. You can greatly simplify this task with the following query:
UPDATE r5
SET r5.REQ_STATUS = 'CP'
FROM R5REQUISITIONS r5
WHERE NOT EXISTS (SELECT 1 FROM R5REQUISLINES r5q WHERE r5q.RQL_REQ = r5.REQ_CODE AND r5q.RQL_STATUS = 'C')
Your 'SELECT CASE' is returning more than 1 record, so it can't be assigned to #NUMBER. Either fix the sub-query to only return the record your looking for or hack it to return only 1 with a 'LIMIT 1' qualification.
I don't know what your data looks like so I can't tell you why your case subquery returns more records than you think it should.
Try running this and see what it returns, that will probably tell you wall you need to know:
SELECT
CASE WHEN
(SELECT COUNT(R5REQUISLINES.RQL_REQ)
WHERE R5REQUISLINES.RQL_STATUS IN ('A')
) IN
(SELECT COUNT(R5REQUISLINES.RQL_REQ)
WHERE R5REQUISLINES.RQL_STATUS IN ( 'A','C')
)
THEN 1
ELSE 0
END AS NUMCOUNT1
FROM R5REQUISLINES JOIN
R5REQUISITIONS ON R5REQUISLINES.RQL_REQ = R5REQUISITIONS.REQ_CODE
GROUP BY R5REQUISLINES.RQL_REQ, R5REQUISITIONS.REQ_CODE,R5REQUISLINES.RQL_STATUS
If there is more than 1 row returned, that's where your problem is.

How to query a SQL statement which depends on other values of same table?

I have a table with 3 columns( name, objectroot_dn, distinguishedname). Here distinguishedname is like a parent to objectroot_dn. I have to find whether for each objectroot_dn is there a child exists or not?
I can do this using the query below. It will return True if there is a child, False if there is not. But my problem is when the total dataset gets increased it takes lots of time.
For example, If the total number of row is 50,000 then it takes 10 mins for this query to complete.
Since I'm using a framework for different database, I can't index the columns.
SELECT
name,
objectroot_dn,
distinguishedname,
CASE
WHEN (SELECT count(*)
FROM (SELECT name
FROM elaoucontainergeneraldetails
WHERE objectroot_dn = dn.distinguishedname
LIMIT 1) AS tabel1) > 0
THEN 'True'
ELSE 'False'
END
FROM elaoucontainergeneraldetails AS dn
WHERE objectroot_dn = 'SOME_VALUE';
Please let me know how can I increase the speed of this query.
Thanks in advance. Appreciate all help.
You can have the same solution using left join or exists:
SELECT
dn.name,
dn.objectroot_dn,
dn.distinguishedname,
CASE
WHEN dn_in.objectroot_dn is not null
THEN 'True'
ELSE 'False'
END
FROM elaoucontainergeneraldetails AS dn
LEFT JOIN elaoucontainergeneraldetails dn_in on dn_in.objectroot_dn = dn.distinguishedname
WHERE objectroot_dn = 'SOME_VALUE';
EXISTS(subquery) yields a boolean value:
SELECT dn.name
, dn.objectroot_dn
, dn.distinguishedname
, EXISTS (SELECT *
FROM elaoucontainergeneraldetails nx
WHERE nx.objectroot_dn = dn.distinguishedname
) AS truth_value
FROM elaoucontainergeneraldetails AS dn
WHERE dn.objectroot_dn = 'SOME_VALUE'
;

Performance issue with update query after adding index

I have added the index to my update query and by adding the same query start taking several hours to complete the process.While without index its completing in some minutes i have added the index to faster the process but it became very slow exactly opposite to my desire.
Below is sample code snippet of my code.
Cursor c_updt_stg_rsn is
select distinct substr(r.state_claim_id, 1, 12), ROWID
from nemis.stg_state_resp_rsn r
WHERE r.seq_resp_plan_id = v_seq_resp_plan_id
and r.submitted_claim_id is null
and r.filler_2 is null;
BEGIN
OPEN c_updt_stg_rsn;
LOOP
FETCH c_updt_stg_rsn BULK COLLECT
INTO v_state_claim_id, v_rowid LIMIT c_BULK_SIZE;
FORALL i IN 1 .. v_state_claim_id.COUNT()
UPDATE /*+ index(STG_STATE_RESP_RSN,IDX2_STG_STATE_RESP_RSN) */ nemis.stg_state_resp_rsn
SET (submitted_claim_id , filler_2) = (SELECT DISTINCT submitted_claim_id, sl_group_id FROM nemis.state_sub_Resp_dtl D WHERE
(d.state, d.type_of_claim) in (select distinct state, type_of_claim
from nemis.resp_match_state
where seq_resp_match_table_level_id in
(select seq_resp_match_table_level_id
from nemis.resp_match_table_level
where seq_resp_plan_id = v_seq_resp_plan_id))
AND resp_state_claim_id LIKE v_state_claim_id(i)||'%'
)
WHERE ROWID = v_rowid(i);
IF v_state_claim_id.COUNT() != 0 THEN
v_cnt_rsn := v_cnt_rsn + SQL%ROWCOUNT;
END IF;
COMMIT;
EXIT WHEN c_updt_stg_rsn%NOTFOUND;
END LOOP;
CLOSE c_updt_stg_rsn;

mysql usage of 'not in' without column value

i have a table Transactions that looks similar to this:
id Type Field ObjectId NewValue
1 AddLink HasMember 4567 someDomain/someDirectory/1231
2 AddLink HasMember 4567 someDomain/someDirectory/1232
3 AddLink HasMember 4567 someDomain/someDirectory/1233
4 DeleteLink HasMember 4567 someDomain/someDirectory/1231
The numeric end of "NewValue" is what i am interested in.
In Detail, i need those records where i have a record where type is "AddLink" and where no newer record of type "DeleteLink" exists, i.e. the records with id = 2 or 3 (since 4 deletes 1)
The "ObjectId" as well as the numeric bit of "NewValue" both are IDs of entries of the "tickets" table, and i need the relevant tickets.
i tried this:
SELECT `Tickets`.* FROM `Transactions` AS `addedLinks`
LEFT JOIN `Tickets` ON RIGHT (`addedLinks`.`NewValue`, 4) = `Tickets`.`id`
WHERE `addedLinks`.`Type` = 'AddLink'
AND `addedLinks`.`Field` = 'Hasmember'
AND `addedLinks`.`ObjectId` = '4567'
AND NOT RIGHT (`addedLinks`.`NewValue`, 4) in (
SELECT `Tickets`.* FROM `Transactions` AS `deletedLinks`
LEFT JOIN `Tickets` ON RIGHT (`deletedLinks`.`NewValue`, 4) = `Tickets`.`id`
WHERE `deletedLinks`.`Type` = 'DeleteLink'
AND `addedLinks`.`id` < `deletedLinks`.`id`
AND `deletedLinks`.`Field` = 'Hasmember'
AND `deletedLinks`.`ObjectId` = '4567' )
This gives me:
SQL Error (1241): Operand should contain 1 column(s)
Unless i got something wrong, the problem is
RIGHT (`addedLinks`.`NewValue`, 4)
in the "AND NOT ... in()" statement.
Could anyone point me in the right direction here?
[EDIT]
Thanks to David K-J, the following works:
SELECT `Tickets`.* FROM `Transactions` AS `addedLinks`
LEFT JOIN `Tickets` ON RIGHT (`addedLinks`.`NewValue`, 4) = `Tickets`.`id`
WHERE `addedLinks`.`Type` = 'AddLink'
AND `addedLinks`.`Field` = 'Hasmember'
AND `addedLinks`.`ObjectId` = '5376'
AND NOT (RIGHT (`addedLinks`.`NewValue`, 4)) in (
SELECT `id` FROM `Transactions` AS `deletedLinks`
WHERE `deletedLinks`.`Type` = 'DeleteLink'
AND `addedLinks`.`id` < `deletedLinks`.`id`
AND `deletedLinks`.`Field` = 'Hasmember'
AND `deletedLinks`.`ObjectId` = '5376' )
but i don't understand why?
The problem here is your sub-select, as you are using it to provide the value of an IN clause, your sub-select should only select the id field, i.e. Transactions.* -> Transactions.id
So you end up with:
...
AND NOT (RIGHT (`addedLinks`.`NewValue`, 4)) IN
SELECT id FROM Transactions AS deletedLinks WHERE
...
The reason for this is that IN requires a list to compare with, so foo IN ( 1,2,3,4,5 ). If your subquery is selecting multiple fields, the resulting list is conceptually a list of lists (AoAs) like, [1, 'a'], [2, 'b'], [3, 'c'] and it's going to complain at you =)
Ah that's so complicated and with subquery... make it simpler, will be much faster
CREATE TEMPORARY TABLE `__del_max`
SELECT `NewValue`, MAX(`id`) as id FROM tickets
WHERE type = 'DeleteLink'
GROUP BY NewValue;
CREATE INDEX _nv ON __del_max(`NewValue`)
SELECT * FROM `tickets`
LEFT OUTER JOIN `__del_max` ON tickets.NewValue = __del_max.NewValue AND __del_max.id > tickets.id
WHERE __del_max.id IS NULL
You can have it in single, big join, but it'd be beneficial to have it in TMP table so you can add an index ;)

Error creating a SP with temporal table inside and subquery

I've created this temporal table in my store procedure, as you can see I have more than 1 records for the same ID:
#tmpTableResults
TmpInstallerID TmpConfirmDate TmpConfirmLocalTime
============== ============== ===================
173 2011-11-08 11:45:50
278 2011-11-04 09:06:26
321 2011-11-08 13:21:35
321 2011-11-08 11:44:54
483 2011-11-08 11:32:00
483 2011-11-08 11:31:59
645 2011-11-04 10:03:15
645 2011-11-04 07:03:15
That is the result of the query to create #tmpTableResults
DECLARE #tmpTableResults TABLE
(
TmpInstallerID int,
TmpConfirmDate date,
TmpConfirmLocalTime time
)
DECLARE #tmpTableQuery VarChar(800)
SET #tmpTableQuery = 'select FxWorkorder.INSTALLERSYSID, FxWorkorder.CONFIRMDATE, FxWorkorder.CONFIRMLOCALTIME from FxWorkorder
join install on FxWorkorder.INSTALLERSYSID = install.sysid
join RouteGroupWorkarea on FxWorkorder.WORKAREAGROUPSYSID = RouteGroupWorkarea.IWORKAREA_ID
join RoutingGroup on RouteGroupWorkarea.IRG_ID = RoutingGroup.IRG_IDENTITY
where FxWorkorder.SCHEDULEDDATE > = #StartDate and FxWorkorder.SCHEDULEDDATE <= #EndDate
and FxWorkorder.Jobstatus <> "Unassign"
and FxWorkorder.Jobstatus <> "Route"
and install.FOXTELCODE <> ""
and FxWorkorder.CONFIRMLOCALTIME is not null
and FxWorkorder.CONFIRMDATE <> ""
group by FxWorkorder.INSTALLERSYSID, FxWorkorder.CONFIRMDATE, FxWorkorder.CONFIRMLOCALTIME
order by FxWorkorder.INSTALLERSYSID, FxWorkorder.CONFIRMDATE, FxWorkorder.CONFIRMLOCALTIME desc '
INSERT INTO #tmpTableResults EXEC(#tmpTableQuery)
I'm creating another query to get data from another table and only the first record from the temporal table for the same INSTALLERSYSID
SELECT RoutingGroup.SDESCRIPTION, FxWorkorder.INSTALLERSYSID, FxWorkOrder.JOBSTATUS, Install.FOXTELCODE,
install.NAME, FxWorkOrder.ScheduledDate,
count(*) as TotalJobs, COUNT(CONFIRMDATE) as ConfirmedJobs,
(select TmpInstallerID, TmpConfirmDate, TmpConfirmLocalTime from #tmpTableResults where TmpInstallerID = FxWorkorder.INSTALLERSYSID)
from FxWorkorder
join install on fxworkorder.INSTALLERSYSID = install.sysid
join RouteGroupWorkarea on FxWorkOrder.WORKAREAGROUPSYSID = RouteGroupWorkarea.IWORKAREA_ID
join RoutingGroup on RouteGroupWorkarea.IRG_ID = RoutingGroup.IRG_IDENTITY
where FxWorkorder.SCHEDULEDDATE > = #StartDate and FxWorkorder.SCHEDULEDDATE <= #EndDate
and FxWorkOrder.Jobstatus <> 'Unassign'
and FxWorkOrder.Jobstatus <> 'Route'
and Install.FOXTELCODE <> ''
group by RoutingGroup.SDESCRIPTION,FxWorkOrder.INSTALLERSYSID, FxWorkOrder.JOBSTATUS, Install.FOXTELCODE,install.NAME, FxWorkOrder.ScheduledDate,FxWorkOrder.WORKAREAGROUPSYSID
When I tried to save the sp I got the error
"Only one expression can be specified in the select list when the subquery is not introduced with EXISTS."
I can't see why I got this error. But if I run the query in sql that works. Can someone see the error?
I don't know how your second query works for you ‘in sql’ (where is that supposed to be? do you mean SSMS = SQL Server Management Studio?), but I'm sure it cannot possibly work in any version of SQL Server that exists at the moment. It's because of this subquery in the SELECT list:
(select TmpInstallerID, TmpConfirmDate, TmpConfirmLocalTime from #tmpTableResults where TmpInstallerID = FxWorkorder.INSTALLERSYSID)
The thing is, every expression in the SELECT clause should be scalar, but this subquery returns a row of more than one value. Even if it's only one row, it is illegal there, because it returns several columns. The subquery in that context should return no more than one value, i.e. it should be one column and the result produced should contain either no rows or just one.
You could try this query instead (although I'm not entirely sure without knowing more details about your schema):
SELECT
RoutingGroup.SDESCRIPTION,
FxWorkorder.INSTALLERSYSID,
FxWorkOrder.JOBSTATUS,
Install.FOXTELCODE,
install.NAME, FxWorkOrder.ScheduledDate,
count(*) as TotalJobs, COUNT(CONFIRMDATE) as ConfirmedJobs,
tmp.TmpInstallerID,
tmp.TmpConfirmDate,
tmp.TmpConfirmLocalTime
from FxWorkorder
join install on fxworkorder.INSTALLERSYSID = install.sysid
join RouteGroupWorkarea on FxWorkOrder.WORKAREAGROUPSYSID = RouteGroupWorkarea.IWORKAREA_ID
join RoutingGroup on RouteGroupWorkarea.IRG_ID = RoutingGroup.IRG_IDENTITY
join #tmpTableResults tmp ON tmp.TmpInstallerID = FxWorkorder.INSTALLERSYSID
where FxWorkorder.SCHEDULEDDATE > = #StartDate
and FxWorkorder.SCHEDULEDDATE <= #EndDate
and FxWorkOrder.Jobstatus <> 'Unassign'
and FxWorkOrder.Jobstatus <> 'Route'
and Install.FOXTELCODE <> ''
group by
RoutingGroup.SDESCRIPTION,
FxWorkOrder.INSTALLERSYSID,
FxWorkOrder.JOBSTATUS,
Install.FOXTELCODE,install.NAME,
FxWorkOrder.ScheduledDate,
FxWorkOrder.WORKAREAGROUPSYSID
tmp.TmpInstallerID,
tmp.TmpConfirmDate,
tmp.TmpConfirmLocalTime
That is, I added one more join, the one to #tmpTableResults, as well as added the columns you were trying to pull to the SELECT clause and to the GROUP BY clause.
Also, if I were you I would consider using short aliases for tables, like this:
SELECT
…
wo.INSTALLERSYSID,
wo.JOBSTATUS,
…
from FxWorkorder wo
join …
That might make your queries more readable.