Getting rows within a specified range for pagination - sql

I am trying to write a query to implement pagination, my basic requirements is that I need a query where I can give min and max range of rows to return for e.g. for page 1 I need record from 1 – 10 for page to 11-20 and so on and so forth.
Through some help form internet and here at SO I have written down the following query but it’s not really working out that way it should and returning me a big sum of rows whatever the range is (probably I am missing some join in the query)
SELECT b.id,b.title,b.name
FROM (
SELECT ROW_NUMBER() OVER(ORDER BY (select NULL as noorder)) AS RowNum, *
FROM [student] b
) as alias,[student] b,[class] c
WHERE b.[status]=1
AND c.id=b.class
AND c.name='Science'
AND RowNum BETWEEN 1 AND 5
ORDER BY b.dtetme DESC
I am lost while fixing in it, can someone please point out the mistake.
Thank you!

Your whole query logic + ROW_NUMBER should go in the sub-query. You use outer WHERE just for paging.
ROW_NUMBER must have ORDER BY on which paging is to be implemented.
SELECT a.id ,
a.title ,
a.name
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY b.dtetme DESC) AS RowNum, b.*
FROM [student] b
INNER JOIN [class] c ON c.id = b.class
WHERE b.[status] = 1
AND c.name = 'Science'
) a
WHERE RowNum BETWEEN 1 AND 10 -- change numbers here for pages
ORDER BY t.RowNum

I think the problem is with th addition of [student] b in the FROM, try moving the join into the subquery.
SELECT a.id, a.title, a.name
FROM (
SELECT ROW_NUMBER() OVER(ORDER BY (select NULL as noorder)) AS RowNum, *
FROM [student] b
JOIN [class] c ON c.id = b.class
WHERE b.[status]=1
AND c.name='Science'
) as a
WHERE a.RowNum BETWEEN 1 AND 5
ORDER BY a.dtetme DESC
Also you may want to consider wrapping this in a procedure or function so you can change the range.

It seems you want something like this:
SELECT t.id,t.title,t.name FROM (
SELECT s.id,s.title,s.name, RowNum = ROW_NUMBER() OVER(ORDER BY s.dtetme DESC)
FROM student s
INNER JOIN class c ON c.id = s.class
WHERE s.[status]=1 AND c.name='Science' ) AS t
WHERE t.RowNum BETWEEN 1 AND 5
ORDER BY t.RowNum

Related

How optimize select with max subquery on the same table?

We have many old selects like this:
SELECT
tm."ID",tm."R_PERSONES",tm."R_DATASOURCE", ,tm."MATCHCODE",
d.NAME AS DATASOURCE,
p.PDID
FROM TABLE_MAPPINGS tm,
PERSONES p,
DATASOURCES d,
(select ID
from TABLE_MAPPINGS
where (R_PERSONES, MATCHCODE)
in (select
R_PERSONES, MATCHCODE
from TABLE_MAPPINGS
where
id in (select max(id)
from TABLE_MAPPINGS
group by MATCHCODE)
)
) tm2
WHERE tm.R_PERSONES = p.ID
AND tm.R_DATASOURCE=d.ID
and tm2.id = tm.id;
These are large tables, and queries take a long time.
How to rebuild them?
Thank you
You can query the table only once using something like (untested as you have not provided a minimal example of your create table statements or sample data):
SELECT *
FROM (
SELECT m.*,
COUNT(CASE WHEN rnk = 1 THEN 1 END)
OVER (PARTITION BY r_persones, matchcode) AS has_max_id
FROM (
SELECT tm.ID,
tm.R_PERSONES,
tm.R_DATASOURCE,
tm.MATCHCODE,
d.NAME AS DATASOURCE,
p.PDID,
RANK() OVER (PARTITION BY tm.matchcode ORDER BY tm.id DESC) As rnk
FROM TABLE_MAPPINGS tm
INNER JOIN PERSONES p ON tm.R_PERSONES = p.ID
INNER JOIN DATASOURCES d ON tm.R_DATASOURCE = d.ID
) m
)
WHERE has_max_id > 0;
First finding the maximum ID using the RANK analytic function and then finding all the relevant r_persones, matchcode pairs using conditional aggregation in a COUNT analytic function.
Note: you want to use the RANK or DENSE_RANK analytic functions to match the maximums as it can match multiple rows per partition; whereas ROW_NUMBER will only ever put a single row per partition first.
You're querying table_mappings 3 times; how about doing it only once?
WITH
tab_map
AS
(SELECT a.id,
a.r_persones,
a.matchcode,
a.datasource,
ROW_NUMBER ()
OVER (PARTITION BY a.matchcode ORDER BY a.id DESC) rn
FROM table_mappings a)
SELECT tm.id,
tm.r_persones,
tm.matchcode,
d.name AS datasource,
p.pdid
FROM tab_map tm
JOIN persones p ON p.id = tm.r_persones
JOIN datasources d ON d.id = tm.r_datasource
WHERE tm.rn = 1

