How to use keyset pagination? - sql

Suppose I have total 800 eligible rows in database which is ordered by a column requisitionid in descending order. I want to display the records in 80 pages each page having 10 rows. We are using requisitionid as seek predicate. So the predicate should be less than or greater than? As the query will progress from UI (Angular + primeNG), I want to send just one parameter - requisitionid. If it is less than query, then the query will be SELECT ... FROM ... where requisitionid < ?, so here we are talking about first row. If we go for greater than query i.e. SELECT ... FROM ... where requisitionid > ?, here we are talking about last row of the page.
Please refer to Life without offset
EDIT
Actual code:
with topten as (SELECT DISTINCT TOP 10
REQN.CASE_ID
,userContact2.BV_First_Name + ' ' + userContact2.BV_Last_Name ReQCreater
,REQN.BV_Internal_Job_Title
,REQN.BV_Posted_Job_Title as postedJobTitle
,REQN.BV_Status
,REQN.BV_Taleo_Id
,REQN.BV_WD_PositionID
,jobcode.BV_Job_Code
,loc.BV_LocationCode
,loc.BV_LocationName
,D.BV_Division_Code AS 'divCode',
ISNULL(loc.BV_Address1,'') + CASE WHEN ISNULL(loc.BV_Address1,'') = '' THEN '' ELSE ', ' END + ISNULL(loc.BV_Address2,'') + CASE WHEN ISNULL(loc.BV_Address2,'') = '' THEN '' ELSE ', ' END
+ ISNULL(loc.BV_City,'') + CASE WHEN ISNULL(loc.BV_City,'') = '' THEN '' ELSE ', ' END + ISNULL(loc.BV_State,'') + CASE WHEN ISNULL(loc.BV_State,'') = '' THEN '' ELSE (case when ISNULL(loc.BV_ZipCode,'') = '' THEN '' ELSE ', ' END) END
+ ISNULL(loc.BV_ZipCode,'') AS locationAddress
from dbo.CW_V_REQN as REQN
INNER JOIN dbo.CW_TL_Requisition__Location_Master as reqLocLink on REQN.CASE_ID = reqLocLink.FROM_ID
INNER JOIN dbo.CW_V_LOCTMAST as loc on loc.CASE_ID = reqLocLink.TO_ID
INNER JOIN dbo.CW_TL_UserContactInfo__Location_Master as locUserLink on locUserLink.TO_ID = loc.CASE_ID
INNER JOIN dbo.CW_V_USERCONT as userContact on userContact.CASE_ID = locUserLink.FROM_ID
LEFT JOIN dbo.CW_TL_Requisition__Department as reqDeptLink on REQN.CASE_ID = reqDeptLink.FROM_ID
INNER JOIN dbo.CW_V_DEPARTME as dept on dept.CASE_ID = reqDeptLink.TO_ID
LEFT JOIN dbo.CW_TL_UserContactInfo__Department_Master as deptUserLink on dept.CASE_ID=deptUserLink.TO_ID
INNER JOIN dbo.CW_TL_Requisition__Job_Code as reqJobLink on REQN.CASE_ID = reqJobLink.FROM_ID
LEFT JOIN dbo.CW_V_JOBCODE as jobcode on jobcode.CASE_ID = reqJobLink.TO_ID
LEFT JOIN dbo.CW_V_USERCONT as userContact2 on (userContact2.BV_Login_Name = REQN.CREATED_BY)
LEFT JOIN CW_TL_LocationMaster__Division_Master LD ON (LD.FROM_ID = loc.CASE_ID)
LEFT JOIN CW_V_DIVISION D ON (D.CASE_ID = LD.TO_ID)
WHERE userContact.BV_Login_Name = #LOGINNAME
AND REQN.CASE_ID < #MINCASEIDPREVPAGE
ORDER BY REQN.CASE_ID DESC
)
select topten.*
, T.*
from topten
cross join (select min(case_id) as min from topten) as T

