MS SQL pagination and grouping - sql-server

I need help, because I'm struggling on how to page through and limiting that each page just show one process at a time. I have tried so many things and my brain got fried. Help........
So if the #PageNum = 1 and #Pagesize = 10 and if the first process has 7 rows; first page will show 7 rows of the first process (exactly what I want).
Now if the user chooses #PageNum = 2 and #Pagesize is 10 and the second process has 11. I want it to show the top 10 of this next process.
Currently it's showing only the last 8 of the second process.
I simplified the SQL to remove business information and then just to show the list of what I'm dealing with:
/*
notes:
Parameters passed in are #PageNum and #PageSize
#PageNum is which page they user goes to
#PageSize is the max number of rows to show
*/
DECLARE #StartRow int, #EndRow int
SET #StartRow = (#PageNum -1) * #PageSize +1;
SET #EndRow = #PageNum * #PageSize;
WITH ProcessestoPageThru AS
(Select Name,
ProcessId,
ROW_NUMBER() OVER(ORDER BY Name, ProcessId, ) as RowNo,
COUNT(Name) OVER() as RowCnt
from a whole bunch of tables
where a whole bunch of criteria
)
SELECT * INTO #tmp_ProcessestoPageThru
From ProcessestoPageThru
Where RowNo BETWEEN #StartRow AND #EndRow
Declare #ProcessID int
--Get the top ProcessID, We are only going to display one Process at a time
Select Top 1 #ProcessID = ProcessId
From #tmp_ProcessestoPageThru
Select *
from #tmp_ProcessestoPageThru
Where ProcessId = #ProcessId
Please see example attached:
Example