How can I get latest comment from specific new?

I have a table
News(newsId, text, date)
and
Comments(commentId, text, date, newsId)
and I need to select 10 newest news by date with a newest comment from each. So far I have this, how do I improve it and finish it?
SELECT date,
newsId,
commentid,
date
FROM News,
comments
ORDER BY date DESC
LIMIT 10;
You can do this using row_number() as:
select n.newsid, n.text, n.text, n.date, c.text as comment
from news n left join
(select c.*,
row_number() over (partition by c.newsid order by date desc) as seqnum
from comments c
) c
on c.newsId = n.newsId and seqnum = 1
order by n.date desc
limit 10;
Note that this returns news items with no comments. If you do not want those, then change the left join to an inner join.
if your dbms support row_number then you can try like below
with cte as
(select *,row_number()over(partition by newsId order by date desc) rn
from News
), cte1 as
(
select newsId, text, date from cte where rn<=10
) select cte1.*,c.commentId,c.text as commnt from cte1 join Comments c on cte1.newsid=c.newsid
For older DBMS subquery will useful :
select n.newsid, n.text, n.text, n.date, c.text as comment
from news n inner join
comments c
on c.newsId = n.newsId
where c.date in (select c1.date
from comments c1
where c1.newsid = n.newsid
order by c1.date desc
limit 10
);
Howver, for newest version DBMS row_number() is a generic to do this.

Get Min date as condition

I have a table that contains invoices for all phone numbers, and each number has several invoices, I want to display only the first invoice for precise number but i don't really know how get only first invoice , this is my query
SELECT
b.contrno
a.AR_INVDATE
FROM P_STG_TABS.IVM_INVOICE_RECORD a
INNER JOIN P_EDW_TMP.invoice b
ON b.contrno=a.contrno
WHERE a.AR_INVDATE< (SELECT AR_INVDATE FROM P_STG_TABS.IVM_INVOICE_RECORD WHERE contrno=b.contrno )
Teradata supports a QUALIFY clause to filter the result of an OLAP-function (similar to HAVING after GROUP BY), which greatly simplifies Tim Biegeleisens's answer:
SELECT *
FROM P_STG_TABS.IVM_INVOICE_RECORD a
INNER JOIN P_EDW_TMP.invoice b
ON b.contrno = a.contrno
QUALIFY
ROW_NUMBER()
OVER (PARTITION BY b.contrno
ORDER BY a.AR_INVDATE) = 1
Additionally you can apply the ROW_NUMBER before the join (might be more efficient depending on additional conditions):
SELECT *
FROM
( SELECT *
FROM P_STG_TABS.IVM_INVOICE_RECORD a
QUALIFY
ROW_NUMBER()
OVER (PARTITION BY b.contrno
ORDER BY a.AR_INVDATE) = 1
) AS a
INNER JOIN P_EDW_TMP.invoice b
ON b.contrno = a.contrno
Use ROW_NUMBER():
SELECT
t.contrno,
t.AR_INVDATE
FROM
(
SELECT
b.contrno,
a.AR_INVDATE,
ROW_NUMBER() OVER (PARTITION BY b.contrno ORDER BY a.AR_INVDATE) rn
FROM P_STG_TABS.IVM_INVOICE_RECORD a
INNER JOIN P_EDW_TMP.invoice b
ON b.contrno = a.contrno
) t
WHERE t.rn = 1;
If you are worried about ties, and you want to display all ties, then you can replace ROW_NUMBER with either RANK or DENSE_RANK.
If I correctly understand, then one way is to use group by with min(a.AR_INVDATE):
SELECT
b.contrno,
min(a.AR_INVDATE)
FROM P_STG_TABS.IVM_INVOICE_RECORD a
INNER JOIN P_EDW_TMP.invoice b
ON b.contrno=a.contrno
group by b.contrno

