Query Performance too Slow - sql

Im having performance issues with this query. If I remove the status column it runs very fast but adding the subquery in the column section delays way too much the query 1.02 min. How can I modify this query so it runs fast getting the desired data.
The reason I put that subquery there its because I needed the status for the latest activity, some activities have null status so I have to ignore them.
Establishments: 6.5k rows -
EstablishmentActivities: 70k rows -
Status: 2 (Active, Inactive)
SELECT DISTINCT
est.id,
est.trackingNumber,
est.NAME AS 'establishment',
actTypes.NAME AS 'activity',
(
SELECT stat3.NAME
FROM SACPAN_EstablishmentActivities eact3
INNER JOIN SACPAN_ActivityTypes at3
ON eact3.activityType_FK = at3.code
INNER JOIN SACPAN_Status stat3
ON stat3.id = at3.status_FK
WHERE eact3.establishment_FK = est.id
AND eact3.rowCreatedDT = (
SELECT MAX(est4.rowCreatedDT)
FROM SACPAN_EstablishmentActivities est4
INNER JOIN SACPAN_ActivityTypes at4
ON est4.establishment_fk = est.id
AND est4.activityType_FK = at4.code
WHERE est4.establishment_fk = est.id
AND at4.status_FK IS NOT NULL
)
AND at3.status_FK IS NOT NULL
) AS 'status',
est.authorizationNumber,
reg.NAME AS 'region',
mun.NAME AS 'municipality',
ISNULL(usr.NAME, '') + ISNULL(+ ' ' + usr.lastName, '')
AS 'created',
ISNULL(usr2.NAME, '') + ISNULL(+ ' ' + usr2.lastName, '')
AS 'updated',
est.rowCreatedDT,
est.rowUpdatedDT,
CASE WHEN est.rowUpdatedDT >= est.rowCreatedDT
THEN est.rowUpdatedDT
ELSE est.rowCreatedDT
END AS 'LatestCreatedOrModified'
FROM SACPAN_Establishments est
INNER JOIN SACPAN_EstablishmentActivities eact
ON est.id = eact.establishment_FK
INNER JOIN SACPAN_ActivityTypes actTypes
ON eact.activityType_FK = actTypes.code
INNER JOIN SACPAN_Regions reg
ON est.region_FK = reg.code --
INNER JOIN SACPAN_Municipalities mun
ON est.municipality_FK = mun.code
INNER JOIN SACPAN_ContactEstablishments ce
ON ce.establishment_FK = est.id
INNER JOIN SACPAN_Contacts con
ON ce.contact_FK = con.id
--JOIN SACPAN_Status stat ON stat.id = actTypes.status_FK
INNER JOIN SACPAN_Users usr
ON usr.id = est.rowCreatedBy_FK
LEFT JOIN SACPAN_Users usr2
ON usr2.id = est.rowUpdatedBy_FK
WHERE (con.ssn = #ssn OR #ssn = '*')
AND eact.rowCreatedDT = (
SELECT MAX(eact2.rowCreatedDT)
FROM SACPAN_EstablishmentActivities eact2
WHERE eact2.establishment_FK = est.id
)
--AND est.id = 6266
ORDER BY 'LatestCreatedOrModified' DESC

Try moving that 'activiy' query to a Left Join and see if it speeds it up.

I solved the problem by creating a temporary table and creating an index to it, this removed the need of the slow subquery in the select statement. Then I join the temp table as I do with normal tables.
Thanks to all.

Related

Find all SELECT statements without a WHERE clause using a regex

I'm trying to scan my codebase to find all select queries without a where clause using regex. The results will be fed into an IDE or a grep file output, but must contain the full matching queries only.
My biggest challenge is getting the entire statement without the WHERE. The caveats are:
some selects don't have a where but also don't have a FROM
some selects target a database view (always starts with a vw) which don't need a where clause
Here's a sample list of all queries fetched from one file:
'
DECLARE #RowsAffected INT = 0;
INSERT INTO tblInvoice (InvID, OcID, InvTimeStamp)
SELECT DISTINCT OcInvID, OcID, GETDATE() AS InvTimeStamp
FROM tblOrderCost OC WITH(NOLOCK)
INNER JOIN tblVendor WITH(NOLOCK) ON InvVendorID = VendorID AND VendorType = 1 -- 1 for supplier.
INNER JOIN #tmpOpID tmp WITH(NOLOCK) ON tmp.OpID = OcOpID
WHERE id = ' . quote($order_id, NUMERIC);
$sql = ' SELECT rphrpid,rphwho,rphdate,rphnotes,opid
FROM tblReplacementPartHistory (nolock)
INNER JOIN tblReplacementPart (nolock)
ON rphrpid = rpid
INNER JOIN tblOrderProduct (nolock)
ON rpopid = opid
WHERE oporid =' . quote($order_id, NUMERIC)
. 'ORDER BY rphrpid';
select * from table where id = 1;
'
select count()
';
DECLARE #RowsAffected INT = 0;
INSERT INTO tblInvoice (InvID, OcID, InvTimeStamp)
SELECT DISTINCT OcInvID, OcID, GETDATE() AS InvTimeStamp
FROM tblOrderCost OC WITH(NOLOCK)
INNER JOIN tblVendor WITH(NOLOCK) ON InvVendorID = VendorID AND VendorType = 1
INNER JOIN #tmpOpID tmp WITH(NOLOCK) ON tmp.OpID = OcOpID';
$sql = ' SELECT rphrpid,rphwho,rphdate,rphnotes,opid
FROM tblReplacementPartHistory (nolock)
INNER JOIN tblReplacementPart (nolock)
ON rphrpid = rpid
INNER JOIN tblOrderProduct (nolock)
ON rpopid = opid
ORDER BY rphrpid';
SELECT rphrpid,rphwho,rphdate,rphnotes,opid
FROM vwOrder';
select * from tbl;
I tried several variations of regex patterns and the closest I got was finding matches with the WHERE line stripped out. I would like to have the entire match made only if the query does not have a WHERE clause. I tried the following
SELECT(.*)(\s)*FROM(\s|.)+?((?!.*where))(?=(';|";|;))
SELECT\s*(?!.*\s*where|vw(\w)*).*\s*(';|";|;)
SELECT[^;\n]*(?:\n(?![^\n;]*where)[^;\n]*)*\n[\n]*
The work can also be tested in the regex101 sandbox: https://regex101.com/r/jvbLOE/1
What I expect to see, given the sample data, is only three matches
1. SELECT DISTINCT OcInvID, OcID, GETDATE() AS InvTimeStamp
FROM tblOrderCost OC WITH(NOLOCK)
INNER JOIN tblVendor WITH(NOLOCK) ON InvVendorID = VendorID AND VendorType = 1
INNER JOIN #tmpOpID tmp WITH(NOLOCK) ON tmp.OpID = OcOpID';
2. SELECT rphrpid,rphwho,rphdate,rphnotes,opid
FROM tblReplacementPartHistory (nolock)
INNER JOIN tblReplacementPart (nolock)
ON rphrpid = rpid
INNER JOIN tblOrderProduct (nolock)
ON rpopid = opid
ORDER BY rphrpid';
3. select * from tbl;
You can use following regex to match select queries not having a where clause in it based on your example.
/(?!.*where)select.*?;/gis
Regex 101 example:
https://regex101.com/r/XaGXp6/1

Slow SELECT query over DBLink - Oracle

I'm trying to use to following query over an Oracle DBLink to retrieve some data from Oracle EBS to another system.
WITH
--Este CTE ira receber todos os fornecedores
cteallvendors AS (
SELECT DISTINCT
asu.vendor_name,
apv.vendor_id,
ssa.address_line1,
ssa.address_line2,
ssa.address_line3,
ssa.address_line4,
ssa.city,
ssa.state,
ssa.global_attribute10
|| ''
|| ssa.global_attribute11
|| ''
|| ssa.global_attribute12 cnpj
FROM
apps.ap_suppliers#k2prd asu
LEFT JOIN apps.ap_vendors_v#k2prd apv ON apv.vendor_id = asu.vendor_id
LEFT JOIN apps.ap_supplier_sites_all#k2prd ssa ON apv.vendor_id = ssa.vendor_id
AND asu.vendor_id = ssa.vendor_id
)
SELECT
mmt.transaction_id,
cffea.document_type "TIPO_DOCUMENTO",
rt.vendor_id "NUMERO_FORNECEDOR",
vendors.vendor_name "NOME_FORNECEDOR",
cfi.invoice_num "NUMERO_NOTA",
cfi.series "SERIE",
mp.organization_code "OI",
mmt.subinventory_code "SUBINVENTARIO",
msib.segment1 "CODIGO_ITEM",
mmt.transaction_uom "UOM",
msib.description "DESCRICAO",
mmt.creation_date,
rt.transaction_type
FROM
apps.mtl_material_transactions#k2prd mmt
LEFT JOIN apps.rcv_transactions#k2prd rt ON mmt.rcv_transaction_id = rt.transaction_id
LEFT JOIN apps.cll_f189_invoice_lines#k2prd cfil ON rt.organization_id = cfil.organization_id
AND rt.po_line_location_id = cfil.line_location_id
LEFT JOIN apps.cll_f189_invoices#k2prd cfi ON cfil.invoice_id = cfi.invoice_id
LEFT JOIN apps.cll_f189_invoice_types#k2prd cfit ON cfi.invoice_type_id = cfit.invoice_type_id
LEFT JOIN apps.cll_f189_fiscal_entities_all#k2prd cffea ON cfi.entity_id = cffea.entity_id
LEFT JOIN apps.cll_f189_fiscal_operations#k2prd cffo ON cfil.cfo_id = cffo.cfo_id
LEFT JOIN cteallvendors vendors ON rt.vendor_id = vendors.vendor_id
LEFT JOIN apps.mtl_parameters#k2prd mp ON mmt.organization_id = mp.organization_id
LEFT JOIN apps.mtl_system_items_b#k2prd msib ON msib.inventory_item_id = mmt.inventory_item_id
AND msib.organization_id = mmt.organization_id
LEFT JOIN apps.mtl_transaction_types#k2prd mtt ON mmt.transaction_type_id = mtt.transaction_type_id
WHERE
nvl(mmt.attribute1, '0') = '0'
AND ( ( rt.transaction_type = 'DELIVER'
AND cfit.credit_debit_flag = 'D' )
OR ( rt.transaction_type = 'RETURN TO RECEIVING'
AND cfit.credit_debit_flag = 'C' ) )
AND cffo.cfo_code IN (
1101,
2101,
3101
)
AND upper(mtt.transaction_type_name) IN (
'PO RECEIPT',
'RETURN TO VENDOR'
)
ORDER BY
transaction_id
But the execution times are horrible over DBLink.
It usually takes a few milliseconds to run it on the origin database (around 0.500 to one second when a data load is going, this is the usual time), but when i try to run it from the DBLink, it doesn't bring any results. I left it running for over an hour, other queries over the same DBLink work normally, also, other DBLinks work fine as well.
Could someone help me to solve this situation? Is the query poorly optimized or might be an infrastructure issue going on.

Slow insert into but fast select query

A select query with join returns results in less than 1 sec(returns 1000 rows: 600 ms) but insert into temp table or physical table takes 15-16 seconds.
tested io performance : a select or insert into without any joins takes sub-second to write 1000 rows.
tried trace flag 1118
tried adding clustered index on the temp table and do insert into with tablock and maxdop hint.
None of these improved performance.
Thanks for all your comments. 6000 to 20000 rows that need to be inserted every 5 seconds from Kafka...
I get the data from kafka into sql server using table type variable
Pass it as a parameter to stored procedure
Load this data joining with other tables into a temporary table #table
I use the #table to merge the data into application table
Found a workaround that helps me achieve the target turnaround time but dont exactly know the reason for the behaviour. As I mentioned in the problem statement, the bottleneck was writing the resultset of the select statement that joins the table variable with various other tables to the temp table.
I put this into a stored prod and returned the execution of the stored proc to a temp table. Now the insert takes less than 1 sec
SELECT
i.Id AS IId,
df.Id AS dfid,
MAX(CASE
WHEN lp.Value IS NULL and f.pnp = 1 THEN 0
WHEN lp.Value = 0 and f.tzan = 1 and f.pnp = 0 THEN NULL
ELSE lp.Value
END) 'FV',
MAX(lp.TS),
MAX(lp.Eid),
MAX(0+lp.IsDelayedStream)
FROM
f1 f WITH (NOLOCK)
INNER JOIN ft1 ft WITH (NOLOCK) ON f.FeedTypeId = ft.Id
INNER JOIN FeedDataField fdf WITH (NOLOCK)
ON fdf.FeedId = f.Id
INNER JOIN df1 df WITH (NOLOCK)
ON fdf.dfId = df.Id
INNER JOIN ds1 ds WITH (NOLOCK)
ON df.dsid = ds.Id
INNER JOIN dp1 dp WITH (NOLOCK)
ON ds.dpId = dp.Id
INNER JOIN dc1 dc WITH (NOLOCK)
ON dc.dcId = ds.dcId
INNER JOIN i1 i WITH (NOLOCK)
ON f.iId = I.Id
INNER JOIN id1 id WITH (NOLOCK)
ON id.iId = i.Id
INNER JOIN IdentifierType it WITH (NOLOCK)
ON id.ItId = it.Id
INNER JOIN ivw_Tdf tdf WITH(NOEXPAND)
ON tdf.iId = i.Id
INNER JOIN z.dbo.[tlp] lp
ON lp.Ticker = id.Name AND lp.Field = df.SourceName AND
lp.Contributor = dc.Name AND lp.YellowKey = tdf.TextValue
WHERE
ft.Name in ('X', 'Y') AND f.SA = 1
AND dp.Name = 'B' AND (i.Inactive IS NULL OR i.Inactive = 0)
AND it.Name = 'T' AND id.ValidTo = #InfinityDate
AND tdf.SourceName = 'MSD'
AND tdf.ValidTo = #Infinity
GROUP BY i.Id, df.Id
OPTION(MAXDOP 4, OPTIMIZE FOR (#Infinity = '9999-12-31 23:59:59',
#InfinityDate = '9999-12-31))

what is this code doing can not understand it

I am trying to figure out what a query is doing and I just do not understand why it would join its self to its self on multiple occassions highlighted bit is the piece i am talking about?
its the part that starts with the [SupplyStatusUpdated] = COALESCE( gas supply and gas supply history
USE [CRM];
SELECT gs.GasSupplyID,
[Account ID] = acc.AccountID,
[GMSReference] = cast(gms.SiteRefNum as varchar),
[AccountNumber] = cast(gms.AccountNum as varchar),
[Organisation Name] = prf.[Name] ,
con.DeclaredDate,
[Contract Date] = CAST(con.ContractDate AS DATE),
[Contract Version] = cv.Name,
[Contract Status] = cs.Name,
loa.ContractEndDate [LOA CED],
gs.CurrentSupplierEndDate [PrevSupplierEndDate],
loa.ContractEndDate,
con.StartDate,
[Supply Status] = gss.Name,
[SupplyStatusUpdated] = COALESCE(
(
SELECT TOP 1 MAX(gsh2.CreatedDate)
FROM GasSupply gs2
INNER JOIN GasSupplyHistory gsh2
ON gsh2.GasSupplyFK = gs2.GasSupplyID
WHERE gsh2.EventFK = 2
AND gsh2.Notes LIKE '%Gas Supply Status (%'
AND gsh2.GasSupplyFK = gs.GasSupplyID
GROUP BY gsh2.GasSupplyFK
),
(
SELECT TOP 1 MAX(gsh3.CreatedDate)
FROM GasSupplyHistory gsh3
INNER JOIN (
SELECT gsh4.GasSupplyFK, MAX(gsh4.EventFK) AS [MaxEvent]
FROM GasSupplyHistory gsh4
WHERE gsh4.GasSupplyFK = gs.GasSupplyID
GROUP BY gsh4.GasSupplyFK
HAVING MAX(gsh4.EventFK) = 1
) dt
ON dt.GasSupplyFK = gsh3.GasSupplyFK
)
),
[EAC] = gs.EstimatedAnnualConsumption,
[PreviousSupplier] = r.name,
[Branch] = b.name,
[LeadSource] = ls.name,
gs.UnitPrice,
gs.StandingCharge,
gs.WholesalePrice,
COALESCE(deal.weeknumber,DATEPART(ISOWK, con.[ContractDate])) AS [Week]
FROM acc.Account acc
INNER JOIN [Profile] prf
ON acc.ProfileFK = prf.ProfileID
INNER JOIN [Contract] con
ON acc.AccountID = con.AccountFK
INNER JOIN [loacontract] lo
ON lo.ContractFK = con.ContractID
LEFT join [LeadSource] ls
ON ls.LeadSourceID = con.LeadSourceFK
INNER JOIN [ContractStatus] cs
ON cs.ContractStatusID = con.ContractStatusFK
INNER JOIN [ContractVersion] cv
ON cv.ContractVersionID = con.ContractVersionFK
INNER JOIN GasSupply gs
ON gs.ContractFK = con.ContractID
INNER JOIN GasSupplyStatus gss
ON gss.GasSupplyStatusID = gs.GasSupplyStatusFK
LEFT JOIN Deal deal
ON deal.ContractFK = con.ContractID
OUTER APPLY GetGMSReferenceNumbers(con.ContractID) gms
LEFT JOIN Branch b
ON b.BranchID = deal.BranchFK
LEFT JOIN DealBroker bro
ON bro.DealFK = deal.DealID
AND bro.BrokerTypeFK = 36
LEFT JOIN Person p
ON p.PersonID = bro.EmployeeFK
LEFT JOIN Reseller r
ON r.ResellerID = gs.ResellerFK
LEFT JOIN (
SELECT l.contractfk,
MIN(l.contractenddate)[ContractEndDate]
FROM CRM.[contract].LOAContract l
GROUP BY l.ContractFK
) loa
ON loa.ContractFK = con.ContractID
WHERE acc.AccountID not in (
select AccountFK
from AccountOption
where OptionFK=9
)
AND cast(gms.SiteRefNum as varchar) IS NULL
COALESCE(Something, SomethingElse) says if the first argument is NULL, return the second argument (note that you can have more than 2 args and it'll just keep going down the list).
As such it's running the first sub-query, and if the result is NULL, returning the result of the second query. Why exactly is your business logic, which we can't answer :-)
(MSDN link on Coalesce)
COALESCE returns the first non-null value it finds, so if the GasSupply query returns a result it will use that: if that returns null it will see if the GasSupplyHistory query returns a result, and if so use that. If both queries return null then SupplyStatusUpdated will be null.
It joins to itself to get historical records from the same source as where it gets the current record.
Representation of historical data is is one common reason for using a BigData/NoSQL database instead of a SQL/Relational database.

Making join condition more exclusive causes query to hang

Sorry for the wall of SQL, but I am having some problems with the query below. It seems to never finish executing (it runs for a few minutes, then I kill it). The weird thing is that if I change the join condition for the StudentTestsPre table from TestInstances.fkSchoolYearID = (TestInstancesPre.fkSchoolYearID + 1) to TestInstances.fkSchoolYearID > TestInstancesPre.fkSchoolYearID, then the query returns instantly. How could using a more exclusive join condition cause my query to hang? Seems like that should make the query faster, if anything.
Any ideas?
SELECT *
FROM TestInstances
INNER JOIN StudentTests on StudentTests.fkTestInstanceID = TestInstances.pkTestInstanceID
AND StudentTests.pkStudentTestID IN (SELECT * FROM #tempTests)
INNER JOIN TestInstances TestInstancesPre ON TestInstances.fkSchoolYearID = (TestInstancesPre.fkSchoolYearID + 1)
AND TestInstancesPre.fkTestTypeID = 1 AND TestInstances.fkTestTypeID = 1
INNER JOIN StudentTests StudentTestsPre on StudentTestsPre.fkTestInstanceID = TestInstancesPre.pkTestInstanceID
AND StudentTests.fkStudentID = StudentTestsPre.fkStudentID
INNER JOIN StudentScores_Subject s ON s.fkStudentTestID = StudentTests.pkStudentTestID
AND s.fkTest_SubjectID IN (SELECT pkTestSubjectID FROM MM_Test_Subjects WHERE fkCSTStrandID IN (SELECT number FROM itot(#strAcceptableStrands, N',')) AND fkTestTypeID = 1)
AND s.fkScoreTypeID = 3
INNER JOIN StudentScores_Subject sPre ON sPre.fkStudentTestID = StudentTestsPre.pkStudentTestID
AND sPre.fkTest_SubjectID IN (SELECT pkTestSubjectID FROM MM_Test_Subjects WHERE fkCSTStrandID IN (SELECT number FROM itot(#strAcceptableStrands, N',')) AND fkTestTypeID = 1)
AND sPre.fkScoreTypeID = 3
INNER JOIN MM_Test_PL_SS_Ranges r ON r.fkTest_SubjectID = s.fkTest_SubjectID
AND r.fkSchoolYearID = TestInstances.fkSchoolYearID
AND r.fkTestTypeID = TestInstances.fkTestTypeID
AND (r.fkGradeID = StudentTests.fkGradeID OR r.fkGradeID = 99)
INNER JOIN MM_Test_PL_SS_Ranges rPre ON rPre.fkTest_SubjectID = sPre.fkTest_SubjectID
AND rPre.fkSchoolYearID = TestInstancesPre.fkSchoolYearID
AND rPre.fkTestTypeID = TestInstancesPre.fkTestTypeID
AND (rPre.fkGradeID = StudentTestsPre.fkGradeID OR rPre.fkGradeID = 99)
INNER JOIN StudentScores_Subject s2 ON s2.fkStudentTestID = StudentTests.pkStudentTestID
AND s2.fkTest_SubjectID = s.fkTest_SubjectID
AND s2.fkScoreTypeID = 2
INNER JOIN StudentScores_Subject sPre2 ON sPre2.fkStudentTestID = StudentTestsPre.pkStudentTestID
AND sPre2.fkTest_SubjectID = sPre.fkTest_SubjectID
AND sPre2.fkScoreTypeID = 2
INNER JOIN Students on Students.pkStudentID = StudentTests.fkStudentID
thanks for the help!
For SO, here's the above script with alternative formatting & short aliases:
SELECT *
FROM TestInstances
INNER JOIN StudentTests st
ON st.fkTestInstanceID = ti.pkTestInstanceID
AND st.pkStudentTestID IN (SELECT * FROM #tempTests)
INNER JOIN TestInstances tiPre
ON ti.fkSchoolYearID = (tiPre.fkSchoolYearID + 1)
AND tiPre.fkTestTypeID = 1 AND ti.fkTestTypeID = 1
INNER JOIN StudentTests stPre
ON stPre.fkTestInstanceID = tiPre.pkTestInstanceID
AND st.fkStudentID = stPre.fkStudentID
INNER JOIN StudentScores_Subject s
ON s.fkStudentTestID = st.pkStudentTestID
AND s.fkTest_SubjectID IN (
SELECT pkTestSubjectID
FROM MM_Test_Subjects
WHERE fkCSTStrandID IN (
SELECT number FROM itot(#strAcceptableStrands, N','))
AND fkTestTypeID = 1)
AND s.fkScoreTypeID = 3
INNER JOIN StudentScores_Subject sPre
ON sPre.fkStudentTestID = stPre.pkStudentTestID
AND sPre.fkTest_SubjectID IN (
SELECT pkTestSubjectID
FROM MM_Test_Subjects
WHERE fkCSTStrandID IN (
SELECT number FROM itot(#strAcceptableStrands, N','))
AND fkTestTypeID = 1)
AND sPre.fkScoreTypeID = 3
INNER JOIN MM_Test_PL_SS_Ranges r
ON r.fkTest_SubjectID = s.fkTest_SubjectID
AND r.fkSchoolYearID = ti.fkSchoolYearID
AND r.fkTestTypeID = ti.fkTestTypeID
AND (r.fkGradeID = st.fkGradeID OR r.fkGradeID = 99)
INNER JOIN MM_Test_PL_SS_Ranges rPre
ON rPre.fkTest_SubjectID = sPre.fkTest_SubjectID
AND rPre.fkSchoolYearID = tiPre.fkSchoolYearID
AND rPre.fkTestTypeID = tiPre.fkTestTypeID
AND (rPre.fkGradeID = stPre.fkGradeID OR rPre.fkGradeID = 99)
INNER JOIN StudentScores_Subject s2
ON s2.fkStudentTestID = st.pkStudentTestID
AND s2.fkTest_SubjectID = s.fkTest_SubjectID
AND s2.fkScoreTypeID = 2
INNER JOIN StudentScores_Subject sPre2
ON sPre2.fkStudentTestID = stPre.pkStudentTestID
AND sPre2.fkTest_SubjectID = sPre.fkTest_SubjectID
AND sPre2.fkScoreTypeID = 2
INNER JOIN Students
ON Students.pkStudentID = st.fkStudentID
Take a look at your execution plan. My guess is that doing the calculation in the join aka (TestInstancesPre.fkSchoolYearID + 1) is causing indexes not be used correctly. An easy way to test this would be to change your join to:
TestInstances.fkSchoolYearID = TestInstancesPre.fkSchoolYearID
I have seen performance go way down when doing funky stuff in a join. Things like:
ON t1.column1 = ISNULL(t2.myColumn, 1)
I believe this is because the query becomes non-sargable. Take a look at this SO post for more details on that.
By having a calculation in your comparison, it can invalidate the use of an index. This usually happens when the datatype of the calculation result is different from the datatype of the column being indexed. Sometimes the cost of calculation is large if it has to be repeated enough times (eg from lots of joins). One solution is to store the calculated value in a special column, eg:
CREATE TABLE TestInstances (
...
nextSchoolYearID int);
And use a trigger or logic to maintain nextSchoolYearID = fkSchoolYearID + 1, then use
ON TestInstances.fkSchoolYearID = TestInstancesPre.nextSchoolYearID)
Also, you have AND StudentTests.pkStudentTestID IN (SELECT * FROM #tempTests) in the first join's on clause, but the values in #tempTests are not related to either table.
Try moving that predicate to a where clause at the end, ie:
SELECT
...
WHERE StudentTests.pkStudentTestID IN (SELECT * FROM #tempTests)
Doing this means SELECT * FROM #tempTests will only get executed once, instead of being executed for every row combination of TestInstances and StudentTests.