SQL LEFT JOIN Query reporting with optional parameters - sql

I'm running a query that generates a report for an inventory system. The query takes in 6 parameters, 3 of which are optional. The query works fine if all the parameters are entered, however I can't get the query to operate correctly when one or more optional parameters are omitted.
The important parameters are #AccountNumber, #Branch, #Department, and #OrderBy. The #AccountNumber parameter is required while the other three are optional. If #Branch is omitted I would like the report to show all of the inventory in that account. Similarly, If #Department is omitted but #Branch is present I would like to show all of the inventory in that account's branch. If all three parameters are present then it will show all of the inventory in that account's branch and department. If #OrderBy is omitted then the report orders the inventory by account number ordered ascending by default. The query that I'm using is below:
USE database;
GO
CREATE PROCEDURE RetrievedList
#AccountNumber int,
#Branch nvarchar(50),
#Department nvarchar(50),
#StartDate date,
#EndDate date,
#OrderBy nvarchar(10)
AS
IF #OrderBy = 'Locator'
BEGIN
SELECT [Container].[Acct] AS [Account]
...
FROM [File] LEFT JOIN [Container]
ON [File].[BoxID] = [Container].[BoxID]
WHERE [Container].[Acct] = #AccountNumber
AND [Container].[Branch] = #Branch
AND [Container].[Dept] = #Department
AND [File].[Out_Date] IS NOT NULL
AND [File].[Out_Date] BETWEEN #StartDate AND #EndDate
ORDER BY [Container].[Loc];
END
ELSE IF #OrderBy = 'Title'
BEGIN
SELECT [Container].[Acct] AS [Account]
...
FROM [File] LEFT JOIN [Container]
ON [File].[BoxID] = [Container].[BoxID]
WHERE [Container].[Acct] = #AccountNumber
AND [Container].[Branch] = #Branch
AND [Container].[Dept] = #Department
AND [File].[Out_Date] IS NOT NULL
AND [File].[Out_Date] BETWEEN #StartDate AND #EndDate
ORDER BY [File].[Title1];
END
ELSE
BEGIN
SELECT [Container].[Acct] AS [Account]
...
FROM [File] LEFT JOIN [Container]
ON [File].[BoxID] = [Container].[BoxID]
WHERE [Container].[Acct] = #AccountNumber
AND [Container].[Branch] = #Branch
AND [Container].[Dept] = #Department
AND [File].[Out_Date] IS NOT NULL
AND [File].[Out_Date] BETWEEN #StartDate AND #EndDate
ORDER BY [Container].[Acct], [Container].[Branch], [Container].[Dept], [Container].[BoxNo], [File].[Title1];
END
GO
Keep in mind that I can't use the WHERE [Container].[Branch] = #Branch OR [Container].[Branch] = NULL approach because of the LEFT JOIN; it returns six times the records that are expected.