mysql query with double join

I have 3 tables, but I can only get to join another table count. See below.
The one below works like a charm, but I need to add another "count" from another table.
there is a 3rd table called "ci_nomatch" and contains a reference to ci_address_book.reference
which could have multiple entries (many on many) but I only need the count of that table.
so if ci_address_book would have an entries called "item1","item 2","item3"
and ci_nomatch would have "1,item1,user1","2,item1,user4"
I would like to have returned "2" for Item1 on the query.
Any ideas? I tried another join, but it tells me that the reference does not exist, while it does!
SELECT c.*, IFNULL(p.total, 0) AS matchcount
FROM ci_address_book c
LEFT JOIN (
SELECT addressbook_id, COUNT(match_id) AS total
FROM ci_matched_sanctions
GROUP BY addressbook_id
) AS p
ON c.id=p.addressbook_id
ORDER BY matchcount DESC
LIMIT 0,15
You could subquery it directly in the select
SELECT c.*, IFNULL(p.total, 0) AS matchcount,
(SELECT COUNT(*) FROM ci_nomatch n on n.reference = c.reference) AS othercount
FROM ci_address_book c
LEFT JOIN (
SELECT addressbook_id, COUNT(match_id) AS total
FROM ci_matched_sanctions
GROUP BY addressbook_id
) AS p
ON c.id=p.addressbook_id
ORDER BY matchcount DESC
LIMIT 0,15
#updated for comment. Including an extra column "(matchcount - othercount) AS deducted" would be best done by sub-querying.
SELECT *, matchcount - othercount AS deducted
FROM
(
SELECT c.* , IFNULL( p.total, 0 ) AS matchcount, (
SELECT COUNT( * ) FROM ci_falsepositives n
WHERE n.addressbook_id = c.reference ) AS othercount
FROM ci_address_book c
LEFT JOIN (
SELECT addressbook_id, COUNT( match_id ) AS total
FROM ci_matched_sanctions GROUP BY addressbook_id ) AS p
ON c.id = p.addressbook_id ORDER BY matchcount DESC LIMIT 0 , 15
) S

Problems with SQL Inner join

