SQL + ASP.Net Efficient paging - c#

My method of paging is inefficient as it calls the same query twice therefore doubling the query time. I currently call the 1 query that joins about 5 tables together with XML search querys to allow for passing List from ASP.net.. then I need to call exactly the same query except with a Count(row) to get the amount of records
For Example (I have removed bits to make it easier to read)
Main Query:
WITH Entries AS (
select row_number() over (order by DateReady desc)
as rownumber, Columns...,
from quote
join geolookup as Pickup on pickup.geoid = quote.pickupAddress
where
quote.Active=1
and //More
)
select * from entries
where Rownumber between (#pageindex - 1) * #pagesize + 1 and #pageIndex * #pageSize
end
Count Query:
select count(rowID)
from quote
join geolookup as Pickup on pickup.geoid = quote.pickupAddress
where
quote.Active=1
and //More
)

You could select the results of your big query into a temp table, then you could query this table for the row number and pull out the rows you need.
To do this, add (after your select statement and before the from)
INTO #tmpTable
Then reference your table as #tmpTable
select row_number() over (order by DateReady desc)
as rownumber, Columns...,
into #tmpTable
from quote
join geolookup as Pickup on pickup.geoid = quote.pickupAddress
where
quote.Active=1
and //More
)
SELECT #Count = COUNT(*) FROM #tmpTable
select * from #tmpTable
where Rownumber between (#pageindex - 1) * #pagesize + 1 and #pageIndex * #pageSize

You can set an output parameter which will hold the number of rows from the first query.
You could do something like
WITH Entries AS (
select row_number() over (order by DateReady desc)
as rownumber, Columns...,
from quote
join geolookup as Pickup on pickup.geoid = quote.pickupAddress
where
quote.Active=1
and //More
)
select #rowcount = max(rownumber) from entries
select * from entries
where Rownumber between (#pageindex - 1) * #pagesize + 1 and #pageIndex * #pageSize
Hope this helps

Related

How to use limit the results returned from SQL Server using ROW_NUMBER()

I have database as below
CREATE DATABASE Test2;
CREATE TABLE table1
(
name nvarchar(50),
year int,
total1 int,
total2 int
);
INSERT INTO table1 (name, year, total1,total2)
VALUES ('a', 2020, 25,3);
INSERT INTO table1 (name, year, total1,total2)
VALUES ('b', 2018, 33,4);
INSERT INTO table1 (name, year, total1,total2)
VALUES ('c', 2020, 10,3);
INSERT INTO table1 (name, year, total1,total2)
VALUES ('b', 2018, 7,2);
INSERT INTO table1 (name, year, total1,total2)
VALUES ('a', 2020, 20,6);
I want to limit the results returned from SQL Server (take 2nd row and 3rd row) with this code
select
*
from
(select
year, name,
sum(total1) as "sum_Total1",
sum(total2) as "sum_Total2",
round((cast(isnull(sum(total2), 0) as float)) / (cast(sum(total1) as float)), 3) as "sum_Total2/sum_Total1",
row_number() over (order by round((cast(isnull(sum(total2), 0) as float)) / (cast(sum(total1) as float)), 3) asc) as no
from
Table_1
group by
name, year
order by
round((cast(isnull(sum(total2), 0) as float)) * 100 / (cast(sum(total1) as float)), 3) asc) a
where
a.no > 1 and a.no < 3
SQL Server return an error:
The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP, OFFSET or FOR XML is also specified.
Actually since you have already used the Order By in this line ROW_NUMBER() over(ORDER BY ROUND you don't need to use it again in your inner query after grouping. So all that you need to do is removing the unnecessary order by after your group by keyword.
Also FYI, I can see that you've queried from Table_1 while your table name is table1, so you need to fix it as well.
there are two issues:
You are not using the same table which you created.
to get the 2nd and 3rd row, you'll need to change the condition a.no < 3 to a.no <= 3
there is no use of order by clause as we have no.
Finally:
SELECT *
FROM
(
SELECT year,
name,
SUM(total1) AS "sum_Total1",
SUM(total2) AS "sum_Total2",
ROUND((CAST(ISNULL(SUM(total2), 0) AS FLOAT)) / (CAST(SUM(total1) AS FLOAT)), 3) AS "sum_Total2/sum_Total1",
ROW_NUMBER() OVER(
ORDER BY ROUND((CAST(ISNULL(SUM(total2), 0) AS FLOAT)) / (CAST(SUM(total1) AS FLOAT)), 3) ASC) AS no
FROM Table1
GROUP BY name,
year
--ORDER BY ROUND((CAST(ISNULL(SUM(total2), 0) AS FLOAT)) * 100 / (CAST(SUM(total1) AS FLOAT)), 3) ASC
) a
WHERE a.no > 1
AND a.no <= 3;
Just move the order by outside your subquery:
select *
from
(
select year ,name,
sum(total1) as "sum_Total1",
SUM(total2) as "sum_Total2",
ROUND((CAST(ISNULL(sum(total2),0) as float))/
(CAST(sum(total1) as float)),3) as "sum_Total2/sum_Total1",
ROW_NUMBER() over (ORDER BY
ROUND((CAST(ISNULL(sum(total2),0) as float))/ (CAST(sum(total1) as float)),3) ASC ) as no
from Table1
group by name, year
) a
where a.no > 1 and a.no < 4
order by no;

Entity Framework SqlQuery not returning row_number() value

I have a SELECT statement that returns row_number() value with all other fields from table. Query is pretty simple and is working in SSMS:
SELECT
*, CAST(ROW_NUMBER() OVER(ORDER BY TablePrimaryKey) AS INT) AS [RowNumber]
FROM
TableName
WHERE
TablePrimaryKey > 10
(In real application WHERE statement is much more complex, but for simplicity I put it like this)
In my project I created a POCO class that contains RowNumber property (with all other necessary properties)
...
public int RowNumber { get; set; }
...
And I made sure migrations won't create additional columns in table at my entityConfiguration class:
this.Ignore(x => x.RowNumber);
Problem is that with Entity Framework's SqlQuery() method it returns all columns from the table as it should, but RowNumber is always 0. I'm using Entity Framework 6.1 version. Did I miss something, or can this not be done this way?
This is to answer your question in comment.
WITH main AS ( SELECT * ,ROW_NUMBER() OVER(ORDER BY PrimaryKeyId) AS
'RowNumber' FROM [TableName] WHERE [month] = 10 AND [year] = 2014 )
SELECT * FROM main WHERE RowNumber = 2
C#/EF code:
int RowNumber = 2;
var row = TableName.OrderBy(t=>t.PrimaryKeyId).Skip(RowNumber -1).Take(1);
The SQL generated looks like:
DECLARE #p0 Int = 1
DECLARE #p1 Int = 1
SELECT [t1].[ID], [t1].c1 FROM (
SELECT ROW_NUMBER() OVER (ORDER BY [t0].[ID]) AS [ROW_NUMBER],
[t0].[ID], [t0].c1
FROM [TableName] AS [t0]
) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN #p0 + 1 AND #p0 + #p1
ORDER BY [t1].[ROW_NUMBER]
I am using Linq-to-SQL, EF should be similar: the sorting is on server side, and it returns 1 record only.

select top and a selected row in SQL

I have a league with 3000 entrants. What I want to do is select only 50 of these but if the user does not exist in this top 50 I want to select that row also and display their current position, the below query shows I am selecting the top 50 positions from the main inner query which brings back all players and their positions. Now I want to show the current logged in user and their position so is there any way to select top 50 and the user's entry from the subset by amending the below? I.e. is it possible to run two selects on a subset like
SELECT TOP 50 AND SELECT TOP 1 (Where condition)
FROM
(
Subset
)
my Query
SELECT TOP 50 [LeagueID],
[EntryID],
[UserID],
[TotalPoints],
[TotalBonusPoints],
[TotalPointsLastEvnet],
[TotalBonusPointsLastRaceEvent],
[Prize],
[dbo].[GetTotalPool]([LeagueID]) AS [TotalPool],
DENSE_RANK() OVER( PARTITION BY [LeagueID] ORDER BY [TotalPoints] DESC, [TotalBonusPoints] DESC) AS [Position],
DENSE_RANK() OVER( PARTITION BY [LeagueID] ORDER BY [TotalPointsLastRace] DESC, [TotalBonusPointsLastRace] DESC) AS [PositionLastRace]
FROM
(
// inner query here bringing back all entrants
) AS DATA
You can use union for joining two subset of results: http://msdn.microsoft.com/en-au/library/ms180026.aspx
Also if you need to populate result from same suset then you can use common table expression:
http://technet.microsoft.com/en-us/library/ms190766%28v=sql.105%29.aspx
The pseodocode:
with subset(...)
select top 50
union
select top 1
You can do this without a union. You just need or:
WITH data as (
// inner query here bringing back all entrants
)
SELECT * -- or whatever columns you really want
FROM (SELECT data.*
DENSE_RANK() OVER (PARTITION BY [LeagueID]
ORDER BY [TotalPoints] DESC, [TotalBonusPoints] DESC
) AS [Position],
DENSE_RANK() OVER (PARTITION BY [LeagueID]
ORDER BY [TotalPointsLastRace] DESC, [TotalBonusPointsLastRace] DESC
) AS [PositionLastRace],
ROW_NUMBER() OVER (PARTITION BY [LeagueID]
ORDER BY [TotalPoints] DESC, [TotalBonusPoints] DESC
) as Position_Rownum
FROM data
) d
WHERE Position_RowNum <= 50 or UserId = #Current_userid
This uses row_number() to do what you wanted top to do. I note that your question does not include an order by clause, so I am guessing that you want the ordering by Position.
You can use a CTE instead of sub-query and SELECT TOP 50 and then SELECT TOP 1 and union all something like this....
;WITH CTE AS
(
// inner query here bringing back all entrants
)
SELECT TOP 50 * FROM CTE WHERE <Some Codition>
UNION ALL
SELECT TOP 1 * FROM CTE WHERE <Some other Codition>
Please try the following:
;WITH entrants
([LeagueID],[EntryID],[UserID],[TotalPoints],[TotalBonusPoints],[TotalPointsLastEvnet],
[TotalBonusPointsLastRaceEvent],[Prize],[TotalPool],[Position],[PositionLastRace])
AS
(
SELECT [LeagueID],
[EntryID],
[UserID],
[TotalPoints],
[TotalBonusPoints],
[TotalPointsLastEvnet],
[TotalBonusPointsLastRaceEvent],
[Prize],
[dbo].[GetTotalPool]([LeagueID]) AS [TotalPool],
DENSE_RANK() OVER( PARTITION BY [LeagueID] ORDER BY [TotalPoints] DESC, [TotalBonusPoints] DESC) AS [Position],
DENSE_RANK() OVER( PARTITION BY [LeagueID] ORDER BY [TotalPointsLastRace] DESC, [TotalBonusPointsLastRace] DESC) AS [PositionLastRace]
FROM
(
// inner query here bringing back all entrants
) AS DATA
)
SELECT TOP 50 * FROM entrants
UNION
SELECT * FROM entrants WHERE UserID = #current_userid
ORDER BY Position;

Paginate SQL query

On a winform, in a datagrid, I am displaying about 100k rows, selected from the DB. Showing all these records take a lot of time. Is there a way to make the select query faster or maybe load the first 200 records. And then if the users click the next button, the next 200 records will be displayed. Is this possible? I know mysql has LIMIT, but I need something to work for sql-server 2008.
Stored Proc
Alter Proc Test
#PageNumber int,
#PageSize int
as
create table #t
(
id int
)
insert into #t(id)values(1)
insert into #t(id)values(2)
insert into #t(id)values(3)
insert into #t(id)values(4)
insert into #t(id)values(5)
insert into #t(id)values(6)
insert into #t(id)values(7)
insert into #t(id)values(8)
insert into #t(id)values(9)
insert into #t(id)values(10)
declare #StartIndex int
declare #EndIndex int
declare #PageSizeIndex int
Set #StartIndex = ((#PageNumber - 1) * #PageSize) + 1
Set #EndIndex = #PageNumber * #PageSize
Select RowID, ID From
(
Select ROW_NUMBER() Over(Order by id) as RowID, ID From #t
)K
Where K.RowID >= #StartIndex and k.RowID <= #EndIndex
Drop table #t
Testing Purpose Data testing
Test 1, 3
In addition to the above Stored Proc, You can implement Indexes to make the search fast or you can use SQL Profiler to check the reason for delay in execution time.
There's a method to it, but it's not pretty. If you're using Entity Framework, you can write LINQ to paginate results, something like:
var books= context.Books.OrderBy(b => b.Title).Skip(300).Take(100);
If you throw a SQL Profiler on it, the generated SQL will look something like the following, which you can use as a guide to build your own statement:
SELECT TOP (200)
[Extent1].[Id] AS [Id],
[Extent1].[Title] AS [Title]
FROM
(
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Title] AS [Title],
row_number() OVER (ORDER BY [Extent1].[Title] ASC) AS [row_number]
FROM [dbo].[Books] AS [Extent1]
) AS [Extent1]
WHERE
[Extent1].[row_number] > 100
ORDER BY
[Extent1].[Title] ASC
There are several ways to do this, usually using a CTE or nested query and row_number().
See e.g.
How to do pagination in SQL Server 2008
And in SQL 2012, there is now the ability to do this in a single query at last, viz OFFSET xxx ROWS FETCH NEXT XXX ROWS ONLY - see Row Offset in SQL Server
You can use the a Common Table Expression, with the Row_Number ranking function. Here is an example:
CREATE PROCEDURE PagingSample
#PageNumber int,
#PageSize int
AS
WITH Results AS (
SELECT
ROW_NUMBER() OVER(ORDER BY MR.MRN ASC) As RowNumber,
MR.MRN
FROM
dbo.SomeTable MR WITH (NOLOCK)
)
SELECT
R.RowNumber,
R.MRN
FROM
Results R
WHERE
RowNumber > (#PageNumber * #PageSize) - #PageSize
AND RowNumber < (#PageNumber * #PageSize) + 1
Now pass the page number and the size of your page to the sproc, like so:
Exec PagingSample #PageNumber = 3, #PageSize = 100
And you will get records 201 through 300

How to create t-sql to load next n-amount of records?

I need an example of creating t-sql query to load next 10 records (depends on the default row amount in grid).
the same kind of linq has to skip rows.
So for example I have 100K of results I need to load just 10 between 100 and 110 records and so on. The idea is to make it page load very fast
I need also to build paging for my grid so I need to know how many records in total
In MS SQL 2005/2008 you can do something like this
with cte
as
(
select row_number() over (order by ID) RowNumber, *
from MyTable
)
select *
from cte
where RowNumber between 10 and 20
[Edit]
With total count column
select *
from
(
select
row_number() over (order by ID) RowNumber,
count(*) over() TotalRowCount,
*
from MyTable
) tt
where RowNumber between 10 and 20
Try this
SELECT YourColumn1, YourColumn2, RN
FROM
(
SELECT YourTable1.*, ROW_NUMBER() OVER (ORDER BY YourTable1PK) RN
FROM YourTable1
) sq
WHERE sq.rn BETWEEN 10 AND 20
You can use a query like this. It should be fast as long as you have an index on the Records.Id column.
select *
from
(select
row_number() over (order by Id) as [RowNum],
count(*) over() as [TotalCount],
Id from #Records) as R
where
[RowNum] between #StartRow and (#StartRow + #PageSize)
Check out MSDN to find out more about the ROW_NUMBER() function.

Categories

Resources