Inproving speed of T-SQL script - sql

I need to improve the speed dramatically for the following script. I am thinking about removing the table valued function and placing everything in the stored procedure. But before I do it I wanted to get the experts to take a look and provide me with a solution or a few pointers. The scripting has paging functionality which needs to remain in place somehow.
Here firstly is the Store procedure:
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
ALTER PROC [sbuser].[sp_MemberSearch]
#UserName varchar(200) = null,
#MemberID bigint = null,
#PG int = 1,
#ROWCT numeric(18,2) = 1,
#COLCT numeric(18,2) = 1,
#MODE varchar(50)
AS
IF #MODE = 'MEMBERSEARCH'
BEGIN
SELECT
MemberID, -- 0
UserName, -- 1
LastLogin, -- 2
PrCity, -- 3
Abbr, -- 4
Country, -- 5
AvatarMed, -- 6
Gender, -- 7
HasImages, -- 8
HasVideo, -- 9
HasAudio, -- 10
Domain, -- 11
DisplayName, -- 12
CreateDate, -- 13
Claimed, -- 14
PG, -- 15
MAXPG, -- 16
TOTALRECS, -- 17
ProfileTypeID, -- 18
Zip, -- 19
PhoneNbr, -- 20
PrPhone -- 21
FROM sbuser.tf_FindMember(#UserName,#MemberID,#PG,#ROWCT,#COLCT)
END
and here is the table valued function as named above:
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [sbuser].[tf_FindMember] (
#UserName varchar(200) = null,
#MemberID bigint = null,
#PG int = 1,
#ROWCT numeric(18,2) = 1,
#COLCT numeric(18,2) = 1 )
RETURNS #OUT TABLE (
MemberID bigint, -- 0
UserName varchar(200), -- 1
LastLogin datetime, -- 2
PrCity varchar(50), -- 3
Abbr varchar(5), -- 4
Country varchar(50), -- 5
AvatarMed varchar(50), -- 6
Gender varchar(50), -- 7
HasImages bit, -- 8
HasVideo bit, -- 9
HasAudio bit, -- 10
Domain varchar(100), -- 11
DisplayName varchar(255), -- 12
CreateDate datetime, -- 13
Claimed varchar(1), -- 14
PG int, -- 15
MAXPG int, -- 16
TOTALRECS int, -- 17
ProfileTypeID bigint, -- 18
Zip varchar(50), -- 19
PhoneNbr varchar(50), -- 20
PrPhone varchar(25)) -- 21
AS
BEGIN
DECLARE #START numeric(18,2)
DECLARE #END numeric(18,2)
DECLARE #SIZE numeric(18,2)
DECLARE #MAXPG numeric(18,2)
DECLARE #TOTALRECS numeric(18,2)
DECLARE #TOTALRECS_INT int
DECLARE #MAXPG_INT int
DECLARE #TOTALRECS_REMAINDER numeric(18,2)
SET #SIZE = #ROWCT * #COLCT
SET #Start = (((#PG - 1) * #Size) + 1)
SET #END = (#START + #SIZE - 1)
DECLARE #TMP1 TABLE (
TMPID bigint primary key identity(1,1),
MemberID bigint,
UserName varchar(200),
LastLogin datetime,
PrCity varchar(50),
Abbr varchar(5),
Country varchar(50),
AvatarMed varchar(50),
Gender varchar(50),
HasImages bit,
HasVideo bit,
HasAudio bit,
Domain varchar(100),
DisplayName varchar(255),
CreateDate datetime,
Claimed varchar(1),
ProfileTypeID bigint,
Zip varchar(50),
PhoneNbr varchar(50),
PrPhone varchar(25))
BEGIN
INSERT INTO #TMP1
SELECT
a.MemberID,
a.UserName,
a.LastLogin,
a.PrCity,
b.Abbr,
c.Country,
a.AvatarMed,
a.Gender,
sbuser.sf_MemberHasImages(a.MemberID),
sbuser.sf_MemberHasVideo(a.MemberID),
sbuser.sf_MemberHasAudio(a.MemberID),
d.Domain,
sbuser.sf_DisplayName(a.MemberID),
a.CreateDate,
a.Claimed,
a.ProfileTypeID,
a.Zip,
a.PhoneNbr,
a.PrPhone
FROM Member a
LEFT JOIN State b ON b.StateID = a.StateID
INNER JOIN Country c ON c.countryID = a.CountryID
INNER JOIN Region d ON d.RegionID = a.MemberRegionID
WHERE (sbuser.sf_DisplayName(a.MemberID) LIKE #UserName + '%')
AND a.MemberID <> #MemberID
ORDER BY a.Claimed DESC, sbuser.sf_MemberHasAvatar(a.MemberID) DESC, sbuser.sf_MemberHasMedia(a.MemberID) DESC
END
SELECT #TOTALRECS = MAX(TMPID) FROM #TMP1
SELECT #MAXPG = #TOTALRECS / #SIZE
SET #TOTALRECS_REMAINDER = #TOTALRECS % #SIZE
SET #MAXPG_INT = CAST(#MAXPG AS INT)
SET #TOTALRECS_INT = CAST(#TOTALRECS AS INT)
IF #TOTALRECS_REMAINDER > 0
BEGIN
SET #MAXPG_INT = #MAXPG_INT + 1
END
INSERT INTO #OUT
SELECT
MemberID,
UserName,
LastLogin,
PrCity,
Abbr,
Country,
AvatarMed,
Gender,
HasImages,
HasVideo,
HasAudio,
Domain,
DisplayName,
CreateDate,
Claimed,
#PG,
#MAXPG_INT,
#TOTALRECS_INT,
ProfileTypeID,
Zip,
PhoneNbr,
PrPhone
FROM #TMP1
WHERE (TmpID >= #Start) AND (TmpID <= #END)
RETURN
END
I believe this script was written by software, by my predecessor. I don't have much experience with T-SQL. I would really appreciate any help you can give to improve the execution speed as now our system has over 40,000 members it has gotten extremely slow.
Many thanks for taking a look. I really appreciate it!
Best Regards,
Paul Jacobs
To further assist here are the missing sf scripts:
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [sbuser].[sf_MemberHasImages](#MemberID bigint)
RETURNS BIT
AS
BEGIN
DECLARE #OUT BIT
SET #OUT = (SELECT CAST(COUNT(a.MemberImgID) AS BIT) From MemberImg a INNER JOIN MemberImgGallery b ON b.MemberImgGalleryID=a.MemberImgGalleryID
WHERE b.MemberID = #MemberID)
RETURN #out
END
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [sbuser].[sf_MemberHasAudio](#MemberID bigint)
RETURNS BIT
AS
BEGIN
DECLARE #OUT BIT
SET #OUT = (SELECT CAST(COUNT(MemberAudioID) AS BIT) FROM MemberAudio WHERE MemberID = #MemberID)
RETURN #OUT
END
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [sbuser].[sf_MemberHasVideo](#MemberID bigint)
RETURNS BIT
AS
BEGIN
DECLARE #OUT BIT
SET #OUT = (SELECT CAST(COUNT(MemberVideoID) AS BIT) FROM MemberVideo WHERE MemberID = #MemberID)
RETURN #OUT
END

Looking at the code you've posted, the flow basically is like this:
a stored proc calls a UDF.
---- not perf related, but why is this string being sent to a stored proc named the same? IF #MODE = 'MEMBERSEARCH' in a proc called [sp_MemberSearch]. Seems superfluous from this once-over review of the code.
this should NOT call a UDF. Suggest refactoring the code to have all the searching in the stored proc. Likely today it's being used from many stored procs, so it's easily re-used in the current setup. Not horrible, but you can do it another way, especially if you're using SQL Server 2008. Try re-architecting it as its own stored proc.
The UDF basically performs a select with 3 important pieces/considerations:
(sbuser.sf_DisplayName(a.MemberID) LIKE #UserName + '%'). To me, this is implying that EACH member ID is being passed into the function, and evaluated against the LIKE condition.
ORDER BY 2 different results of UDFs - MemberHasAvatar and MemberHasMedia -- obviously wanting those to be ordered at the top.
The 4 tables: Member, State, Country, Region -- are they indexed properly on the JOINed columns? How well does this SELECT perform when you run this statement without any/some/all of that WHERE clause and ORDER BY clause?
the paging aspects could be improved to use newer TSQL language features, but it could be more than you wanted to take on. ROW_NUMBER() being part of that improvement.
it's not clear what the real-world meaning of this clause is: WHERE (sbuser.sf_DisplayName(a.MemberID) LIKE #UserName + '%')
AND a.MemberID <> #MemberID
Does it mean that we don't want to include the #MemberID in the search results because they're the member performing the search? Probably a better variable name is suitable like #SearchPerformedByMemberID?
The math part, as you probably already know, won't have any measurable performance impact on this function/proc.
Some suggestions for improvement, YMMV!
Suggest storing the DisplayName on the Member table to avoid having to call sbuser.sf_DisplayName.

Related

SQL Function is INCREDIBLY slow

I have a SQL function that is used to return a single value in another view, this function takes well over 30 seconds sometimes on larger databases, I think it might be running over and over?
Honestly I'm just losing my mind at this point and need the help. Does anyone know the best way to optimize this?
The T-SQL function looks like this:
IF OBJECT_ID('Base.fn_AssetPriority') IS NOT NULL
DROP FUNCTION Base.fn_AssetPriority
GO
CREATE FUNCTION Base.fn_AssetPriority
(#LID BIGINT)
RETURNS NVARCHAR(20)
WITH EXECUTE AS CALLER
AS
BEGIN
DECLARE #Priority NVARCHAR(20)
DECLARE #RGID BIGINT
DECLARE #CP TABLE
(
ConsequenceAssessmentID BIGINT,
[Sign] VARCHAR(2),
Score DECIMAL(18,2),
AssetPriority CHAR(1),
ConsNo INT
)
SET #Priority = 'Not Allocated'
INSERT INTO #CP
SELECT
ConsequenceAssessmentID, [Sign], Score, AssetPriority, ConsNo
FROM
Base.ConsequencePriority
ORDER BY
ConsNo DESC
SELECT #RGID = MAX(ID)
FROM ACA.ReviewGroup
WHILE EXISTS (SELECT * FROM #CP)
BEGIN
DECLARE #CAID BIGINT
DECLARE #ConsNo INT
DECLARE #Sign VARCHAR(2)
DECLARE #Score DECIMAL(18,2)
DECLARE #AP CHAR(1)
SELECT TOP 1
#CAID = ConsequenceAssessmentID,
#ConsNo = ConsNo,
#Sign = [Sign],
#Score = Score,
#AP = AssetPriority
FROM
#CP
ORDER BY
ConsNo DESC
IF #Sign = '='
BEGIN
IF EXISTS (SELECT * FROM ACA.ConsequenceAssessment
WHERE LID = #LID AND RGID = #RGID
AND BaseCAID = #CAID AND Score = #Score)
BEGIN
SET #Priority = #AP
BREAK
END
END
ELSE BEGIN
IF EXISTS (SELECT * FROM ACA.ConsequenceAssessment
WHERE LID = #LID AND RGID = #RGID
AND BaseCAID = #CAID AND Score >= #Score)
BEGIN
SET #Priority = #AP
BREAK
END
END
DELETE FROM #CP
WHERE ConsequenceAssessmentID = #CAID
AND ConsNo = #ConsNo
END
RETURN #Priority
END
There is another view that calls this as a field:
Base.fn_AssetPriority(BaseAS.ID) AS AssetPriority,
How on Earth do I optimize this? or get it to run a bit quicker?
It's possible the execution plan for your stored function is stale.
Try doing this and rerunning it.
EXEC sp_recompile N'Base.fn_AssetPriority';
If it gets faster you may want to run that recompile every so often. Maybe use a job to recompile it every day.
You probably don't want to put WITH RECOMPILE in the function's definition, because you use it a lot and the reason for recompilation is changing statistics in the tables it queries.

Subquery - Incorrect Syntax near the keyword 'declare'

Having an issue debugging this, hoping someone can help me clear this up. This is part of a much longer query, but the subquery "B" is the only part that is causing a problem, not sure why... I'm obviously missing something. I'm using sql-server.
The sub query runs fine on its own, just not with the rest of the query.
DECLARE #dstrt AS DATETIME
SET #dstrt = '2020-09-01 00:00:00'
DECLARE #dend AS DATETIME
SET #dend = '2020-09-30 23:59:59'
DECLARE #UnpaidChgsTot table
(
SiteID INT,
Period nvarchar(30),
dcDlqntTot money,
iDelUnits int,
dcPctUnits money,
dcPctGrossPot money,
dcPctActOcc money,
iDatePeriod int
)
DECLARE #sLanguageTermColName nvarchar(20)
SET #sLanguageTermColName = 'English'
-- DECLARE some period counters
DECLARE #StartDayNumber int
DECLARE #EndDayNumber int
SET #StartDayNumber = 0
SET #EndDayNumber = -1
--Hold the original date value
DECLARE #dEndORig datetime
SET #dEndORig = #dEnd
DECLARE #dcGrossPotDenom money
DECLARE #dcActOccDenom money
DECLARE #iTotUnitsDenom int
SET #dcGrossPotDenom = 1
SET #dcActOccDenom = 1
SET #iTotUnitsDenom = 1
--Define a holding table for charge balances by ChargeID
DECLARE #BalT table
(
SiteID int,
ChargeID int,
LedgerID int,
ChargeDescID int,
sChgCategory nvarchar(20),
sDefAcctCode nvarchar(20),
dChgStrt datetime,
dcBalAmt money,
dcBalTax1 money,
dcBalTax2 money
)
/*
* Updated. Delete was taking too long to run, added to WHERE to get rid
* Date: 10192009
* Josh
*/
--Fill the #BalT with charge balances for charges <=dEND
INSERT INTO #BalT
SELECT
B.SiteID,
B.ChargeID,
B.LedgerID,
B.ChargeDescID,
CD.sChgCategory,
CA.sDefAcctCode,
B.dChgStrt,
dcAmt,
dcTax1,
dcTax2
FROM -- PROBLEM STARTS HERE
(
DECLARE #ChargesT2 table
(
SiteID int,
ChargeID int,
ChargeDescID int,
sChgCategory nvarchar(50),
sDefAcctCode nvarchar(5),
dChgStrt datetime,
LedgerID int,
dcAmt money,
dcTax1 money,
dcTax2 money,
sChargeTag nvarchar(20)
)
DECLARE #fnPmtSumByChargeT Table
(
SiteID INT,
ChargeID int,
dcPmtSum money
)
DECLARE #fnPmtSumByChargeT2 Table
(
SiteID INT,
ChargeID int,
dcPmtSum money
)
DECLARE #ChargesTempT table
(
SiteID INT,
ChargeID int,
dcAmt money,
dcTax1 money,
dcTax2 money
)
Declare #ChargesT table
(
SiteID INT,
ChargeID int,
ChargeDescID int,
sChgCategory nvarchar(50),
sDefAcctCode nvarchar(5),
dChgStrt datetime,
LedgerID int,
dcAmt money,
dcTax1 money,
dcTax2 money,
sChargeTag nvarchar(20)
)
INSERT INTO #ChargesT2
SELECT
C.SiteID,
C.ChargeID,
C.ChargeDescID,
sChgCategory,
sDefAcctCode,
C.dChgStrt,
C.LedgerID,
Coalesce(C.dcAmt,0.0) AS dcBalAmt,
Coalesce(C.dcTax1,0.0) AS dcBalTax1,
Coalesce(C.dcTax2,0.0) AS dcBalTax2,
CASE
WHEN CAST(CA.sDefAcctCode AS INT) = 4000 THEN N'Rent'
WHEN CAST(CA.sDefAcctCode AS INT) = 4042 THEN N'LateFee'
WHEN CAST(CA.sDefAcctCode AS INT) = 4041 THEN N'AdminFee'
WHEN CAST(CA.sDefAcctCode AS INT) = 4070 THEN N'Insurance'
WHEN CAST(CA.sDefAcctCode AS INT) BETWEEN 4060 AND 4068 OR CAST(CA.sDefAcctCode AS INT) BETWEEN 4071 AND 4079 THEN N'POS'
--WHEN CAST(CA.sDefAcctCode AS INT) = 2020 THEN 'SecDep'
ELSE N'Others'
END as sChargeTag
FROM Charges AS C
INNER JOIN ChargeDesc AS CD ON C.ChargeDescID = CD.ChargeDescID
LEFT OUTER JOIN ChartOfAccts AS CA ON CD.ChartofAcctID = CA.ChartOfAcctID
WHERE
C.dDeleted IS NULL
AND C.dChgStrt <= #dend
--2016-01-12 - Case # 237424 - C158, L005, Unit 10478, Tenant - Zach Reese.
--Commented out dCreated evaluation. This is because A/R does not prevent backdating charges from changing historical reports. There is no way these two reports
--can tie out in the same period if we do not allow backdating charges that we created in a future period. Ex. Late fee created on January 2, 2016, but dChgStrt of 12/29/2015.
--AND dCreated <= #dEnd --This is to catch a case where NSF charges were added (backdated) after the report end date, affecting historical reporting. 02102011 Josh
AND (bNSF = 0 OR (bNSF = 1 AND dCreated <= #dend))--updated the logic to be different for NSF charges to not change reports historically; this loigc is consistant with A/R. Case # 269909
GROUP BY C.siteID, C.ChargeID, C.ChargeDescID, sChgCategory, sDefAcctCode, C.dChgStrt, C.LedgerID, C.dcAmt, C.dcTax1, C.dcTax2, CA.sDefAcctCode
INSERT INTO #fnPmtSumByChargeT2
SELECT
SiteID,
ChargeID,
dcPmtSum
FROM
(
SELECT
pay.SiteID,
Pay.ChargeID,
SUM(pay.dcPmtAmt) AS dcPmtSum
FROM
(
SELECT
SiteID,
ChargeID,
dcPmtAmt
FROM Payments
WHERE
(dDeleted Is Null)
--AND (bNSF = 0) --this fn must return ALL payments to calculate balances correctly
AND (dPmt <= #dend)
) AS Pay
GROUP BY SiteID, ChargeID
) AS P
INSERT INTO #fnPmtSumByChargeT2
SELECT
C.SiteID,
C.ChargeID,
0
FROM #ChargesT2 C
INSERT INTO #fnPmtSumByChargeT
SELECT
P.SiteID,
P.ChargeID,
SUM(P.dcPmtSum)
FROM #fnPmtSumByChargeT2 P
GROUP BY P.SiteID, P.ChargeID
INSERT INTO #ChargesTempT
SELECT
P.SiteID,
P.ChargeID,
C.dcAmt - Coalesce(dbo.fnPartNonTax(C.dcAmt, C.dcTax1, C.dcTax2, P.dcPmtSum, 2),0.0),
C.dcTax1 - Coalesce(dbo.fnPartTax1(C.dcAmt, C.dcTax1, C.dcTax2, P.dcPmtSum, 2,2),0.0),
C.dcTax2 - Coalesce(dbo.fnPartTax2(C.dcAmt, C.dcTax1, C.dcTax2, P.dcPmtSum,2,2),0.0)
FROM #ChargesT2 C
INNER JOIN #fnPmtSumByChargeT P ON C.ChargeID = P.ChargeID
GROUP BY P.SiteId, P.ChargeID, C.dcAmt, dcTax1, dcTax2, dcPmtSum
HAVING (dcAmt + dcTax1 + dcTax2 - dcPmtSum) > 0 -- Removed 0 sum columns: This was a major bottleneck for this function 10202009 J
INSERT INTO #ChargesT
SELECT
C.SiteID,
C.ChargeID,
C.ChargeDescID,
C.sChgCategory,
C.sDefAcctCode,
C.dChgStrt,
C.LedgerID,
CT.dcAmt,
CT.dcTax1,
CT.dcTax2,
C.sChargeTag
FROM #ChargesT2 C
INNER JOIN #ChargesTempT CT ON C.ChargeID = CT.ChargeID
SELECT *
FROM #ChargesT
AS ChargeT
) AS B -- PROBLEM ENDS HERE
INNER JOIN ChargeDesc AS CD ON B.ChargeDescID = CD.ChargeDescID AND B.Siteid = CD.SiteID
I indicated "Problem starts here" and "problem ends here" in the notes, if that helps.
Thanks in advance.
Inside a subquery the only thing you is write a select statement. You cannot write anything you want like it is a code block.
In your case, you may accomplish what you want by moving all those table variables to outside the parentheses and populating them, then inside the parentheses you just do the select. Like I said, it is not a code block, there are no local variables. There's no prettier way. SQL is a simple language for querying information. That's just how it is.
Alternatively, you may like to use Common Table Expressions (CTEs) to give each subquery a name. The subqueries do not exist as any temp variable or table, just as helper names available during the main query. That may solve what you want do to.

Autogenerate numbers based on "Group" in GroupByExpression RadGrid and display it in RadGrid

I am having a trouble in implementing below requirement.
Current RadGrid: Below is the RadGrid in which I am using GroupByExpressions
to display/show data grouped with "Business Unit" column.
In RadGrid column 2nd(InvoiceLineNo) and 3rd(InvoiceNo), I am auto generating the numbers using Stored Procedure.
i.e., for "InvoiceLineNo" column, Autogenerated No's are: 01,02,03,04,05,06,07,08.......n
for "InvoiceNo" column, Autogenerated No's are: 15100001, 15100002, 15100003........n
where, 15 is a "year" and 100001 are "running numbers"
Requirement is: I want to show the "InvoiceLineNo" column data as Group wise.
Example:
for 1st "Business Unit" group (i.e., SUNWAY LEISURE SDN BHD (CARNIVAL)),
InvoiceLineNo shall be: 01,02,03,04,05,06,07,08
for 2nd "Business Unit" group (i.e., SUNWAY MALL PARKING SDN BHD),
InvoiceLineNo shall be: 01,02,03,04,05,06,07,08
Similarly, I want to show the "InvoiceNo" column data as Group wise.
Example:
for 1st "Business Unit" group (i.e., SUNWAY LEISURE SDN BHD (CARNIVAL)),
InvoiceNo shall be: 15100001,15100001,15100001,15100001,15100001,15100001,15100001,15100001
for 2nd "Business Unit" group (i.e., SUNWAY MALL PARKING SDN BHD),
InvoiceNo shall be: 15100002,15100002,15100002,15100002,15100002,15100002,15100002,15100002
"InvoiceNo" column data will always be unique for different "Business Unit".
I want output to be like below snapshot:
I can autogenerate the numbers serial wise but I am not getting how to autogenerate the 2 column values based on Group and show them like
that.
Please help me to achieve it. Please do reply.
Thanks in advance.
Edit:
Below is the Stored Procedure I am using to generate autogenerated numbers in RadGrid's 2 column's:
ALTER PROCEDURE [dbo].[SDM_Assign_RunningNo]
-- Add the parameters for the stored procedure here
#TableName as nvarchar(50),
#NewID as nvarchar(50) OUTPUT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #rn_year as nvarchar(50)
--Get Year From table
SELECT #rn_year =RNYear FROM dbo.SDM_Tran_RunningNo
WHERE RNYear= YEAR(GetDate())
--get last 2 digits of year
Declare #2digit_rn_year as nvarchar(50)
SELECT #2digit_rn_year = RNYear % 100 FROM dbo.SDM_Tran_RunningNo
WHERE RNYear= YEAR(GetDate())
IF #TableName='SDM_Tran_GenerateInvoice_No'
BEGIN
SELECT #NewID=Next_InvoiceNo FROM dbo.SDM_Tran_RunningNo
WHERE RNYear=#rn_year
UPDATE dbo.SDM_Tran_RunningNo
SET Next_InvoiceNo=Next_InvoiceNo+1
WHERE RNYear=#rn_year
SET #NewID = #2digit_rn_year +'1'+RIGHT('000000' + CAST(#NewID as varchar(10)), 5)
END
ELSE IF #TableName='SDM_Tran_GenerateInvoice_LineNo'
BEGIN
SELECT #NewID=Next_InvoiceLineNo FROM dbo.SDM_Tran_RunningNo
WHERE RNYear=#rn_year
UPDATE dbo.SDM_Tran_RunningNo
SET Next_InvoiceLineNo=Next_InvoiceLineNo+1
WHERE RNYear=#rn_year
SET #NewID = RIGHT('000000' + CAST(#NewID as varchar(10)), 2)
END
END
And then inserting the 2 column values into Table as below (using Stored Procedure),
to display it in RadGrid:
DECLARE #InvoiceNo as nvarchar(50)
--SP to generate new Invoice No
EXEC dbo.SDM_Assign_RunningNo
#TableName='SDM_Tran_GenerateInvoice_No',
#NewID = #InvoiceNo OUTPUT
DECLARE #InvoiceLineNo as nvarchar(50)
--SP to generate new Invoice Line No
EXEC dbo.SDM_Assign_RunningNo
#TableName='SDM_Tran_GenerateInvoice_LineNo',
#NewID = #InvoiceLineNo OUTPUT
INSERT INTO SDM_Tran_GenerateInvoice
VALUES (#InvoiceID,
#SPfoID,
#InvoiceLineNo, #InvoiceNo, #InvoiceType,
#BillingIDfoID, #BusinessUnit, #DirectCost,
#Status, GETDATE(), #AccountCode)
This is a concept... you might need to modify it to suit your requirement.
DECLARE #Temp TABLE (
InvoiceID nvarchar(50),
SPfoID nvarchar(50),
InvoiceLineNo nvarchar(50),
InvoiceNo nvarchar(50),
InvoiceType nvarchar(50),
BillingIDfoID nvarchar(50),
BusinessUnit nvarchar(2000),
DirectCost nvarchar(2000),
Status nvarchar(10),
Date datetime,
AccountCode nvarchar(1000)
)
DECLARE #Temp1 TABLE (
OrderID INT IDENTITY, -- Added This so It Will follow this new Identity Row
InvoiceID nvarchar(50),
SPfoID nvarchar(50),
InvoiceLineNo nvarchar(50),
InvoiceNo nvarchar(50),
InvoiceType nvarchar(50),
BillingIDfoID nvarchar(50),
BusinessUnit nvarchar(2000),
DirectCost nvarchar(2000),
Status nvarchar(10),
Date datetime,
AccountCode nvarchar(1000)
)
DECLARE #CompanyValue nvarchar(2000) = '' --BusinessUnit datatype
DECLARE #Counter nvarchar(50) = '0' --InvoiceNo datatype
DECLARE #InvoiceLine INT = 1
DECLARE #Year INT = YEAR(GETDATE())
DECLARE #ShortYear VARCHAR(2) = SUBSTRING(CONVERT(VARCHAR(4), #Year), 3, 2)
EXEC dbo._RunningNo
#TableName='Invoice',
#NewID = InvoiceID --OUTPUT
INSERT INTO #Temp (InvoiceID, SPfoID, InvoiceType, BillingIDfoID, BusinessUnit, DirectCost, Status, Date, AccountCode)
SELECT InvoiceID, SPfoID, InvoiceType, BillingIDfoID, BusinessUnit, DirectCost, Status, Date, AccountCode FROM [MainTable] ORDER BY BusinessUnit
INSERT INTO #Temp1
SELECT * FROM #Temp ORDER BY BusinessUnit
SELECT * FROM #Temp
SELECT * FROM #Temp1 -- before update
--Update #Temp1 table
UPDATE #Temp1
SET
#Counter = InvoiceNo = CASE WHEN #CompanyValue = '' OR #CompanyValue = BusinessUnit THEN (CONVERT(VARCHAR(100), CONVERT(INT,#Counter) + 1)) ELSE '1' END,
#InvoiceLine = CASE WHEN #CompanyValue = '' OR #CompanyValue = BusinessUnit THEN #InvoiceLine ELSE #InvoiceLine + 1 END,
#CompanyValue = BusinessUnit,
InvoiceLineNo = #ShortYear + '10000' + CONVERT(VARCHAR(3), #InvoiceLine)
SELECT * FROM #Temp1 --after update
--Update main table
UPDATE g
SET g.InvoiceLineNo = t.InvoiceLineNo,
g.InvoiceNo = t.InvoiceNo
FROM SDM_Tran_GenerateInvoice g
INNER JOIN #Temp1 t
ON g.InvoiceID = t.InvoiceID
Select * from [MainTable]
ORDER BY BusinessUnit;

how to perform sorting and filtering in stored procedure with performance optimization?

I want to perform sorting and filtering in my stored procedure.
My create table for Holiday table:
CREATE TABLE [dbo].[Holiday](
[HolidaysId] [int] IDENTITY(1,1) NOT NULL,
[HolidayDate] [date] NULL,
[HolidayDiscription] [nvarchar](500) NULL,
[Name] [nvarchar](max) NULL,
CONSTRAINT [PK_Holiday] PRIMARY KEY CLUSTERED
(
[HolidaysId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
My filtering criteria would be as:
Starts With
Is Equal to
Not Equal to.
Note:Please ignore HolidayId in filter comparision.
My Table:Holiday
HolidaysId int,Name nvarchar(500),HolidayDate date.
Sample Input:
HolidayId Name Date
1 abc 1/1/2015
2 pqr 1/2/2015
3 xyz 1/3/2015
Output:
Case 1:Starts with(This is just for name column only.likewise i want to do for HolidayDate column too)
Input:ab(filtering parameter)
Query:where Name like '%ab%' order by Name(when sort column name is passed as parameter in stored procedure for column to sort(for eg:Name))
output:1,abc,1/1/2015
Case 2:Is Equal to(Same as above)
Input:prr(filtering parameter)
output:2,pqr,1/2/2015
Case 3:Not Equal to(Same as above)
Input:bbb(filtering parameter)
output:All Records
This is my stored procedure so far:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[sp_PagedItems]
(
#Page int,
#RecsPerPage int
)
AS
-- We don't want to return the # of rows inserted
-- into our temporary table, so turn NOCOUNT ON
SET NOCOUNT ON
--Create a temporary table
CREATE TABLE #TempItems
(
ID int,
Name varchar(50),
HolidayDate date
)
-- Insert the rows from tblItems into the temp. table
INSERT INTO #TempItems (ID, Name,HolidayDate)
SELECT HolidaysId,HolidayDiscription,HolidayDate FROM holiday
-- Find out the first and last record we want
DECLARE #FirstRec int, #LastRec int
SELECT #FirstRec = (#Page - 1) * #RecsPerPage
SELECT #LastRec = (#Page * #RecsPerPage + 1)
-- Now, return the set of paged records, plus, an indiciation of we
-- have more records or not!
SELECT *,
MoreRecords =
(
SELECT COUNT(*)
FROM #TempItems TI
WHERE TI.ID >= #LastRec
)
FROM #TempItems
WHERE ID > #FirstRec AND ID < #LastRec
-- Turn NOCOUNT back OFF
SET NOCOUNT OFF
Now there are 4 things i would send to my stored procedure are:
Page no
PageSize(number of records to retrive)
Sorting Column Name(Name Or HolidayDate)
My filter Column name(Name of Holidaydate) and Operator like StartWith or Equal to or not equal to.(ColumnName and Operator)
Can anybody help me to perform sorting and filtering and if any performance optimization related changes is there then please please do suggest me.
I've not tested this, but something like this you can use as starter and do modifications to make it stable:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[sp_PagedItems]
(
#ID int = NULL,
#Name varchar(50) = NULL,
#HolidayDate date = NULL,
#SortCol varchar(20) = '',
#Page int=1,
#RecsPerPage int=10 -- default size, you can change it or apply while executing the SP
)
AS
BEGIN
-- We don't want to return the # of rows inserted
-- into our temporary table, so turn NOCOUNT ON
SET NOCOUNT ON
--Create a temporary table
CREATE TABLE #TempItems
(
ID int,
Name varchar(50),
HolidayDate date
)
-- Insert the rows from tblItems into the temp. table
INSERT INTO #TempItems (ID, Name,HolidayDate)
SELECT HolidaysId, HolidayDiscription, HolidayDate
FROM holiday
-- Find out the first and last record we want
DECLARE #FirstRec int, #LastRec int
SELECT #FirstRec = (#Page - 1) * #RecsPerPage
SELECT #LastRec = (#Page * #RecsPerPage + 1)
-- Now, return the set of paged records, plus, an indiciation of we
-- have more records or not!
; WITH CTE_Results
AS (
SELECT ROW_NUMBER() OVER (ORDER BY
CASE WHEN #SortCol = 'ID_Asc' THEN ID
END ASC,
CASE WHEN #SortCol = 'ID_Desc' THEN ID
END DESC,
CASE WHEN #SortCol = 'Name_Asc' THEN Name
END ASC,
CASE WHEN #SortCol = 'Name_Desc' THEN Name
END DESC,
CASE WHEN #SortCol = 'HolidayDate_Asc' THEN HolidayDate
END ASC,
CASE WHEN #SortCol = 'HolidayDate_Desc' THEN HolidayDate
END DESC
) AS ROWNUM,
ID,
Name,
HolidayDate
FROM #TempItems
WHERE
(#ID IS NULL OR ID = #ID)
AND (#Name IS NULL OR Name LIKE '%' + #Name + '%')
AND (#HolidayDate IS NULL OR HolidayDate = #HolidayDate)
)
SELECT
ID,
Name,
HolidayDate
FROM CTE_Results
WHERE
ROWNUM > #FirstRec
AND ROWNUM < #LastRec
ORDER BY ROWNUM ASC
-- Turn NOCOUNT back OFF
SET NOCOUNT OFF
END
GO
You can check the blog posts I've written on:
Creating Stored Procedures with Dynamic Search (filter)
Creating Stored Procedures with Dynamic Search & Paging (Pagination)
Creating Stored Procedure with Dynamic Search, Paging and Sorting
You can also use the FETCH-OFFSET clause for Pagination if you are on SQL 2012 or more, link.
This is how i have done and i am getting expected output but still i want to take improvement suggestion from all of you if there is any.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[HolidayFetchList]
#pageno int,
#pagesize int,
#sortorder varchar(10),
#sortcolumn varchar(100),
#filter varchar(max),
#count int OUTPUT
AS
BEGIN
declare #Start int=(#pageno)*#pagesize;
declare #End int=#Start+#pagesize;
SET NOCOUNT ON;
DECLARE #tblHoliday AS TABLE
(HolidaysId int,HolidayDate date,HolidayDiscription nvarchar(500),HolidayName nvarchar(max),RN int)
declare #sql varchar(max)= '
select HolidaysId,HolidayDate,HolidayDiscription,HolidayDiscription as HolidayName,ROW_NUMBER() OVER
(ORDER BY '+#sortcolumn + ' '+#sortorder+' ) AS RN from Holiday
WHERE 1=1 '+#filter
print #sql
INSERT INTO #tblHoliday
exec (#sql)
select #count=COUNT(*) from #tblHoliday
print #count
select * from #tblHoliday where RN>#Start and RN<=#End order by RN
END
Please do give me any suggestion if you have any.
It's NOT recommended to use #temp tables because you can affect the RAM on your server. But, bad news :(, you should NOT use the exec command either... now you are susceptible to SQL Injection in your application. So, I think there are at least two options: 1) Using views (include flag values), table valued-functions and other components; 2) Filtering inside the WHERE statement as shown below:
SELECT * FROM Holiday
WHERE
CASE WHEN #paramStartDate Is Not Null THEN HolidayDate ELSE '' END
>= CASE WHEN #paramStartDate Is Not Null THEN #paramStartDate ELSE '' END
AND
CASE WHEN #paramEndDate Is Not Null THEN HolidayDate ELSE '' END
<= CASE WHEN #paramEndDate Is Not Null THEN #paramEndDate ELSE '' END
AND
CASE WHEN #paramName Is Not Null THEN [Name] ELSE '' END
LIKE CASE WHEN #paramName Is Not Null THEN '%' + #paramName + '%' ELSE '' END
You should keep in mind that this method can increment the time of process. If so, you have the possibility of creating several stored procedures, one for HolidayDate search, another one for Name search and another one that combines filters. Your application must be able to decide which one to use depending on the input parameters.
For pagination (ad-hoc reports) I would use OFFSET and FETCH. Use some advantage of T-SQL, then you won't need temporary tables and any of that mess.

t-sql string unique ID (Northwind database)

I've been trying to get this right for some time now with no use.
I have a table in mssql database and I want to insert new row using stored procedure
CREATE TABLE "Customers" (
"CustomerID" NCHAR(5) NOT NULL,
"CompanyName" NVARCHAR(40) NOT NULL,
"ContactName" NVARCHAR(30) NULL,
"ContactTitle" NVARCHAR(30) NULL,
"Address" NVARCHAR(60) NULL,
"City" NVARCHAR(15) NULL,
"Region" NVARCHAR(15) NULL,
"PostalCode" NVARCHAR(10) NULL,
"Country" NVARCHAR(15) NULL,
"Phone" NVARCHAR(24) NULL,
"Fax" NVARCHAR(24) NULL,
PRIMARY KEY ("CustomerID")
);
The problem is CustomerID field which contains unique string for each record (ALFKI, BERGS, BERGS, etc.)
I want to make a stored procedure which will insert a row with new data and create an unique CustomerID. Build in functions are out of a question as I need the string to be 5 chars long.
I have a procedure which generates 5 chars ID as follows
begin
declare #chars char(26) = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
declare #i int = 0
declare #id varchar(max) = ''
while #i < 5
begin
set #id = #id + substring(#chars, cast(ceiling(rand() * 26) as int), 1)
set #i = #i + 1
end
Select (cast(#id as nvarchar(400)))
end
And the one that I tried to make work with no use. It is supposed to select an unique id (set #id = 'ANATR' is there on purpose to make it go into the loop
begin
declare #randID varchar(5) = ''
declare #selectID varchar(20) = ''
declare #chars char(26) = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
declare #i int = 0
declare #id varchar(10) = ''
while #i < 5
begin
set #id = #id + substring(#chars, cast(ceiling(rand() * 26) as int), 1)
set #i = #i + 1
end
select #id
set #id = 'ANATR'
SET #selectID = (SELECT CustomerID FROM CUSTOMERS WHERE CustomerID = #id)
while #selectID <> 'NULL'
begin
set #id = ''
while #i < 5
begin
set #id = #id + substring(#chars, cast(ceiling(rand() * 26) as int), 1)
set #i = #i + 1
end
SET #selectID = (SELECT CustomerID FROM CUSTOMERS WHERE CustomerID = #id)
SELECT #id
end
end
Here is the insert procedure I have at the moment
CREATE PROCEDURE [dbo].[InsertCustomers]
(
#CustomerID nchar(5),
#CompanyName nvarchar(40),
#ContactName nvarchar(30) = NULL,
#ContactTitle nvarchar(30) = NULL,
#Address nvarchar(60) = NULL,
#City nvarchar(15) = NULL,
#Region nvarchar(15) = NULL,
#PostalCode nvarchar(10) = NULL,
#Country nvarchar(15) = NULL,
#Phone nvarchar(24) = NULL,
#Fax nvarchar(24) = NULL
)
AS
SET NOCOUNT OFF;
INSERT INTO [dbo].[Customers] ([CustomerID], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax]) VALUES (#CustomerID, #CompanyName, #ContactName, #ContactTitle, #Address, #City, #Region, #PostalCode, #Country, #Phone, #Fax);
The main problem here is that the incremental cost of detecting collisions from the generated string, and try again, increases as you generate more and more strings (since you have to read all of those strings to make sure you didn't generate a duplicate). At the same time, the odds of hitting a duplicate goes up, meaning the bigger the table gets, the slower this process will get.
Why do you need to generate the unique string at runtime? Build them all in advance. This article and this post are about random numbers, but the basic concept is the same. You build up a set of unique strings and pull one off the stack when you need one. Your chance of collisions stays constant at 0% throughout the lifetime of the application (provided you build up a stack of enough unique values). Pay for the cost of collisions up front, in your own setup, instead of incrementally over time (and at the cost of a user waiting for those attempts to finally yield a unique number).
This will generate 100,000 unique 5-character strings, at the low, one-time cost of about 1 second (on my machine):
;WITH
a(a) AS
(
SELECT TOP (26) number + 65 FROM master..spt_values
WHERE type = N'P' ORDER BY number
),
b(a) AS
(
SELECT TOP (10) a FROM a ORDER BY NEWID()
)
SELECT DISTINCT CHAR(b.a) + CHAR(c.a) + CHAR(d.a) + CHAR(e.a) + CHAR(f.a)
FROM b, b AS c, b AS d, b AS e, b AS f;
That's not enough? You can generate about 1.12 million unique values by changing TOP (10) to TOP (20). This took 18 seconds. Still not enough? TOP (24) will give you just under 8 million in about 2 minutes. It will get exponentially more expensive as you generate more strings, because that DISTINCT has to do the same duplicate checking you want to do every single time you add a customer.
So, create a table:
CREATE TABLE dbo.StringStack
(
ID INT IDENTITY(1,1) PRIMARY KEY,
String CHAR(5) NOT NULL UNIQUE
);
Insert that set:
;WITH
a(a) AS
(
SELECT TOP (26) number + 65 FROM master..spt_values
WHERE type = N'P' ORDER BY number
),
b(a) AS
(
SELECT TOP (10) a FROM a ORDER BY NEWID()
)
INSERT dbo.StringStack(String)
SELECT DISTINCT CHAR(b.a) + CHAR(c.a) + CHAR(d.a) + CHAR(e.a) + CHAR(f.a)
FROM b, b AS c, b AS d, b AS e, b AS f;
And then just create a procedure that pops one off the stack when you need it:
CREATE PROCEDURE dbo.AddCustomer
#CustomerName VARCHAR(64) /* , other params */
AS
BEGIN
SET NOCOUNT ON;
DELETE TOP (1) dbo.StringStack
OUTPUT deleted.String, #CustomerName /* , other params */
INTO dbo.Customers(CustomerID, CustomerName /*, ...other columns... */);
END
GO
No silly looping, no needing to check if the CustomerID you generated just exists, etc. The only additional thing you'll want to build is some type of check that notifies you when you're getting low.
As an aside, these are terrible identifiers for a CustomerID. What is wrong with a sequential surrogate key, like an IDENTITY column? How is a 5-digit random string with all this effort involved, any better than a unique number the system can generate for you much more easily?
Muhammed Ali 's answer works, but will prove rather ressource intensive (especially when there aren't many combinations of 5 letters left to use) : your function uses the random generator, and it will take it a while to find a combination that isn't used, especially since it has a very limited memory of its previous results.
This means it will try, and might give you something of the sort (exaggerating a bit) : BAGER the first time, then ANSWE the second time, then again BAGER the third time. You see you will lose a good amount of time with the generator giving you the same answer over and over again (especially over 12M possible combinations).
If you are looking for a fixed length ID (since you use NCHAR(5), I guess that's a good assumption), I would rather look into building a table that contains all the possible combinations, and pick one value of this table every time you need one. You would delete it once it got used, or mark it as used (which I would prefer, for reuseability reasons).
This leads to my final comment (which I cannot put as comment 'cause I don't have enough reputation) : why not use the IDENTITY function provided by MS-SQL ? This provides a much better handling of the Primary key generation...
I believe you can do something like this to make sure you all get a unique id
begin
declare #chars char(26) = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
declare #i int = 0
declare #id varchar(max) = ''
while (1=1)
begin
set #id = #id + substring(#chars, cast(ceiling(rand() * 26) as int), 1)
set #i = #i + 1
IF (NOT EXISTS(SELECT * FROM Customers WHERE CustomerID = #id) AND LEN(#id) = 5)
BREAK
ELSE
CONTINUE
end
Select (cast(#id as nvarchar(400)))
end
Set the while condition to be always true and break out of while loop only when both of your requirements are TRUE i.e Length of new ID is 5 and it does not exist in the customers table already.