I'm trying to make an union of selects with a while loop, getting errors - sql

declare #year int
set #year = 2009
while(year <= 2020)
begin
SELECT A.dsformfieldvalue as Instrumento ,
AVG(DATEDIFF(day, B.DTSTARTDATE , C.DTENDDATE)) as TempoMedio ,
'Jul/2009 a Dez/2009' as Periodo
from wfflow_form_field_log A
right join wfflow_execute_task B on A.codflowexecute = B.codflowexecute
right join wfflow_execute_task C on B.codflowexecute = C.codflowexecute
where A.codflow in (326, 439)
and A.codfield = 2498
and B.codtask = 7064
and C.codtask = 7095
and CONVERT(CHAR(4), B.DTSTARTDATE, 120) = #year
and CONVERT(CHAR(4), B.DTSTARTDATE, 100) in ('Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')
set #year = #year + 1
group by A.dsformfieldvalue
union all
end
Basically what I'm trying to do is drastically reduce the number of repeated code, as I have to union a whole bunch of selects. I'm trying with a while loop but it is not working. Any input?

I think what you're trying to do is this
select *
from wfflow_form_field_log A
right join wfflow_execute_task B on
A.codflowexecute = B.codflowexecute
where year(b.dtstartdate) between 2009 and 2020
and month(b.dtstartdate)>=7
and ... -- other filters

Your SQL is syntactically invalid. The general form of a select1 statement is
select <result-columns>
from <from-clause-including-joins>
where <where-criteria>
group by <group-by-criteria>
having <having-criteria>
order by <order-by-criteria>
The general form of union/union all is
<select-statement>
UNION [ALL]
<select-statement>
...
where each select statement returns the same number of columns and the corresponding columns in each select statement are of the same type or of a type implicitly convertible to the type of the column in the 1st select statement.
You've placed your statement
set #year = #year + 1
so that it is a part of the select statement.
Further you've added a UNION ALL with no select statement following.
Fix those problems and you should be good. But...as another answer pointed out, what you're really trying to do is select a range of years. You don't need a union: you just need suitable grouping.
If it was my query, I'd do something like this (if I'm understanding your intent correctly):
declare #dtFrom datetime = '2009-01-01 00:00:00.000' -- start of period
declare #dtThru datetime = '2020-12-31 23:59:59.996' -- end of period
select year = datepart(year,b.dtstartdate) ,
period = case datepart(month,b.dtstartdate) / 6 when 1 then 'Jan-Jun' else 'Jul-Dec' end ,
instrumento = a.dsformfieldvalue ,
tempomento = avg( datediff(day, b.dtstartdate , c.dtenddate ) )
from wfflow_form_field_log a
right join wfflow_execute_task b on A.codflowexecute = B.codflowexecute
and B.codtask = 7064
and b.dtStartDate between #dtFrom and #dtThru
right join wfflow_execute_task d on B.codflowexecute = C.codflowexecute
and C.codtask = 7095
where A.codflow in (326, 439)
and A.codfield = 2498
group by datepart(year,b.dtStartDate) ,
case datepart(month,b.dtstartdate) / 6 when 1 then 'Jan-Jun' else 'Jul-Dec' end ,
A.dsformfieldvalue
order by 1,2,3 -- order by the first 3 columns (the grouping columns)
Easy!

Related

Move Functions from Where Clause to Select Statement