Try with other sample data.
If it is slow with real data then let me know with details.
i) you are only passing parameter like #Pageinde,#Pagesize apart from other search criteria.
ii)TotalPage is the number of pages that you get and have to ppolate your page accirdingly.forget the old calculation.
iii) rowcnt is not required.
declare #Pageindex int=1
declare #Pagesize int=10
declare #t table(processid int, rowcnt int)
insert into #t values(1,345),(1,345),(1,345),(1,345),(1,345),(1,345),(1,345)
,(1,345),(1,345),(1,345),(1,345),(1,345)
,(2,345),(2,345),(2,345),(2,345),(2,345),(2,345),(2,345),(2,345)
,(2,345),(2,345),(2,345),(3,345),(3,345)
;with CTE as
(
select *
,DENSE_RANK()over(order by processid) rownum
--,ROW_NUMBER()over(partition by processid order by processid) rownum1
,ROW_NUMBER()over(order by processid) rownum2
from #t
)
,CTE1 AS(
select *,
case when (rownum2/cast(#Pagesize as FLOAT))=1 then (rownum2/#Pagesize )
ELSE (rownum2/#Pagesize )+rownum END PageIndex
from cte c
)
select processid,rownum2
,(select max(PageIndex) from cte1) TotalPage
from cte1
where PageIndex=#Pageindex

You also need to do a PARTITION BY on ProcessId to display the results by that column value. Here is the code that may help you to do that.
;WITH ProcessestoPageThru AS
(
Select Name,
ProcessId,
ROW_NUMBER() OVER( PARTITION BY ProcessId ORDER BY Name, ProcessId ) as RowNo,
COUNT(Name) OVER() as RowCnt
FROM processes
)
SELECT Name,
ProcessId,
RowNo,
RowCnt
From ProcessestoPageThru
Where RowNo BETWEEN #StartRow AND #EndRow
AND ProcessId = #ProcessID

Related

Insert Rows with Prefix and Incrementing Number

I'd like to insert 300 rows where the Username consists of a prefix followed by an incrementing number from 001-300 (Or just 1-300).
For example: PRC001, PRC002, PRC003, PRC004
How would I do this in a single statement?
EDIT: I'm using SSMS 2016 and Microsoft Azure Database
Insert Into YourTable (SomeID)
Select Top 300 Format(Row_Number() Over (Order By Number),'PRC000') From master..spt_values
Another Option would be to create an ad-hoc tally table
;with cte0(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cteN(N) As (Select Row_Number() over (Order By (Select NULL)) From cte0 N1, cte0 N2, cte0 N3)
Select Top 300 Format(N,'PRC000') From cteN
You can use any table (which has more than 300 records) as well
Select Top 300 Format(Row_Number() Over (Order By (Select null)),'PRC000') From AnyLargerTable
The Insert would be
You want to generate those rows and insert them into a table?
Then you can try something like this:
DECLARE #i INTEGER
SET #i = 1
WHILE #i <= 300
BEGIN
PRINT 'PRC' + right('00' + cast(#i AS VARCHAR), 3)
/* add your insert here... */
SET #i = #i + 1
END
Output:
PRC001
PRC002
PRC003
PRC004
PRC005
PRC006
...
PRC298
PRC299
PRC300

Row number with len in SQL Server

I want to take the length of a specific row and show this substring, but I'm not getting luck, can somebody help me I was trying something like this:
declare #teste int
declare #rows int
select #rows = (select COUNT(DS_Description) from IMPL_Activities)
while #rows > 0
begin
--select #test = (select LEN(DS_Description), ROW_NUMBER() over (order by ID_Deployment) rn
--from IMPL_Activities where rn in (#rows))
select
#test = (select *
from
(select
ROW_NUMBER() over(order by ID_Deployment) rn
from IMPL_Activities) as imp
where rn in (#rows)
select LEN(DS_Description) from IMPL_Activities where rn in (#rows))
set #rows = #rows - 1
select SUBSTRING(DS_Description, 1, #test)
from IMPL_Activities
end
But can't save the number of character that have this column in that row, I don't know if did you understood, but comments, or edit, to make sure that everyone will understand.
Thanks
No cursor necessary, is this what you want?
SELECT
SUBSTRING(DS_Description,LEN(DS_Description)/2,1)
FROM
IMPL_Activities;

SQL Server query with pagination and count

I want to make a database query with pagination. So, I used a common-table expression and a ranked function to achieve this. Look at the example below.
declare #table table (name varchar(30));
insert into #table values ('Jeanna Hackman');
insert into #table values ('Han Fackler');
insert into #table values ('Tiera Wetherbee');
insert into #table values ('Hilario Mccray');
insert into #table values ('Mariela Edinger');
insert into #table values ('Darla Tremble');
insert into #table values ('Mammie Cicero');
insert into #table values ('Raisa Harbour');
insert into #table values ('Nicholas Blass');
insert into #table values ('Heather Hayashi');
declare #pagenumber int = 2;
declare #pagesize int = 3;
declare #total int;
with query as
(
select name, ROW_NUMBER() OVER(ORDER BY name ASC) as line from #table
)
select top (#pagesize) name from query
where line > (#pagenumber - 1) * #pagesize
Here, I can specify the #pagesize and #pagenumber variables to give me just the records that I want. However, this example (that comes from a stored procedure) is used to make a grid pagination in a web application. This web application requires to show the page numbers. For instance, if a have 12 records in the database and the page size is 3, then I'll have to show 4 links, each one representing a page.
But I can't do this without knowing how many records are there, and this example just gives me the subset of records.
Then I changed the stored procedure to return the count(*).
declare #pagenumber int = 2;
declare #pagesize int = 3;
declare #total int;
with query as
(
select name, ROW_NUMBER() OVER(ORDER BY name ASC) as line, total = count(*) over()from #table
)
select top (#pagesize) name, total from query
where line > (#pagenumber - 1) * #pagesize
So, along with each line, it will show the total number of records. But I didn't like it.
My question is if there's a better way (performance) to do this, maybe setting the #total variable without returning this information in the SELECT. Or is this total column something that won't harm the performance too much?
Thanks
Assuming you are using MSSQL 2012, you can use Offset and Fetch which cleans up server-side paging greatly. We've found performance is fine, and in most cases better. As far as getting the total column count, just use the window function below inline...it will not include the limits imposed by 'offset' and 'fetch'.
For Row_Number, you can use window functions the way you did, but I would recommend that you calculate that client side as (pagenumber*pagesize + resultsetRowNumber), so if you're on the 5th page of 10 results and on the third row you would output row 53.
When applied to an Orders table with about 2 million orders, I found the following:
FAST VERSION
This ran in under a second. The nice thing about it is that you can do your filtering in the common table expression once and it applies both to the paging process and the count. When you have many predicates in the where clause, this keeps things simple.
declare #skipRows int = 25,
#takeRows int = 100,
#count int = 0
;WITH Orders_cte AS (
SELECT OrderID
FROM dbo.Orders
)
SELECT
OrderID,
tCountOrders.CountOrders AS TotalRows
FROM Orders_cte
CROSS JOIN (SELECT Count(*) AS CountOrders FROM Orders_cte) AS tCountOrders
ORDER BY OrderID
OFFSET #skipRows ROWS
FETCH NEXT #takeRows ROWS ONLY;
SLOW VERSION
This took about 10 sec, and it was the Count(*) that caused the slowness. I'm surprised this is so slow, but I suspect it's simply calculating the total for each row. It's very clean though.
declare #skipRows int = 25,
#takeRows int = 100,
#count int = 0
SELECT
OrderID,
Count(*) Over() AS TotalRows
FROM Location.Orders
ORDER BY OrderID
OFFSET #skipRows ROWS
FETCH NEXT #takeRows ROWS ONLY;
CONCLUSION
We've gone through this performance tuning process before and actually found that it depended on the query, predicates used, and indexes involved. For instance, the second we introduced a view it chugged, so we actually query off the base table and then join up the view (which includes the base table) and it actually performs very well.
I would suggest having a couple of straight-forward strategies and applying them to high-value queries that are chugging.
DECLARE #pageNumber INT = 1 ,
#RowsPerPage INT = 20
SELECT *
FROM TableName
ORDER BY Id
OFFSET ( ( #pageNumber - 1 ) * #RowsPerPage ) ROWS
FETCH NEXT #RowsPerPage ROWS ONLY;
What if you calculate the count beforehand?
declare #pagenumber int = 2;
declare #pagesize int = 3;
declare #total int;
SELECT #total = count(*)
FROM #table
with query as
(
select name, ROW_NUMBER() OVER(ORDER BY name ASC) as line from #table
)
select top (#pagesize) name, #total total from query
where line > (#pagenumber - 1) * #pagesize
Another way, is to calculate max(line). Check the link
Return total records from SQL Server when using ROW_NUMBER
UPD:
For single query, check marc_s's answer on the link above.
with query as
(
select name, ROW_NUMBER() OVER(ORDER BY name ASC) as line from #table
)
select top (#pagesize) name,
(SELECT MAX(line) FROM query) AS total
from query
where line > (#pagenumber - 1) * #pagesize
#pagenumber=5
#pagesize=5
Create a common table expression and write logic like this
Between ((#pagenumber-1)*(#pagesize))+1 and (#pagenumber *#pagesize)
There are many way we can achieve pagination: I hope this information is useful to you and others.
Example 1: using offset-fetch next clause. introduce in 2005
declare #table table (name varchar(30));
insert into #table values ('Jeanna Hackman');
insert into #table values ('Han Fackler');
insert into #table values ('Tiera Wetherbee');
insert into #table values ('Hilario Mccray');
insert into #table values ('Mariela Edinger');
insert into #table values ('Darla Tremble');
insert into #table values ('Mammie Cicero');
insert into #table values ('Raisa Harbour');
insert into #table values ('Nicholas Blass');
insert into #table values ('Heather Hayashi');
declare #pagenumber int = 1
declare #pagesize int = 3
--this is a CTE( common table expression and this is introduce in 2005)
with query as
(
select ROW_NUMBER() OVER(ORDER BY name ASC) as line, name from #table
)
--order by clause is required to use offset-fetch
select * from query
order by name
offset ((#pagenumber - 1) * #pagesize) rows
fetch next #pagesize rows only
Example 2: using row_number() function and between
declare #table table (name varchar(30));
insert into #table values ('Jeanna Hackman');
insert into #table values ('Han Fackler');
insert into #table values ('Tiera Wetherbee');
insert into #table values ('Hilario Mccray');
insert into #table values ('Mariela Edinger');
insert into #table values ('Darla Tremble');
insert into #table values ('Mammie Cicero');
insert into #table values ('Raisa Harbour');
insert into #table values ('Nicholas Blass');
insert into #table values ('Heather Hayashi');
declare #pagenumber int = 2
declare #pagesize int = 3
SELECT *
FROM
(select ROW_NUMBER() OVER (ORDER BY PRODUCTNAME) AS RowNum, * from Products)
as Prodcut
where RowNum between (((#pagenumber - 1) * #pageSize )+ 1)
and (#pagenumber * #pageSize )
I hope these will be helpful to all
I don't like other solutions for being too complex, so here is my version.
Execute three select queries in one go and use output parameters for getting the count values. This query returns the total count, the filter count, and the page rows. It supports sorting, searching, and filtering the source data. It's easy to read and modify.
Let's say you have two tables with one-to-many relationship, items and their prices changed over time so the example query is not too trivial.
create table shop.Items
(
Id uniqueidentifier not null primary key,
Name nvarchar(100) not null,
);
create table shop.Prices
(
ItemId uniqueidentifier not null,
Updated datetime not null,
Price money not null,
constraint PK_Prices primary key (ItemId, Updated),
constraint FK_Prices_Items foreign key (ItemId) references shop.Items(Id)
);
Here is the query:
select #TotalCount = count(*) over()
from shop.Items i;
select #FilterCount = count(*) over()
from shop.Items i
outer apply (select top 1 p.Price, p.Updated from shop.Prices p where p.ItemId = i.Id order by p.Updated desc) as p
where (#Search is null or i.Name like '%' + #Search + '%')/**where**/;
select i.Id as ItemId, i.Name, p.Price, p.Updated
from shop.Items i
outer apply (select top 1 p.Price, p.Updated from shop.Prices p where p.ItemId = i.Id order by p.Updated desc) as p
where (#Search is null or i.Name like '%' + #Search + '%')/**where**/
order by /**orderby**/i.Id
offset #SkipCount rows fetch next #TakeCount rows only;
You need to provide the following parameters to the query:
#SkipCount - how many records to skip, calculated from the page number.
#TakeCount - how many records to return, calculated from or equal to the page size.
#Search - a text to search for in some columns, provided by the grid search box.
#TotalCount - the total number of records in the data source, the output parameter.
#FilterCount - the number of records after the search and filtering operations, the output parameter.
You can replace /**orderby**/ comment with the list of columns and their ordering directions if the grid must support sorting the rows by columns. you get this info from the grid and translate it to an SQL expression. We still need to order the records by some column initially, I usually use ID column for that.
If the grid must support filtering data by each column individually, you can replace /**where**/ comment with an SQL expression for that.
If the user is not searching and filtering the data, but only clicks through the grid pages, this query doesn't change at all and the database server executes it very quickly.

How to make this sql query

I have 2 SQL Server tables with the following structure
Turns-time
cod_turn (PrimaryKey)
time (datetime)
Taken turns
cod_taken_turn (Primary Key)
cod_turn
...
and several other fields which are irrelevant to the problem. I cant alter the table structures because the app was made by someone else.
given a numeric variable parameter, which we will assume to be "3" for this example, and a given time, I need to create a query which looking from that time on, it looks the first 3 consecutive records by time which are not marked as "taken". For example:
For example, for these turns, starting by the time of "8:00" chosen by the user
8:00 (not taken)
9:00 (not taken)
10:00 (taken)
11:00 (not taken)
12:00 (not taken)
13:00 (not taken)
14:00 (taken)
The query it would have to list
11:00
12:00
13:00
I cant figure out how to make the query in pure sql, if possible.
with a cursor
declare #GivenTime datetime,
#GivenSequence int;
select #GivenTime = cast('08:00' as datetime),
#GivenSequence = 3;
declare #sequence int,
#code_turn int,
#time datetime,
#taked int,
#firstTimeInSequence datetime;
set #sequence = 0;
declare turnCursor cursor FAST_FORWARD for
select turn.cod_turn, turn.[time], taken.cod_taken_turn
from [Turns-time] as turn
left join [Taken turns] as taken on turn.cod_turn = taken.cod_turn
where turn.[time] >= #GivenTime
order by turn.[time] asc;
open turnCursor;
fetch next from turnCursor into #code_turn, #time, #taked;
while ##fetch_status = 0 AND #sequence < #GivenSequence
begin
if #taked IS NULL
select #firstTimeInSequence = coalesce(#firstTimeInSequence, #time)
,#sequence = #sequence + 1;
else
select #sequence = 0,
#firstTimeInSequence = null;
fetch next from turnCursor into #code_turn, #time, #taked;
end
close turnCursor;
deallocate turnCursor;
if #sequence = #GivenSequence
select top (#GivenSequence) * from [Turns-time] where [time] >= #firstTimeInSequence
order by [time] asc
WITH Base AS (
SELECT *,
CASE WHEN EXISTS(
SELECT *
FROM Taken_turns taken
WHERE taken.cod_turn = turns.cod_turn) THEN 1 ELSE 0 END AS taken
FROM [Turns-time] turns)
, RecursiveCTE As (
SELECT TOP 1 cod_turn, [time], taken AS run, 0 AS grp
FROM Base
WHERE [time] >= #start_time
ORDER BY [time]
UNION ALL
SELECT R.cod_turn, R.[time], R.run, R.grp
FROM (
SELECT T.*,
CASE WHEN T.taken = 0 THEN 0 ELSE run+1 END AS run,
CASE WHEN T.taken = 0 THEN grp + 1 ELSE grp END AS grp,
rn = ROW_NUMBER() OVER (ORDER BY T.[time])
FROM Base T
JOIN RecursiveCTE R
ON R.[time] < T.[time]
) R
WHERE R.rn = 1 AND run < #run_length
), T AS(
SELECT *,
MAX(grp) OVER () AS FinalGroup,
COUNT(*) OVER (PARTITION BY grp) AS group_size
FROM RecursiveCTE
)
SELECT cod_turn,time
FROM T
WHERE grp=FinalGroup AND group_size=#run_length
I think there is not a simple way to achieve this.
But probably there are many complex ways :). This is an approach that should work in Transact-SQL:
CREATE TABLE #CONSECUTIVE_TURNS (id int identity, time datetime, consecutive int)
INSERT INTO #CONSECUTIVE_TURNS (time, consecutive, 0)
SELECT cod_turn
, time
, 0
FROM Turns-time
ORDER BY time
DECLARE #i int
#n int
SET #i = 0
SET #n = 3 -- Number of consecutive not taken records
while (#i < #n) begin
UPDATE #CONSECUTIVE_TURNS
SET consecutive = consecutive + 1
WHERE not exists (SELECT 1
FROM Taken-turns
WHERE id = cod_turn + #i
)
SET #i = #i + 1
end
DECLARE #firstElement int
SELECT #firstElement = min(id)
FROM #CONSECUTIVE_TURNS
WHERE consecutive >= #n
SELECT *
FROM #CONSECUTIVE_TURNS
WHERE id between #firstElement
and #firstElement + #n - 1
This is untested but I think it will work.
Pure SQL
SELECT TOP 3 time FROM [turns-time] WHERE time >= (
-- get first result of the 3 consecutive results
SELECT TOP 1 time AS first_result
FROM [turns-time] tt
-- start from given time, which is 8:00 in this case
WHERE time >= '08:00'
-- turn is not taken
AND cod_turn NOT IN (SELECT cod_turn FROM taken_turns)
-- 3 consecutive turns from current turn are not taken
AND (
SELECT COUNT(*) FROM
(
SELECT TOP 3 cod_turn AS selected_turn FROM [turns-time] tt2 WHERE tt2.time >= tt.time
GROUP BY cod_turn ORDER BY tt2.time
) AS temp
WHERE selected_turn NOT IN (SELECT cod_turn FROM taken_turns)) = 3
) ORDER BY time
Note: I tested it on Postgresql (with some code modification), but not MS SQL Server. I'm not sure about performance compared to T-SQL.
Another set-based solution (tested):
DECLARE #Results TABLE
(
cod_turn INT NOT NULL
,[status] TINYINT NOT NULL
,RowNumber INT PRIMARY KEY
);
INSERT #Results (cod_turn, [status], RowNumber)
SELECT a.cod_turn
,CASE WHEN b.cod_turn IS NULL THEN 1 ELSE 0 END [status] --1=(not taken), 0=(taken)
,ROW_NUMBER() OVER(ORDER BY a.[time]) AS RowNumber
FROM [Turns-time] a
LEFT JOIN [Taken_turns] b ON a.cod_turn = b.cod_turn
WHERE a.[time] >= #Start;
--SELECT * FROM #Results r ORDER BY r.RowNumber;
SELECT *
FROM
(
SELECT TOP(1) ca.LastRowNumber
FROM #Results a
CROSS APPLY
(
SELECT SUM(c.status) CountNotTaken, MAX(c.RowNumber) LastRowNumber
FROM
(
SELECT TOP(#Len)
b.RowNumber, b.[status]
FROM #Results b
WHERE b.RowNumber <= a.RowNumber
ORDER BY b.RowNumber DESC
) c
) ca
WHERE ca.CountNotTaken = #Len
ORDER BY a.RowNumber ASC
) x INNER JOIN #Results y ON x.LastRowNumber - #Len + 1 <= y.RowNumber AND y.RowNumber <= x.LastRowNumber;

SQL Server Full Text Search Very Slow

I have a stored procedure that searches a table which has about 200000+ rows with full text FREETEXT.
Here is the basics of it:
declare #searchKey varchar(150)
if #searchKey Is Null OR LEN(#searchKey)=0
Set #searchKey='""';
Set #searchKey='car';
declare #perPage int
Set #perPage=40
declare #pageNo int
Set #pageNo=1
declare #startIndex int,#endIndex int;
Set #startIndex=#perPage*#pageNo-#perPage+1;
Set #endIndex=#perPage*#pageNo;
Select totalItems
--i pull other colums as well
from (
Select Row_Number() over(order by CreateDate DESC) As rowNumber
,COUNT(*) OVER() as totalItems
--other columns are pulled as well
from MyTable P
Where
#searchKey='""'
OR FreeText((P.Title,P.Description),#searchKey)
) tempData
--where rowNumber>=#startIndex AND rowNumber<=#endIndex
where
rowNumber>=CASE WHEN #startIndex>0 AND #endIndex>0 THEN #startIndex ELSE rowNumber END
AND rowNumber<=CASE WHEN #startIndex>0 AND #endIndex>0 THEN #endIndex ELSE rowNumber END
order by rowNumber
The problem is its running slower then i would like it. Its taking about 3 seconds to load the page. Same page was loading in less then 1 sec when i was using like operator.
In my experience, full text index functions do not work well in a clause that contains an "OR" operator. I have had to get the same behavior by adjusting my query to use a UNION. Try this and see if you can get better performance.
declare #searchKey varchar(150)
if #searchKey Is Null OR LEN(#searchKey)=0
Set #searchKey='""';
Set #searchKey='car';
declare #perPage int
Set #perPage=40
declare #pageNo int
Set #pageNo=1
declare #startIndex int,#endIndex int;
Set #startIndex=#perPage*#pageNo-#perPage+1;
Set #endIndex=#perPage*#pageNo;
Select totalItems
--i pull other colums as well
from (
Select Row_Number() over(order by CreateDate DESC) As rowNumber
,COUNT(*) OVER() as totalItems
--other columns are pulled as well
from
(
select * from
MyTable A
Where
#searchKey='""'
UNION
select * from MyTable B
where FreeText((B.Title,B.Description),#searchKey)
) as innerTable
) tempData
--where rowNumber>=#startIndex AND rowNumber<=#endIndex
where
rowNumber>=CASE WHEN #startIndex>0 AND #endIndex>0 THEN #startIndex ELSE rowNumber END
AND rowNumber<=CASE WHEN #startIndex>0 AND #endIndex>0 THEN #endIndex ELSE rowNumber END
order by rowNumber

Resources