How to modify this query for pagination fetching? - sql

This is a pretty complex query so i am having difficulties here. If you help me i appreciate it.
select AvgLevel,TotalCount,tblPokedex.PokemonId,Name,Type1,Type2,Hp,tblPokedex.Attack,tblPokedex.Defense,tblPokedex.SpAttack,tblPokedex.SpDefense,tblPokedex.Speed,(Hp+tblPokedex.Attack+tblPokedex.Defense+tblPokedex.SpAttack+tblPokedex.SpDefense+tblPokedex.Speed) as TotalStats
from tblPokemonStats,tblAvailablePokemons,tblPokedex
left join tblUsersPokemons on tblPokedex.PokemonId=tblUsersPokemons.PokemonId
where tblPokemonStats.PokemonId=tblPokedex.PokemonId
group by tblPokedex.PokemonId,tblPokedex.Name,tblPokedex.Type1,tblPokedex.Type2,tblPokedex.Hp,tblPokedex.Attack,tblPokedex.Defense,tblPokedex.SpAttack,tblPokedex.SpDefense,tblPokedex.Speed,tblPokemonStats.AvgLevel,tblPokemonStats.TotalCount
order by PokemonId asc
Alright so what i want to do is for example select between top 100 and 150
How can i do that ?

Here's one way - tack on a row_number column and filter on that:
select AvgLevel /*...*/
from (
select AvgLevel /*...*/, row_number() over (order by PokemonId) rownum
from tblPokemonStats,tblAvailablePokemons,tblPokedex
left join tblUsersPokemons on tblPokedex.PokemonId=tblUsersPokemons.PokemonId
where tblPokemonStats.PokemonId=tblPokedex.PokemonId
group by /* etc ... */
) A
where A.rownum > 100 and A.rownum <= 150
row_number does what it sounds like - it numbers your rows.
Here's another possibility - say you want rows X through Y. You can select the TOP Y rows according to your order. Then use that a subquery and select the TOP Y-X according to the opposite order.
Assuming X = 100 and Y = 150:
select top 50 *
from (
select top 150 PokemonId
from tblPokemonStats
order by PokemonId
) A
order by PokemonId desc
As far as performance, I'm not sure which would be better. My gut says that the ORDER BY approach is better for pages near the 'front' so to speak, but row_number() would be better for later pages (high X and Y). That's just a guess though - run some tests and see what works for you.

Related

How to use ROWNUM for a maximum and another minimum ordering in ORACLE?

Currently i am trying to output the top row for 2 condition. One is max and one is min.
Current code
Select *
from (MY SELECT STATEMENT order by A desc)
where ROWNUM <= 1
UPDATE
I am now able to do for both condition. But i need the A to be the highest, if same then check for the B lowest.
E.g Lets say there is 2 rows, Both A is 100 and B is 50 for one and 60 for other.
In this case the 100:50 shld be choose because A is same then B is lowest.
E.g
Lets say there is 2 rows, A is 100 for one and 90 for other, since one is higher no need to check for B.
I tried using max and min but this method seems to work better, any suggestions
Well, after your clarification, you are looking for one record. With Max A. And the smallest B, in case there is more than one record with MAX A. This is simply:
Select *
from (MY SELECT STATEMENT order by A desc, B)
where ROWNUM = 1;
This sorts by A descending first, so you get all maximal A records first. Then it sorts by B, so inside each A group you get the least B first. This gives you the desired A record first, no matter if the found A is unique or not.
or avoid the vagaries of rownun and go for row_number() instead:
SELECT
*
FROM (
SELECT
*
, ROW_NUMBER (ORDER BY A DESC) adesc
, ROW_NUMBER (ORDER BY B ASC) basc
FROM SomeQuery
)
WHERE adesc = 1
OR basc = 1
footnote: select * is a convenience only, please replace with the actual columns required along with table names etc.
Try this if that works
Select *
from (MY SELECT STATEMENT order by A desc)
where ROWNUM <= 1
union
Select *
from (MY SELECT STATEMENT order by A asc)
where ROWNUM <= 1
SELECT * FROM
(Select foo.*, 0 as union_order
from (MY SELECT STATEMENT order by A desc) foo
where ROWNUM <= 1
UNION
Select foo.*, 1
from (MY SELECT STATEMENT order by B asc) foo
where ROWNUM <= 1)
ORDER BY
union_order

choosing best 100 players from sql