I have a union query that runs abysmally slow I believe mostly because there are two functions in the where clause of each union. I am pretty sure that there is no getting around the unions, but there may be a way to move the functions from the where of each. I won't post ALL of the union sections because I don't think it is necessary as they are all almost identical with the exception of one table in each. The first function was created by someone else but it takes a date, and uses the "frequency" value like "years, months, days, etc." and the "interval" value like 3, 4, 90 to calculate the new "Due Date". For instance, a date of today with a frequency of years, and an interval of 3, would produce the date 4/21/2025. Here is the actual function:
ALTER FUNCTION [dbo].[ReturnExpiration_IntervalxFreq](#Date datetime2,#int int, #freq int)
RETURNS datetime2
AS
BEGIN
declare #d datetime2;
SELECT #d = case when #int = 1 then null-- '12-31-9999'
when #int = 2 then dateadd(day,#freq,#date)
when #int = 3 then dateadd(week,#freq,#date)
when #int = 4 then dateadd(month,#freq,#date)
when #int = 5 then dateadd(quarter,#freq,#date)
when #int = 6 then dateadd(year,#freq,#date)
end
RETURN #d;
The query itself is supposed to find and identify records whose Due Date has past or is within 90 days of the current date. Here is what each section of the union looks like
SELECT
R.RequirementId
, EC.EmployeeCompanyId
, EC.CompanyId
, DaysOverdue =
CASE WHEN
R.DueDate IS NULL
THEN
CASE WHEN
EXISTS(SELECT 1 FROM tbl_Training_Requirement_Compliance RC WHERE RC.EmployeeCompanyId = EC.EmployeeCompanyId AND RC.RequirementId = R.RequirementId AND RC.Active = 1 AND ((DATEDIFF(DAY, R.DueDate, GETDATE()) > -91 OR R.DueDate Is Null ) OR (DATEDIFF(DAY, dbo.ReturnExpiration_IntervalxFreq(TRC.EffectiveDate, R.IntervalId, R.Frequency), GETDATE()) > -91)) OR R.IntervalId IS NULL)
THEN
DateDiff(day,ISNULL(dbo.ReturnExpiration_IntervalxFreq(TRC.EffectiveDate, R.IntervalId, R.Frequency), '12/31/9999'),getdate())
ELSE
0
END
ELSE
DATEDIFF(day,R.DueDate,getdate())
END
,CASE WHEN
EXISTS(SELECT 1 FROM tbl_Training_Requirement_Compliance RC WHERE RC.EmployeeCompanyId = EC.EmployeeCompanyId AND RC.RequirementId = R.RequirementId AND RC.Active=1 AND (GETDATE() > dbo.ReturnExpiration_IntervalxFreq(RC.EffectiveDate, R.IntervalId, R.Frequency) OR R.IntervalId IS NULL))
THEN
CONVERT(VARCHAR(12),dbo.ReturnExpiration_IntervalxFreq(TRC.EffectiveDate, R.IntervalId, R.Frequency), 101)
ELSE
CONVERT(VARCHAR(12),R.DueDate,101)
END As DateDue
FROM
#Employees AS EC
INNER JOIN dbo.tbl_Training_Requirement_To_Position TRP ON TRP.PositionId = EC.PositionId
INNER JOIN #CompanyReqs R ON R.RequirementId = TRP.RequirementId
LEFT OUTER JOIN tbl_Training_Requirement_Compliance TRC ON TRC.EmployeeCompanyId = EC.EmployeeCompanyId AND TRC.RequirementId = R.RequirementId AND TRC.Active = 1
WHERE
NOT EXISTS(SELECT 1
FROM tbl_Training_Requirement_Compliance RC
WHERE RC.EmployeeCompanyId = EC.EmployeeCompanyId
AND RC.RequirementId = R.RequirementId
AND RC.Active = 1
)
OR (
(DATEDIFF(DAY, R.DueDate, GETDATE()) > -91
OR R.DueDate Is Null )
OR (DATEDIFF(DAY, dbo.ReturnExpiration_IntervalxFreq(TRC.EffectiveDate, R.IntervalId, R.Frequency), GETDATE()) > -91))
UNION...
It is supposed to exclude records that either don't exist at all on the tbl_Training_Requirement_Compliance table, or if they do exist, once the frequency an intervals have been calculated, would have a new due date that is within 90 days of the current date. I am hoping that someone with much more experience and expertise in SQL Server can show me a way, if possible, to remove the functions from the WHERE clause and help the performance of this stored procedure.

Delete the records repeated by date, and keep the oldest

I have this query, and it returns the following result, I need to delete the records repeated by date, and keep the oldest, how could I do this?
select
a.EMP_ID, a.EMP_DATE,
from
EMPLOYES a
inner join
TABLE2 b on a.table2ID = b.table2ID and b.ID_TYPE = 'E'
where
a.ID = 'VJAHAJHSJHDAJHSJDH'
and year(a.DATE) = 2021
and month(a.DATE) = 1
and a.ID <> 31
order by
a.DATE;
Additionally, I would like to fill in the missing days of the month ... and put them empty if I don't have that data, can this be done?
I would appreciate if you could guide me to solve this problem
Thank you!
The other answers miss some of the requirement..
Initial step - do this once only. Make a calendar table. This will come in handy for all sorts of things over the time:
DECLARE #Year INT = '2000';
DECLARE #YearCnt INT = 50 ;
DECLARE #StartDate DATE = DATEFROMPARTS(#Year, '01','01')
DECLARE #EndDate DATE = DATEADD(DAY, -1, DATEADD(YEAR, #YearCnt, #StartDate));
;WITH Cal(n) AS
(
SELECT 0 UNION ALL SELECT n + 1 FROM Cal
WHERE n < DATEDIFF(DAY, #StartDate, #EndDate)
),
FnlDt(d, n) AS
(
SELECT DATEADD(DAY, n, #StartDate), n FROM Cal
),
FinalCte AS
(
SELECT
[D] = CONVERT(DATE,d),
[Dy] = DATEPART(DAY, d),
[Mo] = DATENAME(MONTH, d),
[Yr] = DATEPART(YEAR, d),
[DN] = DATENAME(WEEKDAY, d),
[N] = n
FROM FnlDt
)
SELECT * INTO Cal FROM finalCte
ORDER BY [Date]
OPTION (MAXRECURSION 0);
credit: mostly this site
Now we can write some simple query to stick your data (with one small addition) onto it:
--your query, minus the date bits in the WHERE, and with a ROW_NUMBER
WITH yourQuery AS(
SELECT a.emp_id, a.emp_date,
ROW_NUMBER() OVER(PARTITION BY CAST(a.emp_date AS DATE) ORDER BY a.emp_date) rn
FROM EMPLOYES a
INNER JOIN TABLE2 b on a.table2ID = b.table2ID
WHERE a.emp_id = 'VJAHAJHSJHDAJHSJDH' AND a.id <> 31 AND b.id_type = 'E'
)
--your query, left joined onto the cal table so that you get a row for every day even if there is no emp data for that day
SELECT c.d, yq.*
FROM
Cal c
LEFT JOIN yourQuery yq
ON
c.d = CAST(yq.emp_date AS DATE) AND --cut the time off
yq.rn = 1 --keep only the earliest time per day
WHERE
c.d BETWEEN '2021-01-01' AND EOMONTH('2021-01-01')
We add a rownumbering to your table, it restarts every time the date changes and counts up in order of time. We make this into a CTE (or a subquery, CTE is cleaner) then we simply left join it to the calendar table. This means that for any date you don't have data, you still have the calendar date. For any days you do have data, the rownumber rn being a condition of the join means that only the first datetime from each day is present in the results
Note: something is wonky about your question . You said you SELECT a.emp_id and your results show 'VJAHAJHSJHDAJHSJDH' is the emp id, but your where clause says a.id twice, once as a string and once as a number - this can't be right, so I've guessed at fixing it but I suspect you have translated your query into something for SO, perhaps to hide real column names.. Also your SELECT has a dangling comma that is a syntax error.
If you have translated/obscured your real query, make absolutely sure you understand any answer here when translating it back. It's very frustrating when someone is coming back and saying "hi your query doesn't work" then it turns out that they damaged it trying to translate it back to their own db, because they hid the real column names in the question..
FInally, do not use functions on table data in a where clause; it generally kills indexing. Always try and find a way of leaving table data alone. Want all of january? Do like I did, and say table.datecolumn BETWEEN firstofjan AND endofjan etc - SQLserver at least stands a chance of using an index for this, rather than calling a function on every date in the table, every time the query is run
You can use ROW_NUMBER
WITH CTE AS
(
SELECT a.EMP_ID, a.EMP_DATE,
RN = ROW_NUMBER() OVER (PARTITION BY a.EMP_ID, CAST(a.DATE as Date) ORDER BY a.DATE ASC)
from EMPLOYES a INNER JOIN TABLE2 b
on a.table2ID = b.table2ID
and b.ID_TYPE = 'E'
where a.ID = 'VJAHAJHSJHDAJHSJDH'
and year(a.DATE) = 2021
and MONTH(a.DATE) = 1
and a.ID <> 31
)
SELECT * FROM CTE
WHERE RN = 1
Try with an aggregate function MAX or MIN
create table #tmp(dt datetime, val numeric(4,2))
insert into #tmp values ('2021-01-01 10:30:35', 1)
insert into #tmp values ('2021-01-02 10:30:35', 2)
insert into #tmp values ('2021-01-02 11:30:35', 3)
insert into #tmp values ('2021-01-03 10:35:35', 4)
select * from #tmp
select tmp.*
from #tmp tmp
inner join
(select max(dt) as dt, cast(dt as date) as dt_aux from #tmp group by cast(dt as date)) compressed_rows on
tmp.dt = compressed_rows.dt
drop table #tmp
results:

Month Aggregation with 0 for NULL results

I have seen something similar but I can't get this to work:
SELECT
CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY.CALENDAR_MONTH
, CNTRSINTDATA.FACT_QUICKBOOKS_TRANS_TSI.TSI_NOMINAL_CODE
, CNTRSINTDATA.DIM_CNTRS_TSI_COA.TSI_NOMINAL_ACC
, CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY.CNTRS_FIN_YEAR
, SUM (CNTRSINTDATA.FACT_QUICKBOOKS_TRANS_TSI.QB_TRANS_AMOUNT) AS CNTRS_ACC_BUDGET
FROM
CNTRSINTDATA.FACT_QUICKBOOKS_TRANS_TSI
, CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY
, CNTRSINTDATA.DIM_CNTRS_TSI_COA
WHERE
CNTRSINTDATA.FACT_QUICKBOOKS_TRANS_TSI.QB_TRANS_DATE = CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY.CALENDAR_DATE
AND CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY.CNTRS_FIN_YEAR LIKE '2017'
AND CNTRSINTDATA.FACT_QUICKBOOKS_TRANS_TSI.TSI_NOMINAL_CODE = CNTRSINTDATA.DIM_CNTRS_TSI_COA.TSI_NOMINAL_CODE
AND CNTRSINTDATA.FACT_QUICKBOOKS_TRANS_TSI.TSI_NOMINAL_CODE = '6598'
GROUP BY
CNTRSINTDATA.FACT_QUICKBOOKS_TRANS_TSI.TSI_NOMINAL_CODE
, CNTRSINTDATA.DIM_CNTRS_TSI_COA.TSI_NOMINAL_ACC
, CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY.CALENDAR_MONTH
, CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY.CNTRS_FIN_YEAR;`
The above query returns results for:
Feb-17 250
Jul-17 400
Jun-17 654
May-17 654
Oct-17 150
Nov-17 250
Aug-17 250
Sep-17
I need the rest of the months to also come back with zero's as there no transactions on the account that month.
Jan-17 0
Feb-17 250
Mar-17 0
Apr-17 0
Jul-17 400
Jun-17 654
May-17 654
Oct-17 150
Nov-17 250
Aug-17 250
Sep-17 0
Dec-17 0
There is a date table that has all the months as VARCHAR2 against date. Just cant get the right syntax. Can anyone help please?
Let's break that down and make it more manageable.
Firstly, translate that to SQL-92 join syntax and add some aliases
SELECT
dcde.CALENDAR_MONTH
, fqtt.TSI_NOMINAL_CODE
, dctc.TSI_NOMINAL_ACC
, dcde.CNTRS_FIN_YEAR
, SUM (fqtt.QB_TRANS_AMOUNT) AS CNTRS_ACC_BUDGET
FROM
CNTRSINTDATA.FACT_QUICKBOOKS_TRANS_TSI fqtt
INNER JOIN CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY dcde
ON fqtt.QB_TRANS_DATE = dcde.CALENDAR_DATE
INNER JOIN CNTRSINTDATA.DIM_CNTRS_TSI_COA dctc
ON AND fqtt.TSI_NOMINAL_CODE = dctc.TSI_NOMINAL_CODE
WHERE
dcde.CNTRS_FIN_YEAR LIKE '2017'
AND
fqtt.TSI_NOMINAL_CODE = '6598'
GROUP BY
fqtt.TSI_NOMINAL_CODE
, dctc.TSI_NOMINAL_ACC
, dcde.CALENDAR_MONTH
, dcde.CNTRS_FIN_YEAR;
Next, you said that there exists a date record (presumably in CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY), so swap that around
SELECT
dcde.CALENDAR_MONTH
, fqtt.TSI_NOMINAL_CODE
, dctc.TSI_NOMINAL_ACC
, dcde.CNTRS_FIN_YEAR
, SUM (fqtt.QB_TRANS_AMOUNT) AS CNTRS_ACC_BUDGET
FROM
CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY dcde
INNER JOIN CNTRSINTDATA.FACT_QUICKBOOKS_TRANS_TSI fqtt
ON fqtt.QB_TRANS_DATE = dcde.CALENDAR_DATE
INNER JOIN CNTRSINTDATA.DIM_CNTRS_TSI_COA dctc
ON AND fqtt.TSI_NOMINAL_CODE = dctc.TSI_NOMINAL_CODE
WHERE
dcde.CNTRS_FIN_YEAR LIKE '2017'
AND
fqtt.TSI_NOMINAL_CODE = '6598'
GROUP BY
fqtt.TSI_NOMINAL_CODE
, dctc.TSI_NOMINAL_ACC
, dcde.CALENDAR_MONTH
, dcde.CNTRS_FIN_YEAR;
Finally, you don't actually want inner joins because that eliminates tuples where there is no match. In this case, you want a left join because you want to have the left value even if no value exists on the right. You also need to coalesce your sum expression to 0 because (for reasons I cannot fathom), the SQL standard defines the sum of a bunch records only containing null as null.
SELECT
dcde.CALENDAR_MONTH
, fqtt.TSI_NOMINAL_CODE
, dctc.TSI_NOMINAL_ACC
, dcde.CNTRS_FIN_YEAR
, COALESCE(SUM (fqtt.QB_TRANS_AMOUNT), 0) AS CNTRS_ACC_BUDGET
FROM
CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY dcde
LEFT JOIN CNTRSINTDATA.FACT_QUICKBOOKS_TRANS_TSI fqtt
ON fqtt.QB_TRANS_DATE = dcde.CALENDAR_DATE
LEFT JOIN CNTRSINTDATA.DIM_CNTRS_TSI_COA dctc
ON AND fqtt.TSI_NOMINAL_CODE = dctc.TSI_NOMINAL_CODE
WHERE
dcde.CNTRS_FIN_YEAR LIKE '2017'
AND
fqtt.TSI_NOMINAL_CODE = '6598'
GROUP BY
fqtt.TSI_NOMINAL_CODE
, dctc.TSI_NOMINAL_ACC
, dcde.CALENDAR_MONTH
, dcde.CNTRS_FIN_YEAR;
Give that a try and see if it is what you want
#Adam G,
Thank you for your suggestion. I actually got an idea from your commentry and instead created a select statement to get all the months I needed first. After that I joined to a select statement picking up the transactions where months matched and it worked. See below (come table names have changed)
SELECT DISTINCT
CNTRS_DATE_ENTITY.CALENDAR_MONTH
, CNTRS_DATE_ENTITY.CNTRS_FIN_MONTH_POS
, COALESCE (TRANS_DATA.TRANS_AMOUNT,0) AS TRANS_AMOUNT
FROM
CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY CNTRS_DATE_ENTITY
LEFT JOIN
(
SELECT
QBS_TRANS_TSI.QB_NOMINAL_CODE
, TO_CHAR(QBS_TRANS_TSI.QB_TRANS_DATE, 'Mon-YY') AS TRANS_MONTH
, COALESCE (SUM(QBS_TRANS_TSI.QB_TRANS_AMOUNT),0) AS TRANS _AMOUNT
FROM
CNTRSINTDATA.FACT_QBS_TRANS_TSI QBS_TRANS_TSI
WHERE
QBS_TRANS_TSI.TSI_NOMINAL_CODE = '6598'
GROUP BY
QBS_TRANS_TSI.QB_NOMINAL_CODE
, TO_CHAR(QBS_TRANS_TSI.QB_TRANS_DATE, 'Mon-YY')
) TRANS_DATA
ON TRANS_DATA.TRANS_MONTH = CNTRS_DATE_ENTITY.CALENDAR_MONTH
WHERE
CNTRS_DATE_ENTITY.CNTRS_FIN_YEAR LIKE '2017'
ORDER BY
CNTRS_DATE_ENTITY.CNTRS_FIN_MONTH_POS
However the results have all the months but have blanks in the entity columns where the sum is zero. Any ideas on a remedy for this?
Here is one possible solution:
Generate fake empty transactions for the target period
for example using CTE:
declare #StartDate datetime = '20170101'
declare #EndDate datetime = '20171231'
;
with dt as
(
select #StartDate As 'thedate'
union all
select dateadd(month, 1, thedate) from dt where thedate < dateadd(month, -1, #EndDate)
)
select
dt.thedate 'row date',
datename(month,dt.thedate) 'Month',
YEAR(dt.thedate) 'Year',
0 'Amount '
from dt
Now you can add them to the transaction table (like union all) or you can do a left join with filtering on the result of your query.
I hope it helps! 🙂

TSQL how to use if else in Where clause

I want to create a report, the report will have parameter for the user to select
-IsApprovedDate
-IsCatcheDate
I would like to know how to used the if else in the where clause.
Example if the user selects IsApprovedDate the report will lookup based on approved Date else will lookup based on catch date. In my query I will get top10 fish size base on award order weight here is my query.
;WITH CTE AS
(
select Rank() OVER (PARTITION BY c.trophyCatchCertificateTypeId order by c.catchWeight desc ) as rnk
,c.id,c.customerId, Cust.firstName + ' '+Cust.lastName as CustomerName
,CAST(CONVERT(varchar(10),catchWeightPoundsComponent)+'.'+CONVERT(varchar(10),catchWeightOuncesComponent) as numeric(6,2) ) WLBS
,c.catchGirth,c.catchLength,ct.description county
,t.description award--
,c.trophyCatchCertificateTypeId
,s.specificSpecies--
,c.speciesId
from Catches c
INNER JOIN TrophyCatchCertificateTypes t on c.trophyCatchCertificateTypeId = t.id
INNER JOIN Species s on c.speciesId = s.id
INNER JOIN Counties ct on c.countyId = ct.id
INNER JOIN Customers Cust on c.customerId = cust.id
Where c.bigCatchCertificateTypeId is not null
and c.catchStatusId =1
and c.speciesId =1 and c.isTrophyCatch =1
and c.catchDate >= #startDay and c.catchDate<=#endDay
)
Select * from CTE c1
Where rnk <=10
Just use conditional logic for this:
where . . . and
((#IsApprovedDate = 1 and c.ApprovedDate >= #startDay and c.ApprovedDate <= #endDay) or
(#IsCatchDate = 1 and c.catchDate >= #startDay and c.catchDate <= #endDay)
)
EDIT:
I would actually write this as:
where . . . and
((#IsApprovedDate = 1 and c.ApprovedDate >= #startDay and c.ApprovedDate < dateadd(day, 1 #endDay) or
(#IsCatchDate = 1 and c.catchDate >= #startDay and c.catchDate < dateadd(day, 1, #endDay))
)
This is a safer construct because it work when the date values have times and when they do not.
Performance will be much better if you build the WHERE clause dynamically in your code and then execute it.

why does adding the where statement to this sql make it run so much slower?

I have inherited a stored procedure and am having problems with it takes a very long time to run (around 3 minutes). I have played around with it, and without the where clause it actually only takes 12 seconds to run. None of the tables it references have a lot of data in them, can anybody see any reason why adding the main where clause below makes it take so much longer?
ALTER Procedure [dbo].[MissingReadingsReport] #SiteID INT,
#FormID INT,
#StartDate Varchar(8),
#EndDate Varchar(8)
As
If #EndDate > GetDate()
Set #EndDate = Convert(Varchar(8), GetDate(), 112)
Select Dt.FormID,
DT.FormDAte,
DT.Frequency,
Dt.DayOfWeek,
DT.NumberOfRecords,
Dt.FormName,
dt.OrgDesc,
Dt.CDesc
FROM (Select MeterForms.FormID,
MeterForms.FormName,
MeterForms.SiteID,
MeterForms.Frequency,
DateTable.FormDate,
tblOrganisation.OrgDesc,
CDesc = ( COMPANY.OrgDesc ),
DayOfWeek = CASE Frequency
WHEN 'Day' THEN DatePart(dw, DateTable.FormDate)
WHEN 'WEEK' THEN
DatePart(dw, MeterForms.FormDate)
END,
NumberOfRecords = CASE Frequency
WHEN 'Day' THEN (Select TOP 1 RecordID
FROM MeterReadings
Where
MeterReadings.FormDate =
DateTable.FormDate
And MeterReadings.FormID =
MeterForms.FormID
Order By RecordID DESC)
WHEN 'WEEK' THEN (Select TOP 1 ( FormDate )
FROM MeterReadings
Where
MeterReadings.FormDate >=
DateAdd(d
, -4,
DateTable.FormDate)
And MeterReadings.FormDate
<=
DateAdd(d, 3,
DateTable.FormDate)
AND MeterReadings.FormID =
MeterForms.FormID)
END
FROM MeterForms
INNER JOIN DateTable
ON MeterForms.FormDate <= DateTable.FormDate
INNER JOIN tblOrganisation
ON MeterForms.SiteID = tblOrganisation.pkOrgId
INNER JOIN tblOrganisation COMPANY
ON tblOrganisation.fkOrgID = COMPANY.pkOrgID
/*this is what makes the query run slowly*/
Where DateTable.FormDAte >= #StartDAte
AND DateTable.FormDate <= #EndDate
AND MeterForms.SiteID = ISNULL(#SiteID, MeterForms.SiteID)
AND MeterForms.FormID = IsNull(#FormID, MeterForms.FormID)
AND MeterForms.FormID > 0)DT
Where ( Frequency = 'Day'
And dt.NumberofRecords IS NULL )
OR ( ( Frequency = 'Week'
AND DayOfWeek = DATEPART (dw, Dt.FormDate) )
AND ( FormDate <> NumberOfRecords
OR dt.NumberofRecords IS NULL ) )
Order By FormID
Based on what you've already mentioned, it looks like the tables are properly indexed for columns in the join conditions but not for the columns in the where clause.
If you're not willing to change the query, it may be worth it to look into indexes defined on the where clause columns, specially that have the NULL check
Try replacing your select with this:
FROM
(select siteid, formid, formdate from meterforms
where siteid = isnull(#siteid, siteid) and
meterforms.formid = isnull(#formid, formid) and formid >0
) MeterForms
INNER JOIN
(select formdate from datetable where formdate >= #startdate and formdate <= #enddate) DateTable
ON MeterForms.FormDate <= DateTable.FormDate
INNER JOIN tblOrganisation
ON MeterForms.SiteID = tblOrganisation.pkOrgId
INNER JOIN tblOrganisation COMPANY
ON tblOrganisation.fkOrgID = COMPANY.pkOrgID
/*this is what makes the query run slowly*/
)DT
I would be willing to bet that if you moved the Meterforms where clauses up to the from statement:
FROM (select [columns] from MeterForms WHERE SiteID= ISNULL [etc] ) MF
INNER JOIN [etc]
It would be faster, as the filtering would occur before the join. Also, having your INNER JOIN on your DateTable doing a <= down in your where clause may be returning more than you'd like ... try moving that between up to a subselect as well.
Have you run an execution plan on this yet to see where the bottleneck is?
Random suggestion, coming from an Oracle background:
What happens if you rewrite the following:
AND MeterForms.SiteID = ISNULL(#SiteID, MeterForms.SiteID)
AND MeterForms.FormID = IsNull(#FormID, MeterForms.FormID)
...to
AND (#SiteID is null or MeterForms.SiteID = #SiteID)
AND (#FormID is null or MeterForms.FormID = #FormID)