For key based pagination on a descending key, the WHERE clause predicate should be < for the next page and > for the previous page. Also, the ORDER BY clause for the previous page needs to be ASC (for the TOP predicate) along an outer DESC (for the descending key display sequence). Below is an example.
--create test table with sample data
CREATE TABLE dbo.YourTable(
requisitionid int PRIMARY KEY
);
WITH
t10 AS (SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) t(n))
,t1000 AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS num FROM t10 AS a CROSS JOIN t10 AS b CROSS JOIN t10 AS c)
INSERT INTO dbo.YourTable WITH(TABLOCKX)
SELECT num
FROM t1000
WHERE num <= 800;
GO
--query for first page (page 1) on descending key (returns 800-791)
SELECT TOP(10) requisitionid
FROM YourTable
ORDER BY requisitionid DESC;
GO
--query for next page (page 2) on descending key (returns 790-781)
DECLARE #LastRequisitionIdOnPage int = 791;
SELECT TOP(10) requisitionid
FROM YourTable
WHERE requisitionid < #LastRequisitionIdOnPage
ORDER BY requisitionid DESC;
GO
--query for previous page (page 1) on descending key (returns 800-791)
DECLARE #FirstRequisitionIdOnPage int = 790;
SELECT requisitionid
FROM (
SELECT TOP(10) requisitionid
FROM YourTable
WHERE requisitionid > #FirstRequisitionIdOnPage
ORDER BY requisitionid ASC
) AS prev_page
ORDER BY requisitionid DESC;;
GO