Ive got table with columns:
PlayerId
Points
I would like to get 100 best players (the more points player have, the better he is). What would be the quesry for that ?
Im using sql server 2008
SELECT TOP 100 PlayerId
FROM TableName
ORDER BY Points DESC
To break it down:
TOP 100 - Selects the top 100 records to return.
ORDER BY Points DESC - Orders the results by the Points field, and DESC sets them in numerical reverse (assuming Points is an integer data type).
You'll need to select the top X from the query and then order by the points in descending order.
select top 100 * from players order by Points DESC
What happens when two or more players have the same points?, or even worse, what happens if you have 120 players with the maximum points. You should use a query that returns every one of those players. I recommend that you use RANK if this is the case.
;WITH CTE AS
(
SELECT *, RANK() OVER(ORDER BY Points DESC) RN
FROM Players
)
SELECT *
FROM CTE
WHERE RN <= 100
SELECT TOP 100 PlayerID
FROM <<Table>>
ORDER BY Points DESC
Assuming your table name is Players:
SELECT TOP 100 PlayerId, Points
FROM Players
ORDER BY Points DESC
SELECT TOP 100 *
FROM tblName ORDER BY Points DESC
SELECT TOP 100 PlayerId FROM name_of_your_table ORDER BY Points DESC

Compare SQL groups against eachother

How can one filter a grouped resultset for only those groups that meet some criterion compared against the other groups? For example, only those groups that have the maximum number of constituent records?
I had thought that a subquery as follows should do the trick:
SELECT * FROM (
SELECT *, COUNT(*) AS Records
FROM T
GROUP BY X
) t HAVING Records = MAX(Records);
However the addition of the final HAVING clause results in an empty recordset... what's going on?
In MySQL (Which I assume you are using since you have posted SELECT *, COUNT(*) FROM T GROUP BY X Which would fail in all RDBMS that I know of). You can use:
SELECT T.*
FROM T
INNER JOIN
( SELECT X, COUNT(*) AS Records
FROM T
GROUP BY X
ORDER BY Records DESC
LIMIT 1
) T2
ON T2.X = T.X
This has been tested in MySQL and removes the implicit grouping/aggregation.
If you can use windowed functions and one of TOP/LIMIT with Ties or Common Table expressions it becomes even shorter:
Windowed function + CTE: (MS SQL-Server & PostgreSQL Tested)
WITH CTE AS
( SELECT *, COUNT(*) OVER(PARTITION BY X) AS Records
FROM T
)
SELECT *
FROM CTE
WHERE Records = (SELECT MAX(Records) FROM CTE)
Windowed Function with TOP (MS SQL-Server Tested)
SELECT TOP 1 WITH TIES *
FROM ( SELECT *, COUNT(*) OVER(PARTITION BY X) [Records]
FROM T
)
ORDER BY Records DESC
Lastly, I have never used oracle so apolgies for not adding a solution that works on oracle...
EDIT
My Solution for MySQL did not take into account ties, and my suggestion for a solution to this kind of steps on the toes of what you have said you want to avoid (duplicate subqueries) so I am not sure I can help after all, however just in case it is preferable here is a version that will work as required on your fiddle:
SELECT T.*
FROM T
INNER JOIN
( SELECT X
FROM T
GROUP BY X
HAVING COUNT(*) =
( SELECT COUNT(*) AS Records
FROM T
GROUP BY X
ORDER BY Records DESC
LIMIT 1
)
) T2
ON T2.X = T.X
For the exact question you give, one way to look at it is that you want the group of records where there is no other group that has more records. So if you say
SELECT taxid, COUNT(*) as howMany
GROUP by taxid
You get all counties and their counts
Then you can treat that expressions as a table by making it a subquery, and give it an alias. Below I assign two "copies" of the query the names X and Y and ask for taxids that don't have any more in one table. If there are two with the same number I'd get two or more. Different databases have proprietary syntax, notably TOP and LIMIT, that make this kind of query simpler, easier to understand.
SELECT taxid FROM
(select taxid, count(*) as HowMany from flats
GROUP by taxid) as X
WHERE NOT EXISTS
(
SELECT * from
(
SELECT taxid, count(*) as HowMany FROM
flats
GROUP by taxid
) AS Y
WHERE Y.howmany > X.howmany
)
Try this:
SELECT * FROM (
SELECT *, MAX(Records) as max_records FROM (
SELECT *, COUNT(*) AS Records
FROM T
GROUP BY X
) t
) WHERE Records = max_records
I'm sorry that I can't test the validity of this query right now.

Oracle Rownum SQL

