Total Count in paging query taking more time in SQL Server - sql-server-2012

In my below paging query, taking 25 records from 100k records in just 2 seconds. But when I add TOTROWS column to my query for returning the total count of records (100k) it is taking more than 1 minute. Is there any method to find total no of records in optimized manner?
Below one is running fast without including TOTROWS column in the outer select query.
DECLARE #PRODUCTNAME NVARCHAR(200),
#PAGE VARCHAR(100)
SET NOCOUNT ON;
DECLARE #ROWNUM INT = 25
DECLARE #ROWCOUNT
DECLARE #TOTROWS INT
DECLARE #XY INT
SET #PAGE = 1
SELECT TOP 25
ID, NAME
FROM
(SELECT
*,
TOTROWS = COUNT(ID) OVER()
FROM
(SELECT DISTINCT
TP.ID AS ID, TP.NAME AS [NAME],
ROW_NUMBER() OVER (ORDER BY TP.ID ASC) AS Row
FROM
PDF TP
<WHERE CONDITIONS>
UNION ALL
SELECT DISTINCT
TP.ID AS ID, TP.NAME AS [NAME],
ROW_NUMBER() OVER (ORDER BY TP.ID ASC) AS Row
FROM
HTML TP
WHERE <conditions>) a
WHERE
ROW > (#PAGE - 1) * 25) XY
Below one is running slow after adding TOTROWS column in the outer select query.
DECLARE #PRODUCTNAME NVARCHAR(200),
#PAGE VARCHAR(100)
SET NOCOUNT ON;
DECLARE #ROWNUM INT = 25
DECLARE #ROWCOUNT
DECLARE #TOTROWS INT
DECLARE #XY INT
SET #PAGE = 1
SELECT TOP 25
ID, NAME, TOTROWS
FROM
(SELECT
*,
TOTROWS = COUNT(ID) OVER()
FROM
(SELECT DISTINCT
TP.ID AS ID, TP.NAME AS [NAME],
ROW_NUMBER() OVER (ORDER BY TP.ID ASC) AS Row
FROM
PDF TP
<WHERE CONDITIONS>
UNION ALL
SELECT DISTINCT
TP.ID AS ID, TP.NAME AS [NAME],
ROW_NUMBER() OVER (ORDER BY TP.ID ASC) AS Row
FROM
HTML TP
WHERE <conditions>) a
WHERE
ROW > (#PAGE - 1) * 25) XY

Related

Selecting data from table where sum of values in a column equal to the value in another column

Sample data:
create table #temp (id int, qty int, checkvalue int)
insert into #temp values (1,1,3)
insert into #temp values (2,2,3)
insert into #temp values (3,1,3)
insert into #temp values (4,1,3)
According to data above, I would like to show exact number of lines from top to bottom where sum(qty) = checkvalue. Note that checkvalue is same for all the records all the time. Regarding the sample data above, the desired output is:
Id Qty checkValue
1 1 3
2 2 3
Because 1+2=3 and no more data is needed to show. If checkvalue was 4, we would show the third record: Id:3 Qty:1 checkValue:4 as well.
This is the code I am handling this problem. The code is working very well.
declare #checkValue int = (select top 1 checkvalue from #temp);
declare #counter int = 0, #sumValue int = 0;
while #sumValue < #checkValue
begin
set #counter = #counter + 1;
set #sumValue = #sumValue + (
select t.qty from
(
SELECT * FROM (
SELECT
ROW_NUMBER() OVER (ORDER BY id ASC) AS rownumber,
id,qty,checkvalue
FROM #temp
) AS foo
WHERE rownumber = #counter
) t
)
end
declare #sql nvarchar(255) = 'select top '+cast(#counter as varchar(5))+' * from #temp'
EXECUTE sp_executesql #sql, N'#counter int', #counter = #counter;
However, I am not sure if this is the best way to deal with it and wonder if there is a better approach. There are many professionals here and I'd like to hear from them about what they think about my approach and how we can improve it. Any advice would be appreciated!
Try this:
select id, qty, checkvalue from (
select t1.*,
sum(t1.qty) over (partition by t2.id) [sum]
from #temp [t1] join #temp [t2] on t1.id <= t2.id
) a where checkvalue = [sum]
Smart self-join is all you need :)
For SQL Server 2012, and onwards, you can easily achieve this using ROWS BETWEEN in your OVER clause and the use of a CTE:
WITH Running AS(
SELECT *,
SUM(qty) OVER (ORDER BY id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS RunningQty
FROM #temp t)
SELECT id, qty, checkvalue
FROM Running
WHERE RunningQty <= checkvalue;
One basic improvement is to try & reduce the no. of iterations. You're incrementing by 1, but if you repurpose the logic behind binary searching, you'd get something close to this:
DECLARE #RoughAverage int = 1 -- Some arbitrary value. The closer it is to the real average, the faster things should be.
DECLARE #CheckValue int = (SELECT TOP 1 checkvalue FROM #temp)
DECLARE #Sum int = 0
WHILE 1 = 1 -- Refer to BREAK below.
BEGIN
SELECT TOP (#RoughAverage) #Sum = SUM(qty) OVER(ORDER BY id)
FROM #temp
ORDER BY id
IF #Sum = #CheckValue
BREAK -- Indicating you reached your objective.
ELSE
SET #RoughAverage = #CheckValue - #Sum -- Most likely incomplete like this.
END
For SQL 2008 you can use recursive cte. Top 1 with ties limits result with first combination. Remove it to see all combinations
with cte as (
select
*, rn = row_number() over (order by id)
from
#temp
)
, rcte as (
select
i = id, id, qty, sumV = qty, checkvalue, rn
from
cte
union all
select
a.id, b.id, b.qty, a.sumV + b.qty, a.checkvalue, b.rn
from
rcte a
join cte b on a.rn + 1 = b.rn
where
a.sumV < b.checkvalue
)
select
top 1 with ties id, qty, checkvalue
from (
select
*, needed = max(case when sumV = checkvalue then 1 else 0 end) over (partition by i)
from
rcte
) t
where
needed = 1
order by dense_rank() over (order by i)

How can I update a column with row_number in SQL Server?

How can I udpate a column with row_number in SQL Server 2008 R2?
BEGIN TRANSACTION
DECLARE #count int
DECLARE #maxcount int
SET #count = 1
SET #maxcount = (SELECT count(*)
FROM Applicant_Detail ad
WHERE ad.identification_code = 1)
PRINT #maxcount
WHILE (#count<#maxcount)
BEGIN
UPDATE ad
SET ad.NRIC_nbr = s.myRowNumber
FROM Applicant_Detail ad
INNER JOIN (
SELECT ROW_NUMBER() OVER (ORDER BY NRIC_nbr ASC) AS myRowNumber
FROM Applicant_Detail ad
)S
ON s.myRowNumber = #count
SET #count = #count+1
END
This query takes a lot of time. I do not have any column in the applicant_detail table which has sequential data? I use the count logic but takes lot of time?
What i want?
Update the column of the table with sequential data like 1,2,3,4,5,6,7,8,9...... max row of the table?
Try this:
declare #count int = (select count(1) from Applicant_Detail)
;with cte as
(select *, row_number() over (order by #count) rn
from Applicant_Detail)
update cte
set NRIC_nbr = rn
select * from Applicant_Detail
Demo
Solution for this problem
BEGIN TRANSACTION
;WITH numbering AS
( SELECT AD.NRIC_nbr,
AD.application_number,
ROW_NUMBER() OVER(ORDER BY AD.application_number) AS ROWNUMBER
FROM Applicant_Detail ad WHERE AD.identification_code=1
)
UPDATE numbering
SET NRIC_nbr=ROWNUMBER
when you want to update the column with row_numebr. it is the perfect solution
WITH TEMP AS
(
SELECT id as rid,
ROW_NUMBER() OVER (ORDER BY [ID] ASC) AS RN
FROM ORG
)
update ORG Set columnname1 ='200'+(select RN FROM TEMP where TEMP.rid=ORG.id)

SQL Procedure to get row number to determine 'Rank'

I have a table which contains a column called 'Score'. I want to write a procedure which loops through all rows in this table, and determines where out of all rows, each specific row ranks i.e biggest score is rank 1, smallest score = n.
This is my poor attempt so far:
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #Rank INT = 0;
DECLARE #UserID UNIQUEIDENTIFIER;
DECLARE cur CURSOR FOR SELECT UserID FROM tblMember
OPEN cur
FETCH NEXT FROM cur INTO #UserID
--loop through all users
WHILE ##FETCH_STATUS = 0 BEGIN
SELECT #Rank = ROW_NUMBER() OVER(ORDER BY Score DESC) FROM tblDetails WHERE UserID = #UserID;
PRINT #Rank
Print ' For '
Print #UserID;
UPDATE tblDetails SET Rank = #Rank WHERE UserID = #UserID;
FETCH NEXT FROM cur INTO #UserID
END
END
Unfortauntely this effort ranks each entry as 1 - probably becuase the query has the WHERE clause refining the result set. But I cannot determine what the correct query should be!
WITH q AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY userId ORDER BY score DESC) rn
FROM tblDetail
)
UPDATE q
SET rank = rn
I believe the correct query you want is:
with toupdate as (
select d.*,
row_number() over (order by score desc) as seqnum
from tblDetails
)
update toupdate
set [rank] = seqnum;
I used row_number() because your example does. You might want rank() or dense_rank() if you want scores with the same value to have the same rank:
with toupdate as (
select d.*,
rank() over (order by score desc) as seqnum
from tblDetails
)
update toupdate
set [rank] = seqnum;
The important difference is that there is no partition by clause in the query.
may be basing on the userid you are trying to update or increment the rank column..So as per my understanding what ever the Userid = 1 you want to update Rank column
declare #t INT;
SELECT #t = MAX(Userid) From tblDetails
;
with cte(w) as
(select 1
UNION ALL
Select Userid + 1
From
tblDetails where Userid < #t)
UPdate tblDetails
set [Rank]=cte.w
FROM cte
INNER JOIN tblDetails S
ON S.Userid = cte.w

How to select a portion of rows from a table?

I need to select X rows from a table starting at position Y; a specific column is used for ordering the table.
This query almost works:
DECLARE #Index int
DECLARE #Count int
SELECT * FROM
(
SELECT TOP (#Count) * FROM
(
SELECT TOP (#Index + #Count) * FROM Table
ORDER BY Table.OrderColumn ASC
) AS T1
ORDER BY T1.OrderColumn DESC
) AS T2
ORDER BY T2.OrderColumn ASC
However, if there aren't enough rows in the table (say, the table has 120 rows and I want 50 rows starting from position 100), this query just ignores the starting position and returns the last X rows.
Also, using three levels of SELECTs and ordering strikes me as quite bad performance-wise.
What is the right way to do this?
Here's a variation that may work
DECLARE #Index int = 5403
DECLARE #Count int = 1000
SELECT * FROM
(
SELECT TOP (#Index + #Count) *,
ROW_NUMBER() over (order by OrderColumn) as Sequence
FROM MyTable
ORDER BY MyTable.OrderColumn ASC
) as T
WHERE Sequence BETWEEN #Index and #Index + #Count - 1
ORDER BY OrderColumn
The derived table (nested query) shouldn't hurt performance. SQL Server will optimize for it. Although it will depend on the what the real query looks like.
If SQL 2012 is used OFFSET clause may be handy, a sample based on AdventureWorks is provided below :-
DECLARE #Index int = 100 DECLARE #Count int = 50
SELECT SalesOrderID, OrderDate, CustomerID, SalesPersonID
FROM Sales.SalesOrderHeader
ORDER BY OrderDate, SalesOrderID
OFFSET #Index ROWS FETCH NEXT #Count ROWS ONLY;
if it is SQL2008, windows functions with CTE would be helpful, a sample based on AdventureWorks:-
DECLARE #Index int = 100
DECLARE #Count int = 50
;WITH C AS
(
SELECT ROW_NUMBER() OVER( ORDER BY OrderDate,SalesOrderID ) AS rownum,
SalesOrderID, OrderDate, CustomerID, SalesPersonID
FROM Sales.SalesOrderHeader
)
SELECT SalesOrderID, OrderDate, CustomerID, SalesPersonID
FROM C
WHERE rownum BETWEEN #Index + 1 AND #Index + #Count
ORDER BY rownum;

How to get previous and next row's value effeciently in SQL server

Say I have these rows,
InstrumentID
547
698
708
InstrumentID is not autogenerated column.
Say if I pass the parameter in procedure as 698, I should get previous value as 547 and next value as 708. How do I do this efficiently in SQL?
I have this procedure but it is not efficient (and not correct).
Alter PROCEDURE GetNextAndPreviousInsturmentID
(
#InstrumentID varchar(14),
#PreviousInstrumentID varchar(14) OUT,
#NextInstrumentID varchar(14) OUT
)
AS
BEGIN
Declare #RowNum int = 0
Select #RowNum = ROW_NUMBER() Over (Order by Cast(#InstrumentID as decimal(18))) From Documents Where InstrumentID = #InstrumentID
;With normal As
(
Select ROW_NUMBER() Over (Order by Cast(#InstrumentID as decimal(18))) as RowNum, Cast(InstrumentID as decimal(18)) as InstrumentID
From Documents
)
Select #PreviousInstrumentID = InstrumentID From normal
Where RowNum = #RowNum - 1
Select #NextInstrumentID = InstrumentID From normal
Where RowNum = #RowNum + 1
END
GO
Here is a simpler solution, still it's more efficient
SELECT P.PreviousID, N.NextID
FROM
(SELECT MAX(D.InstrumentID) PreviousID
FROM Documents D
WHERE InstrumentID < #InstrumentID) P
CROSS JOIN
(SELECT MIN(D.InstrumentID) NextID
FROM Documents D
WHERE InstrumentID > #InstrumentID) N
Try this:
Alter PROCEDURE GetNextAndPreviousInsturmentID
(
#InstrumentID varchar(14),
#PreviousInstrumentID varchar(14) OUT,
#NextInstrumentID varchar(14) OUT
)
AS
BEGIN
Declare #Ids TABLE(Id varchar(14))
;With normal As
(
--Numerate our rows
Select ROW_NUMBER() Over (Order by Cast(Documents.InstrumentID as decimal(18)) as RowNumber,
Documents.InstrumentID
From Documents
)
--Insert three rows from our table with our id and previos/next id
INSERT INTO #Ids(Id)
SELECT TOP(3) normal.InstrumentID
FROM normal
WHERE RowNumber >=
(
SELECT RowNumber - 1
FROM normal
WHERE normal.InstrumentID = #InstrumentID
)
ORDER BY normal.RowNumber
--select next and previos ids
SELECT #PreviousInstrumentID = Min(CAST(Id as decimal(18))),
#NextInstrumentID = MAX(CAST(Id as decimal(18)))
FROM #Ids
END
GO
In MS SQL 2012 we have new window functions like FIRST_VALUE and LAST_VALUE, unfortunately in sql 2008 these functions are missing.
WITH CTE AS (
SELECT rownum = ROW_NUMBER() OVER (ORDER BY p.LogDate), p.LogDate
FROM DeviceLogs p
)
SELECT prev.logdate PreviousValue, CTE.logdate, nex.logdate NextValue
FROM CTE
LEFT JOIN CTE prev ON prev.rownum = CTE.rownum - 1
LEFT JOIN CTE nex ON nex.rownum = CTE.rownum + 1
GO
select
LAG(InstrumentID) OVER (ORDER BY InstrumentID) PreviousValue,
InstrumentID,
LEAD(InstrumentID) OVER (ORDER BY InstrumentID) NextValue
from documents
Hi I think this will be much more efficient:
Select Next :
select top 1 ID from mytable
where ID >'698'
order by ID asc
Select Prev:
select top 1 ID from mytable
where ID <'698'
order by ID desc