Select TOP N and set variable if more could be selected - sql

Is there a more efficient way of doing the following SQL?
I want to select the top 50 results, but I also want to set a variable to tell me if I would have gotten more results back without the TOP
DECLARE #MoreExists BIT
SET #MoreExists = 0
DECLARE #Count INT
SELECT #Count = Count(*)
FROM MyTable WHERE ... --some expensive where clause
IF #Count > 50
SET #MoreExists = 1
SELECT TOP 50 Field1, Field2, ...
FROM MyTable WHERE ... --same expensive where clause

Select 51 results instead, use the top 50 in the client layer, and use the count to know if there are more.

A spin on #Dougs answer
SET NOCOUNT ON
SELECT TOP 51 Field1, Field2, ...
into #t
FROM MyTable WHERE ... --same expensive where clause
if ##rowcount > 50
SET #MoreExists = 1
SET NOCOUNT OFF
SELECT TOP 50 Field1, Field2, ...
from #t
-- maintain ordering with an order by clause

Yes.
The common approach is to use ROW_NUMBER():
WITH MyTableEntries AS
(
SELECT ROW_NUMBER() OVER (ORDER BY Date DESC) AS Row, col1, col2
FROM MyTable
WHERE
-- some expensive WHERE clause
)
SELECT col1, col2
FROM MyTableEntries
WHERE Row BETWEEN(#PageIndex - 1) * #PageSize + 1 and #PageIndex*#PageSize
The efficient approach shown at this SqlServercentral article:
DECLARE #startRow INT ; SET #startrow = 50
;WITH cols
AS
(
SELECT table_name, column_name,
ROW_NUMBER() OVER(ORDER BY table_name, column_name) AS seq,
ROW_NUMBER() OVER(ORDER BY table_name DESC, column_name desc) AS totrows
FROM [INFORMATION_SCHEMA].columns
)
SELECT table_name, column_name, totrows + seq -1 as TotRows
FROM cols
WHERE seq BETWEEN #startRow AND #startRow + 49
ORDERBY seq

How about using COUNT(*) OVER... in a sub-query?
DECLARE #ReqCount int;
SET #ReqCount = 50;
SELECT TOP (#ReqCount) *
FROM
(
SELECT *, Count(*) OVER() AS TotalCnt
FROM MyTable WHERE ...
) t
ORDER BY ...
;
And if you want to use ROW_NUMBER() too, then try:
SELECT *
FROM
(
SELECT *, ROW_NUMBER() OVER (ORDER BY ...) AS RowNum, Count(*) OVER() AS TotalCnt
FROM MyTable WHERE ...
) t
WHERE RowNum BETWEEN #StartRange AND #EndRange
ORDER BY ...
;
And then you can easily check to see if TotalCnt > #ReqCount (or #EndRange), to be able to see if there are more to fetch.
Rob

Related

Total Count in paging query taking more time in SQL Server

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

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)

Take top count dynamically

I want to create a stored procedure which takes integer values as a #top from me so that I can use it in my query, but it's not allowing me to set value of top dynamically.
select top #top * from (select url,
count(1) as shared from tblshared
group by url, uniqid having
uniqid = #uniqid) as sha order by
shared desc
I want to retrieve top n records from table so I want to pass the value of n in stored procedure and it will return me that number of top records.
Note: I don't want to use exec.
Thanks.
That will work fine if you wrap #top in brackets
select top (#top) *
from (
select url, count(1) as shared
from tblshared
group by url, uniqid
having uniqid=#uniqid) as sha
order by shared desc
You can use ROW_NUMBER() isntead of top
with t1 as(
select url,row_number() over(
partition by url, uniqid order by url, uniqid desc) as shared
from tblshared
where uniqid = #uniqid )
select * from t1 where shared < #top
REF
You could use SET ROWCOUNT:
SET ROWCOUNT #top
SELECT ...
SET ROWCOUNT 0
declare #v1 int
set #v1 = 25
set rowcount #v1
select * from MyTable Order by DateColumn
set rowcount 0

What is the easiest way to find a sql query returns a result or not?

Consider the following sql server query ,
DECLARE #Table TABLE(
Wages FLOAT
)
INSERT INTO #Table SELECT 20000
INSERT INTO #Table SELECT 15000
INSERT INTO #Table SELECT 10000
INSERT INTO #Table SELECT 45000
INSERT INTO #Table SELECT 50000
SELECT *
FROM (
SELECT *,
ROW_NUMBER() OVER(ORDER BY Wages DESC) RowID
FROM #Table
) sub
WHERE RowID = 3
The result of the query would be 20000 ..... Thats fine as of now i have to find the result of this query,
SELECT *
FROM (
SELECT *,
ROW_NUMBER() OVER(ORDER BY Wages DESC) RowID
FROM #Table
) sub
WHERE RowID = 6
It will not give any result because there are only 5 rows in the table.....
so now my question is
What is the easiest way to find a sql query returns a result or not?
Use ##ROWCOUNT > 0
So as a simple example,
SELECT *
FROM (
SELECT *,
ROW_NUMBER() OVER(ORDER BY Wages DESC) RowID
FROM #Table
) sub
WHERE RowID = 6
IF ##ROWCOUNT > 0 BEGIN
RETURN 1
END
ELSE BEGIN
RETURN 0
END
For more information, here's a link to the documentation.
Like this:
SELECT ##rowcount