Having some problems while trying to optimize my SQL.
I got 2 tables like this:
Names
id, analyseid, name
Analyses
id, date, analyseid.
I want to get the newest analyse from Analyses (ordered by date) for every name (they are unique) in Names. I can't really see how to do this without using 2 x nested selects.
My try (Dont get confused about the names. It's the same principle):
SELECT
B.id,
B.chosendatetime,
vStockNames.name
FROM
vStockNames
INNER JOIN
(
SELECT TOP 1
vAnalysesHistory.id,
vAnalysesHistory.chosendatetime,
vAnalysesHistory.companyid
FROM
vAnalysesHistory
ORDER BY
vAnalysesHistory.chosendatetime DESC
) AS B
ON
B.companyid = vStockNames.stockid
In my example the problem is that i only get 1 row returned (because of top 1). But if I exclude this, I can get multiple analyses of the same name.
Can you help me ? - THanks in advance.
SQL Server 2000+:
SELECT (SELECT TOP 1
a.id
FROM vAnalysesHistory AS a
WHERE a.companyid = n.stockid
ORDER BY a.chosendatetime DESC) AS id,
n.name,
(SELECT TOP 1
a.chosendatetime
FROM vAnalysesHistory AS a
WHERE a.companyid = n.stockid
ORDER BY a.chosendatetime DESC) AS chosendatetime
FROM vStockNames AS n
SQL Server 2005+, using CTE:
WITH cte AS (
SELECT a.id,
a.date,
a.analyseid,
ROW_NUMBER() OVER(PARTITION BY a.analyseid
ORDER BY a.date DESC) AS rk
FROM ANALYSES a)
SELECT n.id,
n.name,
c.date
FROM NAMES n
JOIN cte c ON c.analyseid = n.analyseid
AND c.rk = 1
...without CTE:
SELECT n.id,
n.name,
c.date
FROM NAMES n
JOIN (SELECT a.id,
a.date,
a.analyseid,
ROW_NUMBER() OVER(PARTITION BY a.analyseid
ORDER BY a.date DESC) AS rk
FROM ANALYSES a) c ON c.analyseid = n.analyseid
AND c.rk = 1
You're only asking for the TOP 1, so that's all you're getting. If you want one per companyId, you need to specify that in the SELECT on vAnalysesHistory. Of course, JOINs must be constant and do not allow this. Fortunately, CROSS APPLY comes to the rescue in cases like this.
SELECT
B.id,
B.chosendatetime,
vStockNames.name
FROM
vStockNames
CROSS APPLY
(
SELECT TOP 1
vAnalysesHistory.id,
vAnalysesHistory.chosendatetime,
vAnalysesHistory.companyid
FROM
vAnalysesHistory
WHERE companyid = vStockNames.stockid
ORDER BY
vAnalysesHistory.chosendatetime DESC
) AS B
You could also use ROW_NUMBER() to do the same:
SELECT
B.id,
B.chosendatetime,
vStockNames.name
FROM
vStockNames
INNER JOIN
(
SELECT
vAnalysesHistory.id,
vAnalysesHistory.chosendatetime,
vAnalysesHistory.companyid,
ROW_NUMBER() OVER (PARTITION BY companyid ORDER BY chosendatetime DESC) AS row
FROM
vAnalysesHistory
) AS B
ON
B.companyid = vStockNames.stockid AND b.row = 1
Personally I'm a fan of the first approach. It will likely be faster and is easier to read IMO.
Will something like this work for you?
;with RankedAnalysesHistory as
(
SELECT
vah.id,
vah.chosendatetime,
vah.companyid
,rank() over (partition by vah.companyid order by vah.chosendatetime desc) rnk
FROM
vAnalysesHistory vah
)
SELECT
B.id,
B.chosendatetime,
vsn.name
FROM
vStockNames vsn
join RankedAnalysesHistory as rah on rah.companyid = vsn.stockid and vah.rnk = 1
It seems to me that you only need SQL-92 for this. Of course, explicit documentation of the joining columns between the tables would help.
Simple names
SELECT B.ID, C.ChosenDate, N.Name
FROM (SELECT A.AnalyseID, MAX(A.Date) AS ChosenDate
FROM Analyses AS A
GROUP BY A.AnalyseID) AS C
JOIN Analyses AS B ON C.AnalyseID = B.AnalyseID AND C.ChosenDate = B.Date
JOIN Names AS N ON N.AnalyseID = C.AnalyseID
The sub-select generates the latest analysis for each company; the join with Analyses picks up the Analyse.ID value corresponding to that latest analysis, and the join with Names picks up the company name. (The C.ChosenDate in the select-list could be replaced by B.Date AS ChosenDate, of course.)
Complicated names
SELECT B.ID, C.ChosenDateTime, N.Name
FROM (SELECT A.CompanyID, MAX(A.ChosenDateTime) AS ChosenDateTime
FROM vAnalysesHistory AS A
GROUP BY A.CompanyID) AS C
JOIN vAnalysesHistory AS B ON C.CompanyID = B.CompanyID
AND C.ChosenDateTime = B.ChosenDateTime
JOIN vStockNames AS N ON N.AnalyseID = C.AnalyseID
Same query with systematic renaming (and slightly different layout to avoid horizontal scrollbars).