My typical pattern for this issue is using the IsNull operator like this:
AND [Container].[Branch] = IsNull(#Branch, [Container].[Branch])
It will compare against the parameter if passed; otherwise, it'll compare the column to itself.

#bassrek is right you can compare against itself for that matter you can create a case expression in the JOIN conditions and make things whatever value you really want. One more thing to note though if the only thing that is changing in your query is the order by why not write a series of case statments in your order by to do the ordering you desire instead of copying the same query 3 times and then changing the order by. It will make it easier to maintain your business logic should changes need to be made.
another method instead of having to compare against itself instead for performance as Sean suggests could be to use the IN operator and deal with the null:
ISNULL(#Branch,'') IN ([Container].[Branch],'')
for Example you could do the following to make #Branch, #Department, and #OrderBy optional and combine all 3 selects into a single statement:
SELECT [Container].[Acct] AS [Account]
...
FROM [File] LEFT JOIN [Container]
ON [File].[BoxID] = [Container].[BoxID]
WHERE [Container].[Acct] = #AccountNumber
AND ISNULL(#Branch,'') IN ([Container].[Branch],'')
AND ISNULL(#Department,'') IN ([Container].[Dept],'')
AND [File].[Out_Date] IS NOT NULL
AND [File].[Out_Date] BETWEEN #StartDate AND #EndDate
ORDER BY
CASE
WHEN ISNULL(#OrderBy,'') = 'Locator' THEN [Container].[Loc]
WHEN ISNULL(#OrderBy,'') = 'Title' THEN [File].[Title1]
ELSE [Container].[Acct]
END
,CASE
WHEN LEN(ISNULL(#OrderBy,'')) <> 0 THEN '1'
ELSE [Container].[Branch]
END
,CASE
WHEN LEN(ISNULL(#OrderBy,'')) <> 0 THEN '1'
ELSE [Container].[Dept]
END
,CASE
WHEN LEN(ISNULL(#OrderBy,'')) <> 0 THEN '1'
ELSE [Container].[BoxNo]
END
,CASE
WHEN LEN(ISNULL(#OrderBy,'')) <> 0 THEN '1'
ELSE [File].[Title1]
END

Related

How to inject condition dynamically in sql query based on condition?

I have a stored procedure that currently returns calculates statistics for employee to show on dashboard to track employee sale performance for individual employee.
Now, I am modifying that stored procedure to return the statistics to see the sales performance per department.
#EmployeeId int
#DepartmentId int,
SET #NewLeadForSlaes = (SELECT SUM(SaleStats)
FROM SaleSummary AS SS (nolock)
INNER JOIN CustomerSale AS CS ON SS.SaleId = CS.SaleId
WHERE CS.CityId = #CityId
AND CS.EmployeeId = #EmployeeId
AND SS.Type = 'Lead'
AND SS.Name = 'Sale'
AND (DATEFROMPARTS(SS.SaleYear, SS.SaleMonth, 1) >= #FromDate
AND DATEFROMPARTS(SS.SaleYear, SS.SaleMonth, 1) < #ToDate));
So when it will for EmployeeId, DepartmentId will be null and vice versa.
This is the condition for employee:
CS.EmployeeId = #EmployeeId
This is the condition for department:
CS.DepartmentId = #DepartmentId
How do I inject the condition dynamically but still keeps 1 query?
Give this a Try, Put an ISNULL as I have done below. Since You have already mentioned in your question that the #EmployeeId will be NULL for Department and Vice versa, a Simple ISNULL or a COALESCE should do the job for you
DECLARE
#EmployeeId int,
#DepartmentId int
SET #NewLeadForSlaes = (
SELECT
SUM(SaleStats)
FROM SaleSummary AS SS (nolock)
INNER JOIN CustomerSale AS CS
ON SS.SaleId = CS.SaleId
WHERE CS.CityId = #CityId
AND CS.EmployeeId = #EmployeeId
AND SS.Type = 'Lead'
AND SS.Name = 'Sale'
AND DATEFROMPARTS(SS.SaleYear, SS.SaleMonth, 1) >= #FromDate
AND DATEFROMPARTS(SS.SaleYear, SS.SaleMonth, 1) < #ToDate
AND CS.EmployeeId = ISNULL(#EmployeeId,CS.EmployeeId) -- Pass #EmployeeId as NULL for DepartmentId
AND CS.DepartmentId = ISNULL(#DepartmentId,CS.DepartmentId) -- Pass #DepartmentId as NULL for Employee
);
This is some what of a stab in the dark, if I am honest, but what I think you want is an OR which checks that value matches the value of the column while the other parameter is NULL. I also make some changes to the WHERE, such as making it SARGable (though really you should be adding a date column). I remove the NOLOCK, as I doubt you need it too.
DECLARE #EmployeeId int, --oddly the comma was missing here, but
#DepartmentId int;--, --there was a trailing comma here.
SELECT #NewLeadForSlaes = SUM(SaleStats) --Should this not be #NewLeadForSales? You're also missing the DECLARE
FROM dbo.SaleSummary AS SS --(NOLOCK) --Is there a good reason you're using the NOLOCk hint?
INNER JOIN CustomerSale AS CS ON SS.SaleId = CS.SaleId
WHERE CS.CityId = #CityId
AND ((CS.EmployeeId = #EmployeeId AND #DepartmentId IS NULL)
OR (CS.DepartmentId = #DepartmentId AND #EmployeeId IS NULL))
AND SS.Type ='Lead'
AND SS.Name ='Sale'
--This is SARGable but it's messy. Add a date (and time?) column to your table
AND ((SS.SaleYear = DATEPART(YEAR, #FromDate) AND SS.SaleMonth >= DATEPART(MONTH, #FromDate))
OR (SS.SaleYear > DATEPART(YEAR, #FromDate) AND SS.SaleYear < DATEPART(YEAR, #ToDate))
OR (SS.SaleYear = DATEPART(YEAR, #ToDate) AND SS.SaleMonth <= DATEPART(MONTH, #ToDate)))
OPTION (RECOMPILE);

Add column name to a variable and use it in later calculation in WHERE clause

I have a problem. I need to determine the name of the column under which the calculations will continue. So I wrote a select:
DECLARE #column VARCHAR(MAX)
DECLARE #ColumnA VARCHAR(MAX)
DECLARE #ColumnB VARCHAR(MAX)
SET #ColumnA = 'RegistrationDate'
SET #ColumnB = 'EntryDate'
SET #column = CASE
WHEN CONVERT(DATE,GETDATE()) NOT IN (
'2021-08-04','2021-08-05','2021-08-06','2021-08-07','2021-08-08','2021-08-09','2021-08-10','2021-09-07','2021-09-08','2021-09-09','2021-09-10','2021-09-11',
'2021-09-12','2021-09-13','2021-10-05','2021-10-06','2021-10-07','2021-10-08','2021-10-09','2021-10-10','2021-10-11','2021-11-09','2021-11-10','2021-11-11','2021-11-12','2021-11-13','2021-11-14','2021-11-15','2021-12-07',
'2021-12-08','2021-12-09','2021-12-10','2021-12-11','2021-12-12','2021-12-13'
) THEN
QUOTENAME(#Column)
ELSE
QUOTENAME(#ColumnB)
END
SELECT #column
which returns me [RegistrationDate] or [EntryDate] and stores this in variable #column. Now, when I know under which column should I calculate, I want to insert this variable #column in to my main select one of the WHERE clause:
DECLARE #column VARCHAR(MAX)
DECLARE #ColumnA VARCHAR(MAX)
DECLARE #ColumnB VARCHAR(MAX)
SET #ColumnA = 'RegistrationDate'
SET #ColumnB = 'EntryDate'
SET #column = CASE
WHEN CONVERT(DATE,GETDATE()) NOT IN (
'2021-08-04','2021-08-05','2021-08-06','2021-08-07','2021-08-08','2021-08-09','2021-08-10','2021-09-07','2021-09-08','2021-09-09','2021-09-10','2021-09-11',
'2021-09-12','2021-09-13','2021-10-05','2021-10-06','2021-10-07','2021-10-08','2021-10-09','2021-10-10','2021-10-11','2021-11-09','2021-11-10','2021-11-11','2021-11-12','2021-11-13','2021-11-14','2021-11-15','2021-12-07',
'2021-12-08','2021-12-09','2021-12-10','2021-12-11','2021-12-12','2021-12-13'
) THEN
QUOTENAME(#Column)
ELSE
QUOTENAME(#ColumnB)
END
SELECT
CASE WHEN final.Branch IS NULL THEN 'Total'
ELSE final.Branch
END AS 'Branch',
final.TR
FROM
(
SELECT
CASE
WHEN main.BRANCHNO = 1 THEN 'One'
WHEN main.BRANCHNO = 2 THEN 'Two'
WHEN main.BRANCHNO = 3 THEN 'Three'
WHEN main.BRANCHNO = 4 THEN 'Four'
WHEN main.BRANCHNO = 5 THEN 'Five'
WHEN main.BRANCHNO = 6 THEN 'Six'
END AS 'Branch',
COUNT(*) AS 'TR'
FROM
(
SELECT
*
FROM
[TABLE]
WHERE
Status = 100
AND
BRANCHNO IN (1,2,3,4,5,6)
AND
Type = 'TR'
AND
**#column** = CONVERT(DATE, CASE WHEN DATENAME(dw, getdate()) = 'Monday' THEN getdate()-3 ELSE getdate()-1 END
)
) AS main
GROUP BY
main.BRANCHNO WITH ROLLUP
) AS final
But when I execute query it returns me an error:
Msg 241, Level 16, State 1, Line 11 Conversion failed when converting
date and/or time from character string.
I imagined everything very simple: I put a column name into a variable, and then, that name placed at the beginning of the WHERE clause will be recognized as the column name and then *= CONVERT(DATE, CASE WHEN DATENAME(dw, getdate()) etc will do all work.
But that did not happen. Maybe someone knows why and maybe they know how to solve this task?
You can't use a variable to reference a column name. #column is just a piece of data, which just so happens to contain a column name as a string, but it's still just a string, not actually a reference to a column in a table.
Some options you have seem to be...
AND CASE #column WHEN 'RegistrationDate' THEN RegistrationDate
WHEN 'EntryDate' THEN EntryDate
END
=
CONVERT(DATE, CASE WHEN DATENAME(dw, getdate()) = 'Monday' THEN getdate()-3 ELSE getdate()-1 END)
Or, have two queries which only differ in the column being referenced...
IF (#column = 'RegistrationDate')
<query1>
ELSE IF (#column = 'EntryDate')
<query2>
Or "Dynamic SQL" where you build up a new string with your SQL code and execute that by call sp_executesql (assuming this is SQL Server, which it appears to be).
I recommend reading this : https://www.sommarskog.se/dyn-search.html
EDIT: A pure SQL alternative, assuming SQL Server
DECLARE #mode INT = CASE
WHEN CONVERT(DATE,GETDATE()) NOT IN (
'2021-08-04','2021-08-05','2021-08-06','2021-08-07','2021-08-08','2021-08-09','2021-08-10','2021-09-07','2021-09-08','2021-09-09','2021-09-10','2021-09-11',
'2021-09-12','2021-09-13','2021-10-05','2021-10-06','2021-10-07','2021-10-08','2021-10-09','2021-10-10','2021-10-11','2021-11-09','2021-11-10','2021-11-11','2021-11-12','2021-11-13','2021-11-14','2021-11-15','2021-12-07',
'2021-12-08','2021-12-09','2021-12-10','2021-12-11','2021-12-12','2021-12-13'
) THEN
0
ELSE
1
END;
DECLARE #filter_date DATE = CONVERT(DATE, CASE WHEN DATENAME(dw, getdate()) = 'Monday' THEN getdate()-3 ELSE getdate()-1 END;
WITH
source AS
(
SELECT
*
FROM
[TABLE]
WHERE
Status = 100
AND BRANCHNO IN (1,2,3,4,5,6)
AND Type = 'TR'
),
filtered_source AS
(
SELECT 0 AS mode, * FROM source WHERE RegistrationDate = #filter_date
UNION ALL
SELECT 1 AS mode, * FROM source WHERE EntryDate = #filter_date
)
SELECT
COALESCE(
CASE
WHEN BRANCHNO = 1 THEN 'One'
WHEN BRANCHNO = 2 THEN 'Two'
WHEN BRANCHNO = 3 THEN 'Three'
WHEN BRANCHNO = 4 THEN 'Four'
WHEN BRANCHNO = 5 THEN 'Five'
WHEN BRANCHNO = 6 THEN 'Six'
END,
'Total'
)
AS 'Branch',
COUNT(*) AS 'TR'
FROM
filtered_source
WHERE
mode = #mode
GROUP BY
GROUPING SETS (
(mode),
(mode, BRANCHNO)
);
By always including mode in the GROUPING SETS, the optimiser might be able to yield a better execution plan for the two scenarios.
Still read the link given above though, at the very least to understand why this is necessary, or perhaps why it doesn't quite manage to yield the best execution plan.

Passing parameter to divide field value to stored procedure makes it slow

Recently I made a change to my stored procedure to pass a parameter value to divide the value of one of the fields... and stored procedure became very slow. It was taking 1 minute to run 600 recs and now its taking 8-9 minutes to run same results. Could you please help improve this little change?
I only added the following line in the select list
(nullif(x.tardies, 0) / #addtardies) addtardies
Here's the complete code:
ALTER PROCEDURE [dbo].[z_testCalc]
(
#calendarID int,
#grade varchar(3),
#AbsType varchar(1),
#Tardies varchar(1),
#startDate smallDateTime,
#endDate smallDateTime,
#TeamActivity varchar(50),
#Percent VARCHAR(10),
#AddTardies int
)
AS
BEGIN
SET NOCOUNT ON;
select distinct
x.test1,
x.test2,
x.AbsType,
x.UnexAbs,
x.ExAbs,
x.Tardies,
mp.meetings,
round((1 - cast(x.UnexAbs as decimal(6,3))/cast(mp.meetings as decimal(6,3))) * 100,1) percentPres,
**(nullif(x.tardies, 0) / #addtardies) addtardies,**
x.endDate
from
(SELECT DISTINCT
sch.test1,
p.test1,
case when #AbsType = 'T' then 'Unexc, Exc' when #AbsType = 'U' then 'Unexc' else 'Exc' end 'AbsType',
sum(case when COALESCE(x.status, a.status) = 'A' and CASE WHEN a.excuseID IS NOT NULL THEN x.excuse ELSE a.excuse END = 'U' then 1 else 0 end) 'UnexAbs',
sum(case when COALESCE(x.status, a.status) = 'A' and CASE WHEN a.excuseID IS NOT NULL THEN x.excuse ELSE a.excuse END = 'E' then 1 else 0 end) 'ExAbs',
sum(case when COALESCE(x.status, a.status) = 'T' then 1 else 0 end) 'Tardies',
ros.endDate
FROM
test1 a WITH (NOLOCK)
Thank you in advance.
This doesn't look to me like a likely scenario for huge parameter sniffing issues. Still, when introducing a parameter results in bizarre slowdowns like you describe, I always like to rule it out.
Try adding a line that assigns your parameter to a local variable, and then use the local variable, not the parameter, in your query.
so put this at the top, before the query...
DECLARE #AddTardies_lcl int
SET #AddTardies_lcl = #AddTardies
and then use #AddTardies_lcl in your new logic.
(nullif(x.tardies, 0) / #AddTardies_lcl) addtardies
hope it helps!

Dupe: Each GROUP BY expression must contain at least one column that is not an outer reference

I am getting the following errors when I am trying to create the following stored procedure.
Stored Procedure:
CREATE PROCEDURE spReport
#rLevel nvarchar(50),
#aSelected nvarchar(64),
#wType nvarchar(20),
#dSelected nvarchar(20),
#StartDate smalldatetime,
#EndDate smalldatetime
AS
BEGIN
SET NOCOUNT ON;
declare #total float
if (#dSelected = null)
begin
select #total = sum(total) from bs where #rLevel = CHAR(39)+ #aSelected+CHAR(39)
and wtype = '' + #wType + '' and bdate between CHAR(39)+#StartDate+CHAR(39) and CHAR(39)+#EndDate+CHAR(39)
select #rLevel, bdays, str(sum(total*100/#total ),38,2) as percentages, sum(total)as total1
from bs
where #rLevel= CHAR(39)+#aSelected+CHAR(39) and wtype = CHAR(39)+#wType+CHAR(39) and
bdate between CHAR(39)+#StartDate+CHAR(39) and CHAR(39)+#EndDate+CHAR(39)
group by #rLevel, bdays
order by
CASE #rLevel
WHEN 'r' THEN r
WHEN 'D' THEN D
WHEN 'S' THEN S
END
,bdays
end
END
Error:
Each GROUP BY expression must contain at least one column that is not an outer reference.
I did not understand what I did wrong.
Other than this I am having problem in using the single quotes, Is the way I am using it is it correct?
You just don't need to group by a constant expression, and #rLevel is one.
So, when you remove it... you'll get another problem. It's your ORDER BY clause. It contains references to columns that are neither aggregated nor included in GROUP BY. (You can't order by an arbitrary column when you've got a GROUP BY clause in your statement.)

SQL Query Optimization

This report used to take about 16 seconds when there were 8000 rows to process. Now there are 50000 rows and the report takes 2:30 minutes.
This was my first pass at this and the client needed it yesterday, so I wrote this code in the logical order of what needed to be done, but without optimization in mind.
Now with the report taking longer as the data increases, I need to take a second look at this and optimize it. I'm thinking indexed views, table functions, etc.
I think the biggest bottleneck is looping through the temp table, making 4 select statements, and updating the temp table...50,000 times.
I think I can condense ALL of this into one large SELECT with either (a) 4 joins to the same table to get the 4 statuses, but then I am not sure how to get the TOP 1 in there, or I can try (b) using nested subqueries, but both seem really messy compared to the current code.
I'm not expecting anyone to write code for me, but if some SQL experts can peruse this code and tell me about any obvious inefficiencies and alternate methods, or ways to speed this up, or techniques I should be using instead, it would be appreciated.
PS: Assume that this DB is for the most part normalized, but poorly designed, and that I am not able to add indexes. I basically have to work with it, as is.
Where the code says (less than) I had to replace a "less than" symbol because it was cropping some of my code.
Thanks!
CREATE PROCEDURE RptCollectionAccountStatusReport AS
SET NOCOUNT ON;
DECLARE #Accounts TABLE
(
[AccountKey] INT IDENTITY(1,1) NOT NULL,
[ManagementCompany] NVARCHAR(50),
[Association] NVARCHAR(100),
[AccountNo] INT UNIQUE,
[StreetAddress] NVARCHAR(65),
[State] NVARCHAR(50),
[PrimaryStatus] NVARCHAR(100),
[PrimaryStatusDate] SMALLDATETIME,
[PrimaryDaysRemaining] INT,
[SecondaryStatus] NVARCHAR(100),
[SecondaryStatusDate] SMALLDATETIME,
[SecondaryDaysRemaining] INT,
[TertiaryStatus] NVARCHAR(100),
[TertiaryStatusDate] SMALLDATETIME,
[TertiaryDaysRemaining] INT,
[ExternalStatus] NVARCHAR(100),
[ExternalStatusDate] SMALLDATETIME,
[ExternalDaysRemaining] INT
);
INSERT INTO
#Accounts (
[ManagementCompany],
[Association],
[AccountNo],
[StreetAddress],
[State])
SELECT
mc.Name AS [ManagementCompany],
a.LegalName AS [Association],
c.CollectionKey AS [AccountNo],
u.StreetNumber + ' ' + u.StreetName AS [StreetAddress],
CASE WHEN c.InheritedAccount = 1 THEN 'ZZ' ELSE u.State END AS [State]
FROM
ManagementCompany mc WITH (NOLOCK)
JOIN
Association a WITH (NOLOCK) ON a.ManagementCompanyKey = mc.ManagementCompanyKey
JOIN
Unit u WITH (NOLOCK) ON u.AssociationKey = a.AssociationKey
JOIN
Collection c WITH (NOLOCK) ON c.UnitKey = u.UnitKey
WHERE
c.Closed IS NULL;
DECLARE #MaxAccountKey INT;
SELECT #MaxAccountKey = MAX([AccountKey]) FROM #Accounts;
DECLARE #index INT;
SET #index = 1;
WHILE #index (less than) #MaxAccountKey BEGIN
DECLARE #CollectionKey INT;
SELECT #CollectionKey = [AccountNo] FROM #Accounts WHERE [AccountKey] = #index;
DECLARE #PrimaryStatus NVARCHAR(100) = NULL;
DECLARE #PrimaryStatusDate SMALLDATETIME = NULL;
DECLARE #PrimaryDaysRemaining INT = NULL;
DECLARE #SecondaryStatus NVARCHAR(100) = NULL;
DECLARE #SecondaryStatusDate SMALLDATETIME = NULL;
DECLARE #SecondaryDaysRemaining INT = NULL;
DECLARE #TertiaryStatus NVARCHAR(100) = NULL;
DECLARE #TertiaryStatusDate SMALLDATETIME = NULL;
DECLARE #TertiaryDaysRemaining INT = NULL;
DECLARE #ExternalStatus NVARCHAR(100) = NULL;
DECLARE #ExternalStatusDate SMALLDATETIME = NULL;
DECLARE #ExternalDaysRemaining INT = NULL;
SELECT TOP 1
#PrimaryStatus = a.StatusName, #PrimaryStatusDate = c.StatusDate, #PrimaryDaysRemaining = c.DaysRemaining
FROM CollectionAccountStatus c WITH (NOLOCK) JOIN AccountStatus a WITH (NOLOCK) ON c.AccountStatusKey = a.AccountStatusKey
WHERE c.CollectionKey = #CollectionKey AND a.StatusType = 'Primary Status' AND a.StatusName 'Cleared'
ORDER BY c.sysCreated DESC;
SELECT TOP 1
#SecondaryStatus = a.StatusName, #SecondaryStatusDate = c.StatusDate, #SecondaryDaysRemaining = c.DaysRemaining
FROM CollectionAccountStatus c WITH (NOLOCK) JOIN AccountStatus a WITH (NOLOCK) ON c.AccountStatusKey = a.AccountStatusKey
WHERE c.CollectionKey = #CollectionKey AND a.StatusType = 'Secondary Status' AND a.StatusName 'Cleared'
ORDER BY c.sysCreated DESC;
SELECT TOP 1
#TertiaryStatus = a.StatusName, #TertiaryStatusDate = c.StatusDate, #TertiaryDaysRemaining = c.DaysRemaining
FROM CollectionAccountStatus c WITH (NOLOCK) JOIN AccountStatus a WITH (NOLOCK) ON c.AccountStatusKey = a.AccountStatusKey
WHERE c.CollectionKey = #CollectionKey AND a.StatusType = 'Tertiary Status' AND a.StatusName 'Cleared'
ORDER BY c.sysCreated DESC;
SELECT TOP 1
#ExternalStatus = a.StatusName, #ExternalStatusDate = c.StatusDate, #ExternalDaysRemaining = c.DaysRemaining
FROM CollectionAccountStatus c WITH (NOLOCK) JOIN AccountStatus a WITH (NOLOCK) ON c.AccountStatusKey = a.AccountStatusKey
WHERE c.CollectionKey = #CollectionKey AND a.StatusType = 'External Status' AND a.StatusName 'Cleared'
ORDER BY c.sysCreated DESC;
UPDATE
#Accounts
SET
[PrimaryStatus] = #PrimaryStatus,
[PrimaryStatusDate] = #PrimaryStatusDate,
[PrimaryDaysRemaining] = #PrimaryDaysRemaining,
[SecondaryStatus] = #SecondaryStatus,
[SecondaryStatusDate] = #SecondaryStatusDate,
[SecondaryDaysRemaining] = #SecondaryDaysRemaining,
[TertiaryStatus] = #TertiaryStatus,
[TertiaryStatusDate] = #TertiaryStatusDate,
[TertiaryDaysRemaining] = #TertiaryDaysRemaining,
[ExternalStatus] = #ExternalStatus,
[ExternalStatusDate] = #ExternalStatusDate,
[ExternalDaysRemaining] = #ExternalDaysRemaining
WHERE
[AccountNo] = #CollectionKey;
SET #index = #index + 1;
END;
SELECT
[ManagementCompany],
[Association],
[AccountNo],
[StreetAddress],
[State],
[PrimaryStatus],
CONVERT(VARCHAR, [PrimaryStatusDate], 101) AS [PrimaryStatusDate],
[PrimaryDaysRemaining],
[SecondaryStatus],
CONVERT(VARCHAR, [SecondaryStatusDate], 101) AS [SecondaryStatusDate],
[SecondaryDaysRemaining],
[TertiaryStatus],
CONVERT(VARCHAR, [TertiaryStatusDate], 101) AS [TertiaryStatusDate],
[TertiaryDaysRemaining],
[ExternalStatus],
CONVERT(VARCHAR, [ExternalStatusDate], 101) AS [ExternalStatusDate],
[ExternalDaysRemaining]
FROM
#Accounts
ORDER BY
[ManagementCompany],
[Association],
[StreetAddress]
ASC;
Don't try to guess where the query is going wrong - look at the execution plan. It will tell you what's chewing up your resources.
You can update directly from another table, even from a table variable: SQL update from one Table to another based on a ID match
That would allow you to combine everything in your loop into a single (massive) statement. You can join to the same tables for the secondary and tertiary statuses using different aliases, e.g.,
JOIN AccountStatus As TertiaryAccountStatus...AND a.StatusType = 'Tertiary Status'
JOIN AccountStatus AS SecondaryAccountStatus...AND a.StatusType = 'Secondary Status'
I'll bet you don't have an index on the AccountStatus.StatusType field. You might try using the PK of that table instead.
HTH.
First use a temp table instead of a table varaiable. These can be indexed.
Next, do not loop! Looping is bad for performance in virtually every case. This loop ran 50000 times rather than once for 50000 records, it will be horrible when you havea million records! Here is a link that will help you understand how to do set-based processing instead. It is written to avoid cursos but loops are similar to cursors, so it should help.
http://wiki.lessthandot.com/index.php/Cursors_and_How_to_Avoid_Them
And (nolock) will give dirty data reads which can be very bad for reporting. If you are in a version of SQl Server higher than 2000, there are better choices.
SELECT #CollectionKey = [AccountNo] FROM #Accounts WHERE [AccountKey] = #index;
This query would benefit from a PRIMARY KEY declaration on your table variable.
When you say IDENTITY, you are asking the database to auto-populate the column.
When you say PRIMARY KEY, you are asking the database to organize the data into a clustered index.
These two concepts are very different. Typically, you should use both of them.
DECLARE #Accounts TABLE
(
[AccountKey] INT IDENTITY(1,1) PRIMARY KEY,
I am not able to add indexes.
In that case, copy the data to a database where you may add indexes. And use: SET STATISTICS IO ON