What are the differences (advantages and disadvantages) between these two coding techniques?
select * from (
select rownum rnun, * from table where rownum < x
) where rnum > y
select * from (
select * from table
) where rownum < x and x > y
The two queries return different rows.
Neither query is deterministic. So neither query should ever be used in a real system.
The first query appears to be at least an attempt to generate a window of rows (rows between x and y). Since there is no ORDER BY, however, the order of rows is not deterministic and the window probably doesn't do what you want.
The second query returns an arbitrary x rows of data (assuming x > y). Otherwise it returns 0 rows (if y >= x). If you're trying to build some sort of windowing query, this isn't it.
If you want a windowing query that works, you'd want something like
SELECT *
FROM (SELECT a.*,
row_number() over (order by something) rnum
FROM table_name)
WHERE rnum BETWEEN x AND y
If you wanted to use ROWNUM, you'd need something like
SELECT *
FROM (SELECT a.*,
rownum rnum
FROM( SELECT b.*
FROM table_name
ORDER BY something) a)
WHERE rownum < y
AND rnum > x
But this tends to be less efficient than the analytic query approach.
Besides the absence of the “order by” clause…..
May be the question is about Oracle STOPKEY feature? In case of “paging” queries Oracle can use a STOPKEY feature to limit the number of rows in the subquery, this can lead to some performance gain.
Look at this query:
select *
from (select a.*,
row_number() over (order by sname) rnum
from t_patient_card a)
where rnum between 1 and 100
Cost Cardinality
SELECT STATEMENT, GOAL = FIRST_ROWS 313272 3571266
VIEW HOSPITAL2$ 313272 3571266
SORT ORDER BY 313272 3571266
COUNT
TABLE ACCESS FULL HOSPITAL2$ T_PATIENT_CARD 38883 3571266
Oracle fetched all the rows before is return only 100 of them
Let’s rewrite the query like this:
select *
from (
select rownum as rn,tt.* from
(
select t.* from t_patient_card t order by t.sname
)tt where rownum<100
)
WHERE rn >1
In this case we user rownum<100 in the subquery to inform the optimizer that we want to get less the 100 rows.
Cost Cardinality
SELECT STATEMENT, GOAL = ALL_ROWS 313272 99
VIEW HOSPITAL2$ 313272 99
COUNT STOPKEY
VIEW HOSPITAL2$ 313272 3571266
SORT ORDER BY STOPKEY 313272 3571266
TABLE ACCESS FULL HOSPITAL2$ T_PATIENT_CARD 38883 3571266
You can see the “count stopkey” and cardinality is only 99 after this step.In my database the second query executes one second faster then the first one.

MSSQL 2008 SP pagination and count number of total records

In my SP I have the following:
with Paging(RowNo, ID, Name, TotalOccurrences) as
(
ROW_NUMBER() over (order by TotalOccurrences desc) as RowNo, V.ID, V.Name, R.TotalOccurrences FROM dbo.Videos V INNER JOIN ....
)
SELECT * FROM Paging WHERE RowNo BETWEEN 1 and 50
SELECT COUNT(*) FROM Paging
The result is that I get the error: invalid object name 'Paging'.
Can I query again the Paging table? I don't want to include the count for all results as a new column ... I would prefer to return as another data set. Is that possible?
Thanks, Radu
After more research I fond another way of doing this:
with Paging(RowNo, ID, Name, TotalOccurrences) AS
(
ROW_NUMBER() over (order by TotalOccurrences desc) as RowNo, V.ID, V.Name, R.TotalOccurrences FROM dbo.Videos V INNER JOIN ....
)
select RowNo, ID, Name, TotalOccurrences, (select COUNT(*) from Paging) as TotalResults from Paging where RowNo between (#PageNumber - 1 )* #PageSize + 1 and #PageNumber * #PageSize;
I think that this has better performance than calling two times the query.
You can't do that because the CTE you are defining will only be available to the FIRST query that appears after it's been defined. So when you run the COUNT(*) query, the CTE is no longer available to reference. That's just a limitation of CTEs.
So to do the COUNT as a separate step, you'd need to not use the CTE and instead use the full query to COUNT on.
Or, you could wrap the CTE up in an inline table valued function and use that instead, to save repeating the main query, something like this:
CREATE FUNCTION dbo.ufnExample()
RETURNS TABLE
AS
RETURN
(
with Paging(RowNo, ID, Name, TotalOccurrences) as
(
ROW_NUMBER() over (order by TotalOccurrences desc) as RowNo, V.ID, V.Name, R.TotalOccurrences FROM dbo.Videos V INNER JOIN ....
)
SELECT * FROM Paging
)
SELECT * FROM dbo.ufnExample() x WHERE RowNo BETWEEN 1 AND 50
SELECT COUNT(*) FROM dbo.ufnExample() x
Please be aware that Radu D's solution's query plan shows double hits to those tables. It is doing two executions under the covers. However, this still may be the best way as I haven't found a truly scalable 1-query design.
A less scalable 1-query design is to dump a completed ordered list into a #tablevariable , SELECT ##ROWCOUNT to get the full count, and select from #tablevariable where row number between X and Y. This works well for <10000 rows, but with results in the millions of rows, populating that #tablevariable gets expensive.
A hybrid approach is to populate this temp/variable up to 10000 rows. If not all 10000 rows are filled up, you're set. If 10000 rows are filled up, you'll need to rerun the search to get the full count. This works well if most of your queries return well under 10000 rows. The 10000 limit is a rough approximation, you can play around with this threshold for your case.
Write "AS" after the CTE table name Paging as below:
with Paging AS (RowNo, ID, Name, TotalOccurrences) as
(
ROW_NUMBER() over (order by TotalOccurrences desc) as RowNo, V.ID, V.Name, R.TotalOccurrences FROM dbo.Videos V INNER JOIN ....
)
SELECT * FROM Paging WHERE RowNo BETWEEN 1 and 50
SELECT COUNT(*) FROM Paging