I have a stored procedure which returns result from two tables using outer join and where conditions. It has order by clause as well. I want to add paging to it so that only requested number of records are returned. How can I do it? I need to supply pagenumber, totalnumber of records, current page etc ? My stored procedure is:
CREATE PROCEDURE [dbo].[hr_SearchVacanciesForService]
#SearchText NVARCHAR(50) = NULL,
#DutyStationID INT = NULL,
#VacancyCategoryIDs VARCHAR(1000) = NULL,
#Language INT = 1
AS
SELECT *
FROM dbo.hr_Vacancies LEFT OUTER JOIN dbo.hr_DutyStations ON dbo.hr_Vacancies.DutyStationID = dbo.hr_DutyStations.DutyStationID
LEFT OUTER JOIN dbo.hr_Companies
ON dbo.hr_Vacancies.CompanyID = dbo.hr_Companies.CompanyID
WHERE dbo.hr_Vacancies.Deleted = 0
AND (dbo.hr_Vacancies.JobTitleLang1 LIKE #LoacalSeacrchText
OR dbo.hr_Vacancies.JobTitleLang2 LIKE #LoacalSeacrchText
OR dbo.hr_Vacancies.DescriptionLang1 LIKE #LoacalSeacrchText
OR dbo.hr_Vacancies.DescriptionLang2 LIKE #LoacalSeacrchText
AND (dbo.hr_Vacancies.DutyStationID = #DutyStationID OR #DutyStationID IS NULL OR #DutyStationID = 0)
ORDER BY HavePriority DESC, StartDate DESC, dbo.hr_Vacancies.VacancyID DESC
Use option with CTE and OVER() clause
CREATE PROCEDURE [dbo].[hr_SearchVacanciesForService]
#SearchText NVARCHAR(50) = NULL,
#DutyStationID INT = NULL,
#VacancyCategoryIDs VARCHAR(1000) = NULL,
#Language INT = 1,
#NumberOfPages int
AS
;WITH cte AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY HavePriority DESC, StartDate DESC, dbo.hr_Vacancies.VacancyID DESC) AS Pages
FROM dbo.hr_Vacancies LEFT OUTER JOIN dbo.hr_DutyStations ON dbo.hr_Vacancies.DutyStationID = dbo.hr_DutyStations.DutyStationID
LEFT OUTER JOIN dbo.hr_Companies ON dbo.hr_Vacancies.CompanyID = dbo.hr_Companies.CompanyID
WHERE dbo.hr_Vacancies.Deleted = 0
AND (dbo.hr_Vacancies.JobTitleLang1 LIKE #LoacalSeacrchText
OR dbo.hr_Vacancies.JobTitleLang2 LIKE #LoacalSeacrchText
OR dbo.hr_Vacancies.DescriptionLang1 LIKE #LoacalSeacrchText
OR dbo.hr_Vacancies.DescriptionLang2 LIKE #LoacalSeacrchText
AND (dbo.hr_Vacancies.DutyStationID = #DutyStationID OR #DutyStationID IS NULL OR #DutyStationID = 0)
)
SELECT *, COUNT(*) OVER() AS totalOfPages
FROM cte
WHERE Pages BETWEEN 1 AND ISNULL(#NumberOfPages, Pages)
Example using OVER() clause with expressions:
SELECT ... ROW_NUMBER() OVER (ORDER BY
CASE WHEN dbo.hr_Vacancies.Priority = 0
THEN 0 ELSE
CASE WHEN CONVERT(DATETIME,CONVERT(CHAR(10),dbo.hr_Vacancies.PriorityDateExpired,101)) > CONVERT(DATETIME,CONVERT(CHAR(10),GETDATE(),101)) OR dbo.hr_Vacancies.PriorityDateExpired IS NULL
THEN 1 ELSE 0 END END DESC, your_second_expression_StartDate DESC)
If you want to show records from 20 to 30:
CREATE PROCEDURE [dbo].[hr_SearchVacanciesForService]
#SearchText NVARCHAR(50) = NULL,
#DutyStationID INT = NULL,
#VacancyCategoryIDs VARCHAR(1000) = NULL,
#Language INT = 1,
#StartPage int = NULL,
#EndPage int = NULL
AS
;WITH cte AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY your_case_expressionForColumnHavePriority DESC, your_case_expressionForColumnStartDate DESC, dbo.hr_Vacancies.VacancyID DESC) AS Pages
FROM dbo.hr_Vacancies LEFT OUTER JOIN dbo.hr_DutyStations ON dbo.hr_Vacancies.DutyStationID = dbo.hr_DutyStations.DutyStationID
LEFT OUTER JOIN dbo.hr_Companies ON dbo.hr_Vacancies.CompanyID = dbo.hr_Companies.CompanyID
WHERE dbo.hr_Vacancies.Deleted = 0
AND (dbo.hr_Vacancies.JobTitleLang1 LIKE #LoacalSeacrchText
OR dbo.hr_Vacancies.JobTitleLang2 LIKE #LoacalSeacrchText
OR dbo.hr_Vacancies.DescriptionLang1 LIKE #LoacalSeacrchText
OR dbo.hr_Vacancies.DescriptionLang2 LIKE #LoacalSeacrchText
AND (dbo.hr_Vacancies.DutyStationID = #DutyStationID OR #DutyStationID IS NULL OR #DutyStationID = 0)
)
SELECT *, COUNT(*) OVER() AS totalOfPages
FROM cte
WHERE Pages BETWEEN ISNULL(#StartPage, Pages) AND ISNULL(#EndPage, Pages)
You can read the following papers :
SQL Server stored procedures to page large tables or queries
Or
Stored Procedure having Sorting, Paging and Filtering
OLD METHOD - Simple Pagination script with (limit,order direction,order column,start index)
#orderColumn int ,
#orderDir varchar(20),
#start int ,
#limit int,
#searchKey varchar(20)
declare #to as int = #start+#limit
select IDENTITY(int, 1, 1) AS SnoID,null as abc, make.link,make.manf,make.name
into #tempMake
from make where status=1 and UPPER(make.name) like upper('%'+#searchKey+'%')
select * from #tempMake where SnoID>#start and SnoID<=#to
order by
CASE WHEN #orderColumn = 1 AND #orderdir = 'desc' THEN #tempMake.[manf] END DESC,
CASE WHEN #orderColumn = 1 AND #orderdir = 'asc' THEN #tempMake.[manf] END ASC,
CASE WHEN #orderColumn = 2 AND #orderdir = 'desc' THEN #tempMake.[link] END DESC,
CASE WHEN #orderColumn = 2 AND #orderdir = 'asc' THEN #tempMake.[link] END ASC
select count(#tempMake.SnoID) as row_count from #tempMake
drop table #tempMake
Related
I have procedure in which I am getting data from 4 tables by joining them.
Here is my code
alter PROCEDURE [dbo].[GetServiceRequestforLead]
#professionalArea nvarchar (35) = '',
#status nvarchar (15) = '',
#experience nvarchar (15) = '',
#PageNumber BIGINT = 1,
#PageSize BIGINT =20,
#userid bigint = 0
AS
BEGIN
SELECT *
FROM (
SELECT Row_number() OVER (ORDER BY sc.Addeddate DESC) AS Row, sc.*
, TotalRows = Count(*) OVER()
FROM (
select Mod_UserDetails.Email as LeadEmailId, Mod_UserDetails.ID as LeaduserId
, ProfessionType, Mod_UserDetails.FirstName as LeadFirstName, Mod_UserDetails.LastName as LeadLastName
, Mod_UserDetails.PhoneNo1 as LeadMobile
, f.*
from Lead_RequestForm f
inner join Lead_ProfessionalArea on Lead_ProfessionalArea.ID = f.Profession
left join ServiceProviderRequestMapping on f.RequestId = ServiceProviderRequestMapping.RequestId
left join mod_userdetails on mod_userdetails.ID = ServiceProviderRequestMapping.Leaduserid
where f.ExperienceRequired like '%'+#experience+'%'
AND f.Status like '%' + #status + '%'
AND Lead_ProfessionalArea.ProfessionType like '%'+ #professionalArea +'%'
AND (ServiceProviderRequestMapping.IsDeleted is null OR ServiceProviderRequestMapping.IsDeleted = 0)
) sc
) AS query
WHERE row > ( #PageSize *( #PageNumber -1))
AND row <= ( #PageSize * #PageNumber )
END
By executing this I am getting more than one row for RequestId column of Lead_RequestForm f table. Data is unique in both the rows but reuqest id is the same. I want to get only the first record with the same Requestid column.
i have added snap here request id column has duplicate value i want only one roq with a request id so want to select first one only
You can do as below -
alter PROCEDURE [dbo].[GetServiceRequestforLead]
#professionalArea nvarchar (35) = '',
#status nvarchar (15) = '',
#experience nvarchar (15) = '',
#PageNumber BIGINT = 1,
#PageSize BIGINT =20,
#userid bigint = 0
AS
BEGIN
Select Tb.*
from
(SELECT t.*, row_number() OVER (partition by RequestId order by Rowid) as seq_nbr
FROM (
SELECT Row_number() OVER (ORDER BY sc.Addeddate DESC) AS Row, sc.*
, TotalRows = Count(*) OVER()
FROM (
select Mod_UserDetails.Email as LeadEmailId, Mod_UserDetails.ID as LeaduserId
, ProfessionType, Mod_UserDetails.FirstName as LeadFirstName, Mod_UserDetails.LastName as LeadLastName
, Mod_UserDetails.PhoneNo1 as LeadMobile
, f.*
from Lead_RequestForm f
inner join Lead_ProfessionalArea on Lead_ProfessionalArea.ID = f.Profession
left join ServiceProviderRequestMapping on f.RequestId = ServiceProviderRequestMapping.RequestId
left join mod_userdetails on mod_userdetails.ID = ServiceProviderRequestMapping.Leaduserid
where f.ExperienceRequired like '%'+#experience+'%'
AND f.Status like '%' + #status + '%'
AND Lead_ProfessionalArea.ProfessionType like '%'+ #professionalArea +'%'
AND (ServiceProviderRequestMapping.IsDeleted is null OR ServiceProviderRequestMapping.IsDeleted = 0)
) sc
) AS query
WHERE row > ( #PageSize *( #PageNumber -1))
AND row <= ( #PageSize * #PageNumber )
)t
)Tb
where Tb.seq_nbr = 1
END
I have a query like this:
DECLARE #Sortorder VARCHAR(5) = 'asc',
#ColumnNumber INT = 9
SELECT
SUBSTRING(csu.UserName, CHARINDEX(CHAR(92), csu.UserName) + 1, LEN(csu.UserName)) AS UserName, w.WorkItemId
FROM [tasks].[WorkItems] w
LEFT JOIN operations.CustomerServiceUser csu ON csu.UserId = w.AssignedToUserId
WHERE
w.[ShowInTaskList] = 1 AND UserName IS NOT NULL
ORDER BY
CASE WHEN #ColumnNumber = 9 AND #SortOrder = 'asc' THEN UserName END ASC,
CASE WHEN #ColumnNumber = 9 AND #SortOrder = 'desc' THEN UserName END DESC
When I do that the data is not sorted by UserName in any order asc or desc, but when I do this:
SELECT
SUBSTRING(csu.UserName, CHARINDEX(CHAR(92), csu.UserName) + 1, LEN(csu.UserName)) AS UserName, w.WorkItemId
FROM [tasks].[WorkItems] w
LEFT JOIN operations.CustomerServiceUser csu ON csu.UserId = w.AssignedToUserId
WHERE
w.[ShowInTaskList] = 1 AND UserName IS NOT NULL
ORDER BY
UserName
What am I doing wrong in the dynamic order by? The values are the same in the declared variables and in the case. To be frank I don't know what keywords I should pass to google ;) Many thanks for the answer.
You have the column UserName and an alias UserName. It is the column value that is being used to sort the results, not the alias. While it is perfectly acceptable to use an alias name inside the ORDER BY clause, it cannot be used inside CASE WHEN statement.
The solution is to use a sub-query (or CTE):
DECLARE
#Sortorder VARCHAR(5) = 'asc',
#ColumnNumber INT = 9
SELECT * FROM (
SELECT SUBSTRING(csu.UserName, /* removed for readability */) AS UserNameCopy, w.WorkItemId
FROM [tasks].[WorkItems] w
LEFT JOIN operations.CustomerServiceUser csu ON csu.UserId = w.AssignedToUserId
WHERE w.[ShowInTaskList] = 1 AND UserName IS NOT NULL
) AS SubQuery
ORDER BY
CASE WHEN #ColumnNumber = 9 AND #SortOrder = 'asc' THEN SubQuery.UserNameCopy END ASC,
CASE WHEN #ColumnNumber = 9 AND #SortOrder = 'desc' THEN SubQuery.UserNameCopy END DESC
You can use CROSS APPLY to make your code look more friendly. It does not affect performance:
DECLARE
#Sortorder VARCHAR(5) = 'asc' ,
#ColumnNumber INT = 9;
SELECT
SUBSTRING(csu.UserName, CHARINDEX(CHAR(92), csu.UserName) + 1,
u.UserName ,
w.WorkItemId
FROM
[tasks].[WorkItems] w
LEFT JOIN operations.CustomerServiceUser csu ON csu.UserId = w.AssignedToUserId
CROSS APPLY (SELECT LEN(csu.UserName) AS UserName ) u
WHERE
w.[ShowInTaskList] = 1
AND UserName IS NOT NULL
ORDER BY
CASE WHEN #ColumnNumber = 9
AND #Sortorder = 'asc' THEN u.UserName
END ASC ,
CASE WHEN #ColumnNumber = 9
AND #Sortorder = 'desc' THEN u.UserName
END DESC;
And example with the data:
CREATE TABLE #a ( aColumn INT, b INT );
INSERT INTO #a
VALUES
( 1, 1 ),
( 1, 2 ),
( 2, 1 ),
( 3, 1 ),
( 1, 3 ),
( 4, 4 );
DECLARE
#Sortorder VARCHAR(5) = 'asc' ,
#ColumnNumber INT = 9;
SELECT
aColumn ,
b aColumn
FROM
#a tbl
CROSS APPLY (
SELECT
CAST(( tbl.aColumn + 1 - 2 ) * 5 AS VARCHAR(100)) r /*or any other kind of operation, such as substring etc*/
) shortcut
ORDER BY
CASE WHEN #ColumnNumber = 9
AND #Sortorder = 'asc' THEN shortcut.r
END ASC ,
CASE WHEN #ColumnNumber = 9
AND #Sortorder = 'desc' THEN shortcut.r
END DESC;
DROP TABLE #a;
I think you can achieve this easily with Dynamic SQL (it worked for me):
DECLARE #statement NVARCHAR(MAX) ,
#SortOrder VARCHAR(5) = 'desc' ,
#columnNumber INT = 9
SET #statement = N'
SELECT
SUBSTRING(csu.UserName, CHARINDEX('\ ',csu.UserName) + 1, LEN(csu.UserName)) as UserName, w.WorkItemId
FROM [tasks].[WorkItems] w
LEFT JOIN operations.CustomerServiceUser csu
ON csu.UserId = w.AssignedToUserId
WHERE
w.[ShowInTaskList] = 1
AND UserName is not null
ORDER BY' +
CASE
WHEN (#ColumnNumber = 9 AND #SortOrder = 'asc') THEN 'UserName ASC'
WHEN (#columnNumber = 9 AND #SortOrder = 'desc') THEN 'UserName DESC'
ELSE 'CustomerId' END
EXEC sys.sp_executesql #statement
GO
I believe the easy and safe way is to use a CTE:
;WITH CTE AS
(
SELECT ROW_NUMBER() OVER (ORDER BY UserName ASC) As AscOrder,
ROW_NUMBER() OVER (ORDER BY UserName DESC) As DescOrder,
-- editor note: CHARINDEX is for '\' and not '\ ', but it messes up the code coloring system of SO, so I've added a space.
SUBSTRING(csu.UserName, CHARINDEX('\ ',csu.UserName) + 1,LEN(csu.UserName)) as UserName, w.WorkItemId
FROM [tasks].[WorkItems] w
LEFT JOIN operations.CustomerServiceUser csu ON csu.UserId = w.AssignedToUserId
WHERE w.[ShowInTaskList] = 1
AND UserName is not null
)
SELECT *
FROM CTE
ORDER BY CASE WHEN #ColumnNumber = 9 AND #SortOrder = 'asc' THEN AscOrder
WHEN #ColumnNumber = 9 AND #SortOrder = 'desc' THEN DescOrder
END
I have written the following script to mimic the incoming parameters from datatables and try to filter the results using the parameters. Everything works fine except the order by clause. Basically it only orders by rownumber and does not take into consideration the case statement which provides the second order by column.
declare #sSortColumn as nvarchar(50)='Country';
declare #sSortDirection as nvarchar(5) = 'Desc';
declare #sSearch as nvarchar(50) = '';
declare #iDisplayLength as int = 20;
declare #iDisplayStart as int = 20;
declare #sIDsearch as int = CASE WHEN ISNUMERIC(#sSearch) = 1 THEN CAST(#sSearch AS INT) ELSE 0 END;
WITH media AS
(
select ROW_NUMBER() OVER (ORDER BY mc.id) as RowNumber,
mc.id,mc.Name, mc.CityID,lc.Name as Country
from Lookup_MediaChannels mc
left join Lookup_SonarMediaTypes st on mc.SonarMediaTypeID = st.ID
left join Lookup_SonarMediaGroups sg on sg.ID = st.SonarMediaGroupID
left join Lookup_MediaTypes mt on mc.MediaTypeID = mt.ID
left join Lookup_SonarMediaGroups sg1 on sg1.ID = mt.MediaGroupID
left join lookup_Countries lc on lc.id = mc.countryid
where mc.Name like '%'+#sSearch+'%'
and (sg1.ID=1 or sg.ID =1 )
or mc.id = #sIDsearch
)
SELECT RowNumber, Name, Country
FROM media
WHERE RowNumber BETWEEN (#iDisplayStart+ 1) AND (#iDisplayStart+ #iDisplayLength)
order by rownumber,
CASE WHEN #sSortColumn = 'Name' AND #sSortDirection = 'Desc'
THEN Name END DESC,
CASE WHEN #sSortColumn = 'Name' AND #sSortDirection != 'Desc'
THEN Name END,
CASE WHEN #sSortColumn = 'Country' AND #sSortDirection = 'Desc'
THEN Country END DESC,
CASE WHEN #sSortColumn = 'Country' AND #sSortDirection != 'Desc'
THEN Country END
This simplified example may help you
http://sqlfiddle.com/#!6/35ffb/10
Set up some dummy data (this would be replaced by your select statement)
create table x(
id int,
name varchar(3),
country varchar(2)
)
insert into x
values (1,'aaa','uk'),
(2,'aaa','us'),
(3,'baa','uk'),
(4,'caa','uk'),
(5,'baa','it')
Some vars to hold sort field and sort order
declare #so char(1)
declare #sf char(1)
set #so = 'a' -- a = asc d = desc
set #sf = 'n' -- n = name c = country
and a select to return sorted data
SELECT row_number()
OVER (order by
CASE WHEN #so = 'd' THEN sf END desc,
CASE WHEN #so <> 'd' THEN sf end,
id
) AS aa, name,country
FROM (
SELECT x.*, case #sf when 'n' then name when 'c' then country end sf
FROM X
) X
I have the below stored procedure and would like to only have one SQL statement. At the moment you can see there are two statements, one for the actual paging and one for a count of the total records which needs to be return to my app for paging.
However, the below is inefficient as I am getting the total rows from the first query:
COUNT(*) OVER(PARTITION BY 1) as TotalRows
How can I set TotalRows as my output parameter?
ALTER PROCEDURE [dbo].[Nop_LoadAllOptimized]
(
#PageSize int = null,
#PageNumber int = null,
#WarehouseCombinationID int = null,
#CategoryId int = null,
#OrderBy int = null,
#TotalRecords int = null OUTPUT
)
AS
BEGIN
WITH Paging AS (
SELECT rn = (ROW_NUMBER() OVER (
ORDER BY
CASE WHEN #OrderBy = 0 AND #CategoryID IS NOT NULL AND #CategoryID > 0
THEN pcm.DisplayOrder END ASC,
CASE WHEN #OrderBy = 0
THEN p.[Name] END ASC,
CASE WHEN #OrderBy = 5
THEN p.[Name] END ASC,
CASE WHEN #OrderBy = 10
THEN wpv.Price END ASC,
CASE WHEN #OrderBy = 15
THEN wpv.Price END DESC,
CASE WHEN #OrderBy = 20
THEN wpv.Price END DESC,
CASE WHEN #OrderBy = 25
THEN wpv.UnitPrice END ASC
)),COUNT(*) OVER(PARTITION BY 1) as TotalRows, p.*, pcm.DisplayOrder, wpv.Price, wpv.UnitPrice FROM Nop_Product p
INNER JOIN Nop_Product_Category_Mapping pcm ON p.ProductID=pcm.ProductID
INNER JOIN Nop_ProductVariant pv ON p.ProductID = pv.ProductID
INNER JOIN Nop_ProductVariant_Warehouse_Mapping wpv ON pv.ProductVariantID = wpv.ProductVariantID
WHERE pcm.CategoryID = #CategoryId
AND (wpv.Published = 1 AND pv.Published = 1 AND p.Published = 1 AND p.Deleted = 0 AND pv.Deleted = 0 and wpv.Deleted = 0)
AND wpv.WarehouseID IN (select WarehouseID from Nop_WarehouseCombination where UserWarehouseCombinationID = #WarehouseCombinationID)
)
SELECT TOP (#PageSize) * FROM Paging PG
WHERE PG.rn > (#PageNumber * #PageSize) - #PageSize
SELECT #TotalRecords = COUNT(p.ProductId) FROM Nop_Product p
INNER JOIN Nop_Product_Category_Mapping pcm ON p.ProductID=pcm.ProductID
INNER JOIN Nop_ProductVariant pv ON p.ProductID = pv.ProductID
INNER JOIN Nop_ProductVariant_Warehouse_Mapping wpv ON pv.ProductVariantID = wpv.ProductVariantID
WHERE pcm.CategoryID = #CategoryId
AND (wpv.Published = 1 AND pv.Published = 1 AND p.Published = 1 AND p.Deleted = 0 AND pv.Deleted = 0 and wpv.Deleted = 0)
AND wpv.WarehouseID IN (select WarehouseID from Nop_WarehouseCombination where UserWarehouseCombinationID = #WarehouseCombinationID)
END
I think I understand your issue here. Have you considered that the Count could be done BEFORE the CTE
and then passed in as value to the CTE as a variable.
i.e, set the value for #TotalRecords up front, pass it in, and so the CTE will use this count rather than executing the count a second time?
Does this make sense, or have I missed your point here.
no problem friend, highly possible i missed a trick here. However without the schema and data its tricky to test what I am suggesting. In the absence of someone giving a better answer, I've put this test script with data together to demo what I am talking about. If this isn't what you want then no problem. If it is just plain missing the point again, then I'll take that on the chin.
Declare #pagesize as int
Declare #PageNumber as int
Declare #TotalRowsOutputParm as int
SET #pagesize = 3
SET #PageNumber = 2;
--create some test data
DECLARE #SomeData table
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[SomeValue] [nchar](10) NULL
)
INSERT INTO #SomeData VALUES ('TEST1')
INSERT INTO #SomeData VALUES ('TEST2')
INSERT INTO #SomeData VALUES ('TEST3')
INSERT INTO #SomeData VALUES ('TEST4')
INSERT INTO #SomeData VALUES ('TEST5')
INSERT INTO #SomeData VALUES ('TEST6')
INSERT INTO #SomeData VALUES ('TEST7')
INSERT INTO #SomeData VALUES ('TEST8')
INSERT INTO #SomeData VALUES ('TEST9')
INSERT INTO #SomeData VALUES ('TEST10');
--Get total count of all rows
Set #TotalRowsOutputParm = (SELECT COUNT(SomeValue) FROM #SomeData p) ;
WITH Paging AS
(
SELECT rn = (ROW_NUMBER() OVER (ORDER BY SomeValue ASC)),
#TotalRowsOutputParm as TotalRows, p.*
FROM [SomeData] p
)
SELECT TOP (#PageSize) * FROM Paging PG
WHERE PG.rn > (#PageNumber * #PageSize) - #PageSize
PRINT #TotalRowsOutputParm
I don't think you can do it without running the query twice if you want to assign it to a variable
however, can't you just add another column and do something like this instead?
;WITH Paging AS (select *,ROW_NUMBER() OVER(ORDER BY name) AS rn FROM sysobjects)
SELECT (SELECT MAX(rn) FROM Paging) AS TotalRecords,* FROM Paging
WHERE rn < 10
Or in your case
SELECT TOP (#PageSize) *,(SELECT MAX(PG.rn) FROM Paging) AS TotalRecords
FROM Paging PG
WHERE PG.rn > (#PageNumber * #PageSize) - #PageSize
Then from the front end grab that column
In the end I decided just to use two different SQL statements, one for count, one for select.
The "COUNT(*) OVER(PARTITION BY 1) as TotalRows" actually was pretty expensive and it turned out much quicker to just use two different statements.
Thank you everyone who helped with this question.
Can anybody help me to optimize this code? At present it takes 17 seconds.
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
--SpResumeSearch NULL,null,null,null,null,null,null,null,null,null,null,NULL,null,null,null,null,null,null,null,0,10,NULL
ALTER PROCEDURE [dbo].[SpResumeSearch]
#Keyword varchar(50) = NULL,
#JobCategoryId int = NULL,
#NationalityId int = NULL,
#CountryId int = NULL,
#LocationId int = NULL,
#Email nvarchar(50) = NULL,
#Gender int = NULL,
#PassportNumber nvarchar(20) = NULL,
#VisaStatus int = NULL,
#PoBox nvarchar(10) = NULL,
#CareerLevelId int = NULL,
#KeySkills nvarchar(50) = NULL,
#ExpectedSalary int = NULL,
#Experience int = NULL,
#DOB varchar(20) = NULL,
#AppliedFrom datetime = NULL,
#AppliedTo datetime = NULL,
#MaritalStatusId int = NULL,
#LanguageId int = NULL,
#PageIndex int,
#NumRows int,
#SortCol varchar(20) = NULL
AS
BEGIN
DECLARE #startRowIndex INT;
SET #startRowIndex = (#PageIndex * #NumRows) + 1;
WITH ResumeListTemp AS
(SELECT DISTINCT M.MemberID, R.ResumeID, R.CreatedDate, R.ModifiedDate, R.CompletedDate, RP.FirstName, RP.LastName, G.Title AS Gender,
RP.DateOfBirth, C.NationalityTitle AS Nationality, RPD.KeySkills, RPD.ExperienceYear AS Experience, V.Title AS VisaStatus, RC.Phone, RC.Mobile,
ROW_NUMBER() OVER (ORDER BY
CASE WHEN #SortCol='FIRSTNAME' THEN FirstName END,
CASE WHEN #SortCol='LASTNAME' THEN RP.LastName END,
CASE WHEN #SortCol='GENDER' THEN G.Title END,
CASE WHEN #SortCol='DOB' THEN RP.DateOfBirth END,
CASE WHEN #SortCol='NATIONALITY' THEN C.NationalityTitle END,
CASE WHEN #SortCol='KEYSKILLS' THEN RPD.KeySkills END,
CASE WHEN #SortCol='EXPERIENCE' THEN RPD.ExperienceYear END,
CASE WHEN #SortCol='VISASTATUS' THEN V.Title END,
CASE WHEN #SortCol='CONTACTNO' THEN RC.Mobile END,
CASE WHEN #SortCol='UPDATEDATE' THEN R.ModifiedDate END,
CASE WHEN #SortCol IS NULL THEN R.CompletedDate END
) AS RowNum
FROM TblResume AS R
LEFT OUTER JOIN TblResumeContactInfo AS RC ON RC.FKResumeID = R.ResumeID
LEFT OUTER JOIN TblResumePersonalDetail AS RP ON RP.FKResumeID = R.ResumeID
LEFT OUTER JOIN TblResumeJobCategory AS RJC ON RJC.FKResumeID = R.ResumeID
LEFT OUTER JOIN TblResumeProfessionalDetail AS RPD ON RPD.FKResumeID = R.ResumeID
LEFT OUTER JOIN TblResumeWorkExperience AS RE ON RE.FKResumeID = R.ResumeID
LEFT OUTER JOIN TblResumeEducation AS RQ ON RQ.FKResumeID = R.ResumeID
LEFT OUTER JOIN TblResumeSkill AS RS ON RS.FKResumeID = R.ResumeID
LEFT OUTER JOIN TblMember AS M ON M.MemberID = R.FKMemberID
LEFT OUTER JOIN TblMasterGender AS G ON G.GenderID = RP.FKGenderID
LEFT OUTER JOIN TblMasterCountry AS C ON C.CountryID = RP.FKNationalityID
LEFT OUTER JOIN TblRisVisaStatus AS V ON V.VisaStatusID = RP.FKVisaStatusID
LEFT OUTER JOIN TblResumeLanguage AS L ON L.FKResumeID = R.ResumeID
WHERE (
-- RC.Address LIKE '%'+COALESCE(#Keyword,RC.Address)+'%' OR
-- RC.City LIKE '%'+COALESCE(#Keyword,RC.City)+'%' OR
R.ResumeID IN ( SELECT _RQ.FKResumeID FROM TblResumeEducation AS _RQ, TblResume AS _R WHERE _RQ.Specialization LIKE '%'+COALESCE(#Keyword, _RQ.Specialization)+'%' AND _RQ.FKResumeID=_R.ResumeID GROUP BY _RQ.FKResumeID) OR
R.ResumeID IN ( SELECT _RQ.FKResumeID FROM TblResumeEducation AS _RQ, TblResume AS _R WHERE _RQ.Institution LIKE '%'+COALESCE(#Keyword, _RQ.Institution)+'%' AND _RQ.FKResumeID=_R.ResumeID GROUP BY _RQ.FKResumeID) OR
RP.FirstName LIKE '%'+COALESCE(#Keyword,RP.FirstName)+'%' OR
RP.LastName LIKE '%'+COALESCE(#Keyword,RP.LastName)+'%' OR
--RP.PassportNumber LIKE '%'+COALESCE(#Keyword,RP.PassportNumber)+'%' OR
--(#Keyword IS NULL OR RP.PassportNumber LIKE '%' + #Keyword +'%') OR
RPD.Summary LIKE '%'+COALESCE(#Keyword,RPD.Summary)+'%' OR
-- R.ResumeID IN ( SELECT _RS.FKResumeID FROM TblResumeSkill AS _RS, TblResume AS _R WHERE _RS.Title LIKE '%'+COALESCE(#Keyword,_RS.Title)+'%' AND _RS.FKResumeID=_R.ResumeID GROUP BY _RS.FKResumeID) OR
-- R.ResumeID IN ( SELECT _RE.FKResumeID FROM TblResumeWorkExperience AS _RE, TblResume AS _R WHERE _RE.Employer LIKE '%'+COALESCE(#Keyword, _RE.Employer)+'%' AND _RE.FKResumeID=_R.ResumeID GROUP BY _RE.FKResumeID) OR
R.ResumeID IN ( SELECT _RE.FKResumeID FROM TblResumeWorkExperience AS _RE, TblResume AS _R WHERE _RE.Designation LIKE '%'+COALESCE(#Keyword, _RE.Designation)+'%' AND _RE.FKResumeID=_R.ResumeID GROUP BY _RE.FKResumeID) OR
R.ResumeID IN ( SELECT _RE.FKResumeID FROM TblResumeWorkExperience AS _RE, TblResume AS _R WHERE _RE.Responsibilities LIKE '%'+COALESCE(#Keyword, _RE.Responsibilities)+'%' AND _RE.FKResumeID=_R.ResumeID GROUP BY _RE.FKResumeID)) AND
R.ResumeID IN ( SELECT _RJC.FKResumeID FROM TblResumeJobCategory AS _RJC, TblResume AS _R WHERE _RJC.FKJobCategoryID = COALESCE(#JobCategoryId, _RJC.FKJobCategoryID) AND _RJC.FKResumeID=_R.ResumeID GROUP BY _RJC.FKResumeID ) AND
RP.FKNationalityID = COALESCE(#NationalityId, RP.FKNationalityID) AND
RC.FKCountryID = COALESCE(#CountryId, RC.FKCountryID) AND
-- RPD.FKJobLocationID = COALESCE(#LocationId, RPD.FKJobLocationID) AND
-- M.Email LIKE '%'+COALESCE(#Email, M.Email)+'%' AND
-- RP.FKGenderID = COALESCE(#Gender, RP.FKGenderID) AND
-- RP.PassportNumber LIKE '%'+COALESCE(#PassportNumber, RP.PassportNumber)+'%' AND
-- RP.FKVisaStatusID = COALESCE(#VisaStatus, RP.FKVisaStatusID) AND
-- COALESCE(RC.ZipCode,'0') LIKE '%'+COALESCE(#PoBox, COALESCE(RC.ZipCode,'0'))+'%' AND
RPD.FKExperienceLevelID = COALESCE(#CareerLevelId, RPD.FKExperienceLevelID) AND
-- RPD.KeySkills LIKE '%'+COALESCE(#KeySkills, RPD.KeySkills)+'%' AND
RPD.FKSalaryID = COALESCE(#ExpectedSalary, RPD.FKSalaryID) AND
RPD.ExperienceYear = COALESCE(#Experience, RPD.ExperienceYear) AND
RP.DateOfBirth = COALESCE(#DOB, RP.DateOfBirth) AND
R.CompletedDate = COALESCE(#AppliedFrom, R.CompletedDate) AND
R.CompletedDate = COALESCE(#AppliedTo, R.CompletedDate) AND
RP.FKMaritalStatusID = COALESCE(#MaritalStatusId, RP.FKMaritalStatusID) AND
R.ResumeID IN ( SELECT _L.FKResumeID FROM TblResumeLanguage AS _L, TblResume AS _R WHERE _L.FKLanguageID = COALESCE(#LanguageId, _L.FKLanguageID) AND _L.FKResumeID=_R.ResumeID GROUP BY _L.FKResumeID ) AND
R.IsCompleted = 1
)
SELECT ResumeListTemp.*, (SELECT COUNT(*) from ResumeListTemp) AS RecCount
FROM ResumeListTemp
WHERE RowNum BETWEEN #startRowIndex AND #StartRowIndex + #NumRows - 1
ORDER BY
CASE WHEN #SortCol='FIRSTNAME' THEN FirstName END,
CASE WHEN #SortCol='LASTNAME' THEN LastName END,
CASE WHEN #SortCol='GENDER' THEN Gender END,
CASE WHEN #SortCol='DOB' THEN DateOfBirth END,
CASE WHEN #SortCol='NATIONALITY' THEN Nationality END,
CASE WHEN #SortCol='KEYSKILLS' THEN KeySkills END,
CASE WHEN #SortCol='EXPERIENCE' THEN Experience END,
CASE WHEN #SortCol='VISASTATUS' THEN VisaStatus END,
CASE WHEN #SortCol='CONTACTNO' THEN Mobile END,
CASE WHEN #SortCol='UPDATEDATE' THEN ModifiedDate END,
CASE WHEN #SortCol IS NULL THEN CompletedDate END
END
try to replace * with the column names
In this case, I think that building your query dynamically and using sp_executesql will give you much better performance.
There's an MSDN article explaining the basics here, and there are some more in-depth articles by Erland Sommarskog, here and here.
Having '%' at the very start of a LIKE clause can cause a table scan, try to remove them if possible.
since there is only one column to be sorted in the result, you may try to replace the ORDER BY clause (twice!) as
CASE #SortCol
WHEN 'FIRSTNAME' THEN FirstName
WHEN 'LASTNAME' THEN LastName
etc
ELSE CompletedDate
END
(you probably need to CONVERT columns to NVARCHAR)
Optimization for WHERE section - seems to me, there is no need of using IN clause, thouse tables are already joined by ResumeID, all you need is to filter them out:
WHERE (
(RQ.Specialization IS NOT NULL AND (#Keyword IS NULL OR RQ.Specialization LIKE '%'+#Keyword+'%')) OR
(RQ.Institution IS NOT NULL AND (#Keyword IS NULL OR RQ.Institution LIKE '%'+#Keyword+'%')) OR
(RP.FirstName IS NOT NULL AND (#Keyword IS NULL OR RP.FirstName LIKE '%'+#Keyword+'%')) OR
(RP.LastName IS NOT NULL AND (#Keyword IS NULL OR RP.LastName LIKE '%'+#Keyword+'%')) OR
(RPD.Summary IS NOT NULL AND (#Keyword IS NULL OR RPD.Summary LIKE '%'+#Keyword+'%')) OR
(RE.Designation IS NOT NULL AND (#Keyword IS NULL OR RE.Designation LIKE '%'+#Keyword+'%')) OR
(RE.Responsibilities IS NOT NULL AND (#Keyword IS NULL OR RE.Responsibilities LIKE '%'+#Keyword+'%'))) AND
(RJC.FKJobCategoryID IS NOT NULL AND (#JobCategoryId IS NULL OR RE.Designation LIKE '%'+#JobCategoryId+'%')) AND
RP.FKNationalityID = COALESCE(#NationalityId, RP.FKNationalityID) AND
RC.FKCountryID = COALESCE(#CountryId, RC.FKCountryID) AND
RPD.FKExperienceLevelID = COALESCE(#CareerLevelId, RPD.FKExperienceLevelID) AND
RPD.FKSalaryID = COALESCE(#ExpectedSalary, RPD.FKSalaryID) AND
RPD.ExperienceYear = COALESCE(#Experience, RPD.ExperienceYear) AND
RP.DateOfBirth = COALESCE(#DOB, RP.DateOfBirth) AND
R.CompletedDate = COALESCE(#AppliedFrom, R.CompletedDate) AND
R.CompletedDate = COALESCE(#AppliedTo, R.CompletedDate) AND
RP.FKMaritalStatusID = COALESCE(#MaritalStatusId, RP.FKMaritalStatusID) AND
(L.FKLanguageID IS NOT NULL AND (#LanguageId IS NULL OR L.FKLanguageID LIKE '%'+#LanguageId+'%')) AND
R.IsCompleted = 1
)
Why are you starting each where clause with a wildcard? you can never optimize using that technique (even if you convert to dynamic SQL) as the database is not unable to use the indexes. Require your users to to at a minimum put in the first letter of what they are searching for.
Get rid of the subselects, they are performance killers. Since you are already joining to those tables, you shouldn't need them.