If I understand your question, you should be able to use the follow clause at the end of your SELECT query:
OFFSET (#Page * 10) ROWS FETCH NEXT 10 ROWS ONLY
If this is not what you are looking for, please post your current query so we can see what you are doing now.

Related

How to ignore duplicate records in CTE Select statement?

I am trying to ignore duplicate records in CTE but I am not able to do that, It seems like a SELECT statement inside CTE does not allow to use ROWNUM() variable numrows to condition in WHERE clause as it is showing Invalid column name 'numrows' error while trying to do so.
SQL Query:
DECLARE #BatchID uniqueidentifier = NEWID();
DECLARE #ClusterID SMALLINT = 1;
DECLARE #BatchSize integer = 20000;
DECLARE #myTableVariable TABLE(EventID BIGINT,HotelID int, BatchStatus varchar(50),BatchID uniqueidentifier);
WITH PendingExtResSvcEventsData_Batch
AS(
SELECT TOP (#BatchSize) t.EventID, t.HotelID, t.BatchStatus, t.BatchID, ROW_NUMBER() OVER (PARTITION BY t.EventID ORDER BY t.EventID) numrows
FROM ExtResSvcPendingMsg t WITH (NOLOCK)
WHERE t.ClusterID = #ClusterID AND numrows = 1 AND NOT EXISTS -- not allowed to use WHERE numrows = 1 here showing *Invalid Column Name*
(select 1 from ExtResSvcPendingMsg t2 where t2.BatchStatus = 'Batched'
and t2.EventID = t.EventID and t2.HotelID = t.HotelID)
)
UPDATE PendingExtResSvcEventsData_Batch
SET BatchStatus='Batched',
BatchID = #BatchID
-- WHERE numrows = 1 (not allowed to use WHERE here because of OUTPUT Clause)
OUTPUT INSERTED.* INTO #myTableVariable
SELECT e.ExtResSvcEventID,e.HotelID,e.ID1,e.ID2,e.ExtResSvcEventType,e.HostID,e.StatusCode,e.ChannelID,e.RequestAtTime,e.ProcessTime,e.DateBegin,e.DateEnd,
e.StatusMsg,em.MsgBodyOut,em.MsgBodyIn,e.ChannelResID
FROM ExtResSvcEvent e WITH (NOLOCK)
INNER JOIN #myTableVariable t ON e.ExtResSvcEventID = t.EventID
INNER JOIN ExtResSvcEventXML em with (nolock) on t.EventID = em.ExtResSvcEventID
ORDER BY e.ExtResSvcEventID
I have also tried to use numrows in final SELECT like INNER JOIN #myTableVariable t ON e.ExtResSvcEventID = t.EventID AND t.numrows = 1 but this gives me a error i.e. The column reference "inserted.numrows" is not allowed because it refers to a base table that is not being modified in this statement.
How do I ignore the duplicate records while using SELECT in CTE?
You can't refer to the numrows column in the WHERE clause of the CTE because that column is not calculated at this point in the plan execution. You need to add a second CTE with a select statement where you can refer to the numrows column:
WITH Base AS (
SELECT TOP (#BatchSize) t.EventID, t.HotelID, t.BatchStatus, t.BatchID, ROW_NUMBER() OVER (PARTITION BY t.EventID ORDER BY t.EventID) numrows
FROM ExtResSvcPendingMsg t WITH (NOLOCK)
WHERE t.ClusterID = #ClusterID
AND NOT EXISTS (select 1 from ExtResSvcPendingMsg t2 where t2.BatchStatus = 'Batched' and t2.EventID = t.EventID and t2.HotelID = t.HotelID)
), PendingExtResSvcEventsData_Batch AS (
SELECT EventID,
HotelID,
BatchStatus,
BatchID
WHERE numrows = 1
)
UPDATE...
I can't vouch for the update statement working as you expect it but the PendingExtResSvcEventsData_Batch should now have one row per EventID.

Replace no result

I have a query like this:
SELECT TV.Descrizione as TipoVers,
sum(ImportoVersamento) as ImpTot,
count(*) as N,
month(DataAllibramento) as Mese
FROM PROC_Versamento V
left outer join dbo.PROC_TipoVersamento TV
on V.IDTipoVersamento = TV.IDTipoVersamento
inner join dbo.PROC_PraticaRiscossione PR
on V.IDPraticaRiscossioneAssociata = PR.IDPratica
inner join dbo.DA_Avviso A
on PR.IDDatiAvviso = A.IDAvviso
where DataAllibramento between '2012-09-08' and '2012-09-17' and A.IDFornitura = 4
group by V.IDTipoVersamento,month(DataAllibramento),TV.Descrizione
order by V.IDTipoVersamento,month(DataAllibramento)
This query must always return something. If no result is produced a
0 0 0 0
row must be returned. How can I do this. Use a isnull for every selected field isn't usefull.
Use a derived table with one row and do a outer apply to your other table / query.
Here is a sample with a table variable #T in place of your real table.
declare #T table
(
ID int,
Grp int
)
select isnull(Q.MaxID, 0) as MaxID,
isnull(Q.C, 0) as C
from (select 1) as T(X)
outer apply (
-- Your query goes here
select max(ID) as MaxID,
count(*) as C
from #T
group by Grp
) as Q
order by Q.C -- order by goes to the outer query
That will make sure you have always at least one row in the output.
Something like this using your query.
select isnull(Q.TipoVers, '0') as TipoVers,
isnull(Q.ImpTot, 0) as ImpTot,
isnull(Q.N, 0) as N,
isnull(Q.Mese, 0) as Mese
from (select 1) as T(X)
outer apply (
SELECT TV.Descrizione as TipoVers,
sum(ImportoVersamento) as ImpTot,
count(*) as N,
month(DataAllibramento) as Mese,
V.IDTipoVersamento
FROM PROC_Versamento V
left outer join dbo.PROC_TipoVersamento TV
on V.IDTipoVersamento = TV.IDTipoVersamento
inner join dbo.PROC_PraticaRiscossione PR
on V.IDPraticaRiscossioneAssociata = PR.IDPratica
inner join dbo.DA_Avviso A
on PR.IDDatiAvviso = A.IDAvviso
where DataAllibramento between '2012-09-08' and '2012-09-17' and A.IDFornitura = 4
group by V.IDTipoVersamento,month(DataAllibramento),TV.Descrizione
) as Q
order by Q.IDTipoVersamento, Q.Mese
Use COALESCE. It returns the first non-null value. E.g.
SELECT COALESCE(TV.Desc, 0)...
Will return 0 if TV.DESC is NULL.
You can try:
with dat as (select TV.[Desc] as TipyDesc, sum(Import) as ToImp, count(*) as N, month(Date) as Mounth
from /*DATA SOURCE HERE*/ as TV
group by [Desc], month(Date))
select [TipyDesc], ToImp, N, Mounth from dat
union all
select '0', 0, 0, 0 where (select count (*) from dat)=0
That should do what you want...
If it's ok to include the "0 0 0 0" row in a result set that has data, you can use a union:
SELECT TV.Desc as TipyDesc,
sum(Import) as TotImp,
count(*) as N,
month(Date) as Mounth
...
UNION
SELECT
0,0,0,0
Depending on the database, you may need a FROM for the second SELECT. In Oracle, this would be "FROM DUAL". For MySQL, no FROM is necessary

MySQL/SQL - When are the results of a sub-query avaliable?

Suppose I have this query
SELECT * FROM (
SELECT * FROM table_a
WHERE id > 10 )
AS a_results LEFT JOIN
(SELECT * from table_b
WHERE id IN
(SElECT id FROM a_results)
ON (a_results.id = b_results.id)
I would get the error "a_results is not a table". Anywhere I could use the re-use the results of the subquery?
Edit: It has been noted that this query doesn't make sense...it doesn't, yes. This is just to illustrate the question which I am asking; the 'real' query actually looks something like this:
SELECT SQL_CALC_FOUND_ROWS * FROM
( SELECT wp_pod_tbl_hotel . *
FROM wp_pod_tbl_hotel, wp_pod_rel, wp_pod
WHERE wp_pod_rel.field_id =12
AND wp_pod_rel.tbl_row_id =1
AND wp_pod.tbl_row_id = wp_pod_tbl_hotel.id
AND wp_pod_rel.pod_id = wp_pod.id
) as
found_hotel LEFT JOIN (
SELECT COUNT(*) as review_count, avg( (
location_rating + staff_performance_rating + condition_rating + room_comfort_rating + food_rating + value_rating
) /6 ) AS average_score, hotelid
FROM (
SELECT r. * , wp_pod_rel.tbl_row_id AS hotelid
FROM wp_pod_tbl_review r, wp_pod_rel, wp_pod
WHERE wp_pod_rel.field_id =11
AND wp_pod_rel.pod_id = wp_pod.id
AND r.id = wp_pod.tbl_row_id
AND wp_pod_rel.tbl_row_id
IN (
SELECT wp_pod_tbl_hotel .id
FROM wp_pod_tbl_hotel, wp_pod_rel, wp_pod
WHERE wp_pod_rel.field_id =12
AND wp_pod_rel.tbl_row_id =1
AND wp_pod.tbl_row_id = wp_pod_tbl_hotel.id
AND wp_pod_rel.pod_id = wp_pod.id
)
) AS hotel_reviews
GROUP BY hotel_reviews.hotelid
ORDER BY average_score DESC
AS sorted_hotel ON (id = sorted_hotel.hotelid)
As you can see, the sub-query which makes up the found_query table is repeated elsewhere downward as another sub-query, so I was hoping to re-use the results
You can not use a sub-query like this.
I'm not sure I understand your query, but wouldn't that be sufficient?
SELECT * FROM table_a a
LEFT JOIN table_b b ON ( b.id = a.id )
WHERE a.id > 10
It would return all rows from table_a where id > 10 and LEFT JOIN rows from table_b where id matches.

SQL get single value inside existing query?

I have a query that returns a bunch of rows.
But using the same query i would like to:
1. get the total row count in the table
2. get the row number where a certian username is located
Right now im doing like so:
BEGIN
DECLARE #startRowIndex INT;
DECLARE #PageIndex INT;
DECLARE #RowsPerPage INT;
SET #PageIndex = 0;
SET #RowsPerPage = 15;
SET #startRowIndex = (#PageIndex * #RowsPerPage) + 1;
WITH messageentries
AS (SELECT Row_number()
OVER(ORDER BY score DESC) AS row,
Count(DISTINCT town.townid) AS towns,
user_details.username,
user_score.score,
allience.alliencename,
allience.allienceid,
allience.alliencetagname,
(SELECT Count(* ) FROM user_details) AS numberofrows
FROM user_details
INNER JOIN user_score
ON user_details.username = user_score.username
INNER JOIN town
ON user_details.username = town.townownername
LEFT OUTER JOIN allience_roles
ON user_details.useralliencerole = allience_roles.roleid
LEFT OUTER JOIN allience
ON allience_roles.allienceid = allience.allienceid
GROUP BY user_details.username,
user_score.score,
allience.alliencename,
allience.allienceid,
allience.alliencetagname)
SELECT *, (SELECT row FROM messageentries WHERE username = 'myUsername') AS myself
FROM messageentries
WHERE row BETWEEN #startRowIndex AND #StartRowIndex + #RowsPerPage - 1
END
That works, but isn't the two nested selects going to run once for every row in the table? :/
...
(SELECT Count(* ) FROM user_details) AS numberofrows
...
(SELECT row FROM messageentries WHERE username = 'myUsername') AS myself
So my question being how can i get the values i want as "low-cost" as possible, and preferably in the same query?
Thanks in advance :)
try this...
DECLARE #NumberOfRows INT
SELECT #NumberOfRows = Count(* ) FROM user_details
WITH messageentries
AS (SELECT Row_number()
OVER(ORDER BY score DESC) AS row,
Count(DISTINCT town.townid) AS towns,
user_details.username,
user_score.score,
allience.alliencename,
allience.allienceid,
allience.alliencetagname,
#NumberOfRows AS numberofrows
FROM user_details
INNER JOIN user_score
ON user_details.username = user_score.username
INNER JOIN town
ON user_details.username = town.townownername
LEFT OUTER JOIN allience_roles
ON user_details.useralliencerole = allience_roles.roleid
LEFT OUTER JOIN allience
ON allience_roles.allienceid = allience.allienceid
GROUP BY user_details.username,
user_score.score,
allience.alliencename,
allience.allienceid,
allience.alliencetagname)
SELECT *, MyRowNumber.row AS myself
FROM messageentries,
(SELECT row FROM messageentries WHERE username = 'myUsername') MyRowNumber
WHERE row BETWEEN #startRowIndex AND #StartRowIndex + #RowsPerPage - 1
(SELECT Count(* ) FROM user_details)
This one will be cached (most probably materialized in a Worktable).
(SELECT row FROM messageentries WHERE username = 'myUsername')
For this one, most probably a Lazy Spool (or Eager Spool) will be built, which will be used to pull this value.

MSSQL Paging is returning random rows when not supposed too

I'm trying to do some basic paging in MSSQL. The problem I'm having is that I'm sorting the paging on a row that (potentially) has similar values, and the ORDER BY clause is returning "random" results, which doesn't work well.
So for example.
If I have three rows, and I'm sorting them by a "rating", and all of the ratings are = '5' - the rows will seemingly "randomly" order themselves. How do I make it so the rows are showing up in the same order everytime?
I tried ordering it by a datetime that the field was last edited, but the "rating" is sorted in reverse, and again, does not work how i expect it to work.
Here is the SQL I'm using thus far. I know it's sort of confusing without the data so.. any help would be greatful.
SELECT * FROM
(
SELECT
CAST(grg.defaultthumbid AS VARCHAR) + '_' +
CAST(grg.garageid AS VARCHAR) AS imagename,
(
SELECT COUNT(imageid)
FROM dbo.images im (nolock)
WHERE im.garageid = grg.garageid
) AS piccount,
(
SELECT COUNT(commentid)
FROM dbo.comments cmt (nolock)
WHERE cmt.garageid = grg.garageid
) AS commentcount,
grg.GarageID, mk.make, mdl.model, grg.year,
typ.type, usr.username, grg.content,
grg.rating, grg.DateEdit as DateEdit,
ROW_NUMBER() OVER (ORDER BY Rating DESC) As RowIndex
FROM
dbo.garage grg (nolock)
LEFT JOIN dbo.users (nolock) AS usr ON (grg.userid = usr.userid)
LEFT JOIN dbo.make (nolock) AS mk ON (grg.makeid = mk.makeid)
LEFT JOIN dbo.type (nolock) AS typ ON (typ.typeid = mk.typeid)
LEFT JOIN dbo.model (nolock) AS mdl ON (grg.modelid = mdl.modelid)
WHERE
typ.type = 'Automobile' AND
grg.defaultthumbid != 0 AND
usr.username IS NOT NULL
) As QueryResults
WHERE
RowIndex BETWEEN (2 - 1) * 25 + 2 AND 2 * 25
ORDER BY
DateEdit DESC
Try ordering by both, e.g.:
ORDER BY Rating DESC, DateEdit ASC
The query first numbers the rows by [Rating], and then re-sorts the results by [DateEdit]. Possibly not what you intended. Ordering by [RowIndex] ASC should sort it out.
ROW_NUMBER() OVER (ORDER BY [Rating] DESC) As [RowIndex]
...
ORDER BY [RowIndex]