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
Related
I have a table with Scheduling slots called:
ScheduleSlots
Fields:
id (int)
scheduleID (int)
time (datetime)
availableslots (int)
CalendarGroupID (int)
Level (int)
enabled (bit)
I want to setup a gridview where I take all of the dates and count enabled and disabled for each day.
I am not sure how to go about writing the sql statement to do this.
ie.
Date Enabled Disabled
3/31/2021 20 20
4/1/2021 10 30
SELECT Time, scheduleID,
(SELECT COUNT(Enabled) FROM [dbo].[ScheduleSlots]
WHERE Cast(Time as Date)>='2021-03-31' AND Cast(Time as Date)<='2021-04-01' AND CalendarGroupID=1 AND Level=1 AND Enabled=1) as Enabled,
(SELECT COUNT(Enabled) FROM [dbo].[ScheduleSlots]
WHERE Cast(Time as Date)>='2021-03-31' AND Cast(Time as Date)<='2021-04-01' AND CalendarGroupID=1 AND Level=1 AND Enabled=0) as Disabled
FROM [dbo].[ScheduleSlots]
WHERE Cast(Time as Date)>='2021-03-31' AND Cast(Time as Date)<='2021-04-01' AND CalendarGroupID=1 AND Level=1
GROUP BY scheduleID, Time
The results I end up with:
[Results][1]
You could do it without inner selects:
SELECT [Time],
SUM([Enabled]) as [Enabled],
SUM([Disabled]) as [Disabled]
FROM [dbo].[ScheduleSlots]
WHERE
[Time]>='2021-03-31'
AND [Time]<='2021-04-01'
AND CalendarGroupID=1
AND Level=1
GROUP BY Cast([Time] as Date)
It is an easy grouping query:
SELECT
CONVERT(DATE, "Time") AS "Date",
SUM(CONVERT(INT, "Enabled")) AS "Enabled",
COUNT() - SUM(CONVERT(INT, "Enabled")) AS "Disabled"
FROM "dbo"."ScheduleSlots"
WHERE "CalendarGroupID" = 1
AND "Level" = 1
GROUP BY CONVERT(DATE, "Time")
I hope I understand you correctly where you want the enable slot and diable slot for each day.
;With CTE(Transdate) as (Select Distinct([time]) from ScheduleSlots)
Select CTE.Transdate,(Select COUNT(Id) from ScheduleSlots where enabled= 1
and Transdate = CTE.Transdate) as Enable,
(Select COUNT(Id) from ScheduleSlots where enabled= 0
and Transdate = CTE.Transdate) as Disable from CTE
I inserted some records to make it clear. If I am wrong with inserted data please send me the insert scripts so, that I can test the results.
CREATE TABLE ScheduleSlots
(
id int,
scheduleID int,
[time] datetime,
availableslots int,
CalendarGroupID int,
[Level] int,
[enabled] bit
)
----insert into ScheduleSlots values(1,1,'2021-03-31',40,1,1,20)
INSERT INTO ScheduleSlots VALUES(1,1,'2021-03-31',20,1,1,0)
INSERT INTO ScheduleSlots VALUES(1,1,'2021-03-31',20,1,1,1)
INSERT INTO ScheduleSlots VALUES(1,1,'2021-03-31',20,1,1,1)
INSERT INTO ScheduleSlots VALUES(1,1,'2021-04-01',40,1,1,1)
INSERT INTO ScheduleSlots VALUES(1,1,'2021-04-01',40,1,1,0)
SELECT DISTINCT
[time] AS [Date]
,SUM(availableslots) OVER(PARTITION BY [time] ORDER BY [time]) AS [avaibale]
,SUM(CAST([enabled] AS INT)) OVER(PARTITION BY [time] ORDER BY [time]) AS [Enabled]
,SUM(availableslots) OVER(PARTITION BY [time] ORDER BY [time]) -
SUM(CAST([enabled] AS INT)) OVER(PARTITION BY [time] ORDER BY [time]) AS [Disabled]
FROM ScheduleSlots
Answer I got:
I'm inserting DataTable in Database using StoredProcedure but the issue is, its inserting twice the actual number of entries of DataTable to be inserted, the procedure is below, kindly guide me, if I'm using wrong approach, why its duplicating the rows? The return which is required is working fine.
Thanks In Advance
ALTER PROCEDURE [dbo].[proc_InsertStore_Recvry]
(#dt_Recovery Recovery_Store READONLY)
AS
Declare #RecoveryIDs as Table (IDs int, ClientIds int)
declare #StoreID int
declare #ClientID int
declare #Arrears decimal(18, 2)
declare #NetDues decimal(18, 2)
declare #Received decimal(18, 2)
Declare #RecoveryRecID int
begin
select * into #tempTable from #dt_Recovery
declare #Count int
set #Count= (select COUNT(*) from #tempTable)
while(#Count > 0)
begin
set #Count = #Count-1
set #ClientID = (Select top 1 ClientID from #tempTable)
set #StoredID = (Select top 1 StoredID from #tempTable where ClientID=#ClientID)
set #Arrears = (Select top 1 Arrears from #tempTable where ClientID=#ClientID)
set #NetDues = (Select top 1 NDues from #tempTable where ClientID=#ClientID)
set #Received = (Select top 1 Received from #tempTable where ClientID=#ClientID)
Insert into tblRecovery (StoreID, ClientID, Arrears, NetDues, Received)
values (#StoreID,#ClientID,#Arrears,#NetDues,#Received)
select #RecoveryID = Scope_Identity()
insert into #RecoveryIDs (IDs,ClientIds) values (#RecoveryID, #ClientID )
delete from #tempTable where ClientID=#ClientID
end
Select * from #RecoveryIDs
it looks like you are using SQL Server. If yes then why are you using a while-loop to insert values into a table and return the inserted Ids?
The same can be accomplished in a far better way via the OUTPUT clause:
OUTPUT documentation
Example:
INSERT INTO tblRecovery(StoreID, ClientID, Arrears, NetDues, Received) OUTPUT INSERTED.ID, INSERTED.CLientId INTO #RecoveryIDs(IDs, ClientIds) SELECT StoredID, ClientID, Arrears, NDues, Received FROM #tempTable
Aside from that there seems to be no issue with your SQL code. So could you post the .NET code as well?
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.
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
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.