How to select top X from a Row_number in SQL Server ? - sql-server

I have a data sample, and now i want to get data using TOP X combine ROW_NUMBER()
IndexNo ProductName
1 Black
2 Blue
3 Brown
4 Green
5 Red
6 White
7 Yellow
As follow in this case, i want to get the data, which after run SQL Statement, result as
IndexNo ProductName
3 Brown
4 Green
5 Red
I use this sql statement for this case, but i get this error Invalid column name 'IndexNo' , this is sql statement .
SELECT TOP 3 ROW_NUMBER() OVER(ORDER BY TEMPA.ProductName) AS IndexNo, TEMPA.ProductName
FROM (
SELECT DISTINCT ProductName FROM PRODUCTS WHERE ProductType ='Food'
) AS TEMPA
WHERE IndexNo between 3 and 5

You could use another level of subquery with parentheses.
SELECT TOP 3 * FROM
( SELECT ROW_NUMBER() OVER(ORDER BY TEMPA.ProductName) AS IndexNo, TEMPA.ProductName
FROM (
SELECT DISTINCT ProductName FROM PRODUCTS
) AS TEMPA
) as TEMPB
WHERE IndexNo between 3 and 5
DEMO

You need to wrap your ROW_NUMBER into Common Table Expression and apply between on the outer level:
with cte as (
SELECT ROW_NUMBER() OVER(ORDER BY TEMPA.ProductName) AS IndexNo, TEMPA.ProductName
FROM (
SELECT DISTINCT ProductName FROM PRODUCTS WHERE ProductType ='Food'
) AS TEMPA
) select top 3 * from cte
WHERE cte.IndexNo between 3 and 5

You need to create the ROW_NUMBER() in one scope and filter it in another scope...
SELECT
*
FROM
(
SELECT *, ROW_NUMBER() OVER (ORDER BY x) AS ix FROM example
)
indexed_example
WHERE
ix BETWEEN 3 AND 5
This is the NOT same for TOP and ORDER BY, as these are applied after the SELECT and WHERE clauses, so this would work fine...
SELECT TOP(3)
*,
ROW_NUMBER() OVER (ORDER BY id DESC) ix
FROM
example
ORDER BY
ix
This is especially useful to your case when using ORDER BY ? OFFSET ? FETCH ? instead of TOP.
SELECT
*,
ROW_NUMBER() OVER (ORDER BY id DESC) ix
FROM
example
ORDER BY
ix DESC
OFFSET 2 ROWS -- Skip 2 rows
FETCH NEXT 3 ROWS ONLY -- Fetch the 3rd, 4th and 5th rows.
In your example, you're also using DISTINCT which is applied after the SELECT values are calculated, but you could use GROUP BY instead as it is applied before the SELECT values are calculated.
SELECT
ROW_NUMBER() OVER (ORDER BY Products.ProductName) ix,
Products.ProductName
FROM
Products
WHERE
Products.ProductType = 'Food'
GROUP BY
Products.ProductName
ORDER BY
ix DESC
OFFSET 2 ROWS
FETCH NEXT 3 ROWS ONLY
All the joins in the FROM clause first (nothing to do in your case)
Apply the WHERE clause
Apply the GROUP BY clause (same effect as your DISTINCT)
Calculate the SELECT values, including the ROW_NUMBER()
Apply the ORDER BY including the OFFSET and FETCH NEXT clauses
Everything you wanted, without needing to next anything in sub-queries.

Related

SQL Newbie - Over Partition?

I have the following query. I am trying to get the Row # to increment whenever the value in Value1 field changes. The SensorData table has 2800 records and the Value1 is either 0 or 3 and changes throughout the day.
SELECT
ROW_NUMBER() OVER(PARTITION BY Value1 ORDER BY Block ASC) AS Row#,
GatewayDetailID, Block, Value1
FROM
SensorData
ORDER BY
Row#
I get the following results:
It seems like it creates only 2 partitions 0 and 3. It is not restarting the row number every time the value 1 changes.?
First instead of creating a permanent table I just changed it to a Temp table.
So, Given your example here is what I came up with:
WITH CTE as(
select ROW_NUMBER() OVER(ORDER BY BLOCK) RN, LAG(Value1,1,VALUE1) OVER (ORDER BY BLOCK) LG,
GatewayDetailID, Block, Value1,Value2,Vaule3
from #tmp
),
CTE2 as (
select *, CASE WHEN LG <> VALUE1 THEN RN ELSE 0 END RowMark
from cte
),
CTE3 AS (
select MIN(Block) BL, RowMark from CTE2
GROUP BY ROwMark
),
CTE4 AS (
SELECT GatewayDetailID,Block,Value1,Value2,Vaule3,RMM from cte2 t1
CROSS APPLY (SELECT MAX(ROWMark) RMM FROM CTE3 t9 where t1.Block >= t9.ROwMark and t1.RN >= t9.RowMark) t2
)
SELECT GateWayDetailID,Block,Value1,Value2,Vaule3, ROW_NUMBER() OVER(Partition by RMM ORDER BY BLOCK) RN
FROM CTE4
ORDER BY BLOCK
I first had to get a Row number for all the rows, then depending on when the Value1 changed I marked that as a new group. From that I created a CTE with the date and row boundry for each group. And then lastly I cross applied that back to the table to find each row in each group.
From that last CTE I merely just applied a simple ROW_NUMBER() function portioned by each RowMarker group and poof....row numbers.
There may be a better way to do this, but this was how I logically worked through the problem.

SQL select the row with max value using row_number() or rank()

I have data of following kind:
RowId Name Value
1 s1 12
22 s1 3
13 s1 4
10 s2 14
22 s2 5
3 s2 100
I want to have the following output:
RowId Name Value
1 s1 12
3 s2 100
I am currently using temp tables to get this in two step. I have been trying to use row_number() and rank() functions but have not been successful.
Can someone please help me with syntax as I feel row_number() and rank() will make it cleaner?
Edit:
I changed the rowId to make it a general case
Edit:
I am open to ideas better than row_number() and rank() if there are any.
If you use rank() you can get multiple results when a name has more than 1 row with the same max value. If that is what you are wanting, then switch row_number() to rank() in the following examples.
For the highest value per name (top 1 per group), using row_number()
select sub.RowId, sub.Name, sub.Value
from (
select *
, rn = row_number() over (
partition by Name
order by Value desc
)
from t
) as sub
where sub.rn = 1
I can not say that there are any 'better' alternatives, but there are alternatives. Performance may vary.
cross apply version:
select distinct
x.RowId
, t.Name
, x.Value
from t
cross apply (
select top 1
*
from t as i
where i.Name = t.Name
order by i.Value desc
) as x;
top with ties using row_number() version:
select top 1 with ties
*
from t
order by
row_number() over (
partition by Name
order by Value desc
)
This inner join version has the same issue as using rank() instead of row_number() in that you can get multiple results for the same name if a name has more than one row with the same max value.
inner join version:
select t.*
from t
inner join (
select MaxValue = max(value), Name
from t
group by Name
) as m
on t.Name = m.Name
and t.Value = m.MaxValue;
If you really want to use ROW_NUMBER() you can do it this way:
With Cte As
(
Select *,
Row_Number() Over (Partition By Name Order By Value Desc) RN
From YourTable
)
Select RowId, Name, Value
From Cte
Where RN = 1;
Unless I'm missing something... Why use row_number() or rank?
select rowid, name, max(value) as value
from table
group by rowid, name

selecting the next set of top 30 records

This is my query:
SELECT Top 30 *
FROM (SELECT *, Row_number() OVER( PARTITION BY EntityPicURL
ORDER BY FavoriteCount desc) AS RN
FROM TweetEntity
WHERE HashTag LIKE '%%23RIPOlgaSyahputra%') A
WHERE RN = 1
ORDER BY FavoriteCount desc , LastModifieddateTime desc
This will select the first 30 unique records of the column entitypicURl. Now that I want to select the next 30 records (31-60).
This is a sort of a query i used earlier but this returned many duplicate entries of entitypicURL.
select *
from (select *, row_no = row_number() over (order by FavoriteCount desc,
LastModifiedDateTime desc)
from TweetEntity
where HashTag like '%%23RIPOlgaSyahputra%') e
where e.row_no > 30 and e.row_no <=60
Now i want to combine the value of first query and include e.row_no>30 and e.row_no<60 from the second query.
It is not a duplicate. My confusion is just combining two queries because both has got row_numbers.
You can use nested CTEs like this:
;WITH CTE1 AS (
SELECT *,
ROW_NUMBER() OVER(PARTITION BY EntityPicURL
ORDER BY FavoriteCount desc) AS RN
FROM TweetEntity
WHERE HashTag like '%%23RIPOlgaSyahputra%'
), CTE2 AS (
SELECT *,
ROW_NUMBER() OVER (ORDER FavoriteCount DESC,
LastModifiedDateTime DESC) AS row_no
FROM CTE1
WHERE RN = 1
)
SELECT *
FROM CTE2
WHERE row_no > 30 and row_no <=60
CTE2 will apply ROW_NUMBER on the filtered by RN = 1 result set of CTE1.
You can Use OFFSET to fetch records like this
you can select 30 records from below query
Select * FROM TweetEntity
ORDER BY FavoriteCount desc OFFSET 0 ROWS FETCH NEXT 30 ROWS ONLY
you can select next 30 records from below query
Select * FROM TweetEntity
ORDER BY FavoriteCount desc OFFSET 30 ROWS FETCH NEXT 30 ROWS ONLY
Note: OFFSET Will work on Sql server 2012+
Limitations in Using OFFSET-FETCH
ORDER BY is mandatory to use OFFSET and FETCH clause.
OFFSET clause is mandatory with FETCH. You can never use, ORDER BY …
FETCH.
TOP cannot be combined with OFFSET and FETCH in the same query
expression.
The OFFSET/FETCH rowcount expression can be any arithmetic, constant,
or parameter expression that will return an integer value. The
rowcount expression does not support scalar sub-queries.

Taking the second last row with only one select in SQL Server?

I was trying to select the second last row with SQL Server. So I wrote a query like this:
SELECT TOP 1 * From Cinema
WHERE CinemaID!=(SELECT TOP 1 CinemaID
FROM Cinema
ORDER BY CinemaID DESC)
ORDER BY CinemaID DESC
and it did what I need. But I want to do the same thing with only one select.
I read that the LIMIT clause in MySql does that. But I couldn't find any equivalent
of that. So I appreciate any help about finding something useful.
To get the 2nd last row in one select:
SELECT TOP 1 * From
(select Top 2 * from Cinema ORDER BY CinemaID DESC) x
ORDER BY CinemaID
It's really only "one" select because the outer select is over only 2 rows.
The best way to do this (and compatible with the ANSI SQL standard), is to use a CTE (Common Table Expression) with the ROW_NUMBER function:
;WITH OrderedCinemas AS
(
SELECT
CinemaID, CinemaName,
ROW_NUMBER() OVER(ORDER BY CinemaID DESC) AS 'RowNum'
FROM dbo.Cinema
)
SELECT
CinemaID, CinemaName
FROM OrderedCinemas
WHERE RowNum = 2
By using this construction, you can get the second highest value very easily - or the fifth hightest (WHERE RowNum = 5) or the top 3 rows (WHERE RowNum <= 3) or whatever you need - the CinemaID values are just ordered and sequentially numbered for your use.
The following doesn't work, explaination of why:
Using ranking-function derived column in where clause (SQL Server 2008)
I'll keep it here for completeness:
SELECT row_number() OVER (ORDER BY col) r, *
FROM tbl
WHERE r = 2
More info:
http://www.bidn.com/blogs/marcoadf/bidn-blog/379/ranking-functions-row_number-vs-rank-vs-dense_rank-vs-ntile
So I think the most readable way of doing it is:
SELECT * FROM (SELECT row_number() OVER (ORDER BY col) r, * FROM tbl) q
WHERE r = 2
Since this (old) question has not been tagged with a specific SQL-Server version and none of (the very good) answers uses only one SELECT clause - for the good reason that it was not possible in old verions - here is one that works only in recent, 2012+ versions:
SELECT c.*
FROM dbo.Cinema AS c
ORDER BY CinemaID DESC
OFFSET 1 ROW
FETCH FIRST 1 ROW ONLY ;
Tested at SQLFiddle
SELECT TOP 1 * FROM tbl_CompanyMaster
where Companyid >= (SELECT MAX(Companyid) - 1 FROM tbl_CompanyMaster)
select * from TABLE_NAME order by COLUMN_NAME desc limit 1,1 ;
Where COLUMN_NAME should be "primary key" or "Unique"
Two selects but a bit quicker
select top 1 * from(
SELECT TOP 2 * From Cinema
WHERE CinemaID
ORDER BY CinemaID DESC) top2
Order by CinemaID
So, in the spirit of only using one SELECT clause as stated in the OP and thoroughly abusing T-SQL in general, I proffer something I would never, ever recommend using in production that nevertheless satisfies the stated criteria:
update Cinema
set Cinema.SomeField = Cinema.SomeField
output inserted.*
from Cinema
inner join
(
select top 2 CinemaID, ROW_NUMBER() over (order by CinemaID desc) as RowNum
from Cinema
) rsRowNum on rsRowNum.CinemaID = Cinema.CinemaID
where RowNum = 2
This query will also work for SQLITE
SELECT * From
(select * from Cinema ORDER BY CinemaID DESC LIMIT 2) AS name
ORDER BY CinemaID LIMIT 1
You're only using one SELECT statement. A SELECT statement can include an arbitrary (more or less) number of subqueries--correlated subqueries, scalar subqueries, and so on, each with their own SELECT clause. But it's still just one SELECT statement.
If you want to avoid a subquery, you could select the top 2, and skip the one you don't want. That kind of programming is pretty brittle, though. You have to remember what to skip every time; sooner or later, you'll forget.
SELECT field_name FROM (SELECT TOP 2 field_name FROM table_name
ORDER BY field_name DESC)
WHERE rownum = 2;
select top 1* from(SELECT TOP 2 * From Cinema
WHERE CinemaID
ORDER BY CinemaID DESC) XYZ
ORDER BY CinemaID
where XYZ is not a keyword. It is just a word. And word can be anything.
If you need to do that, but:
the column is different than id
you need to order by some specific column
can't use SELECT on FROM clause (if you are using old versions of Hibernate, per example).
You can do:
select top 1 * from Cinema
where date < (select MAX(date) from Cinema)
order by date desc
The easiest way to get second last row from sql table is user ORDER BY CinemaID DESC and set LIMIT 1,1
TRY THIS
SELECT * from `Cinema` ORDER BY `CinemaID` DESC LIMIT 1,1
select * from
(select ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as R, * from Cinema ) T1
where (select count(*) from Cinema ) - T1.R = 1
SELECT * FROM record
WHERE record_id=(SELECT max(record_id)-1 FROM record)
Here is my code:
SELECT * From
(select * from table name ORDER BY column name DESC LIMIT 2) AS xyz
ORDER BY column name LIMIT 1;

Filter first then select page

How to first filter the result based on params then to apply where-between?
Some thing like
With Results as
(
Select colName,Title, Row_Number(Over...) as row from a table where colName=5
)
Select * from Results
where
row between #first and #last
But it does not works. I need to move my where colName=5 from with clause to outside then I got wrong data as It first get rows between #first n #last then search for colName=5.
Also I want count of Results.
Any idea?
You can use COUNT(*) OVER() to get the count of the unfiltered results
WITH cte as
(
select *,
ROW_NUMBER() over (order by name desc) AS RN,
count(*) over() AS [Count]
from master..spt_values
)
SELECT name, number,[Count]
FROM cte
WHERE RN BETWEEN 20 AND 24
Returns
name number Count
----------------------------------- ----------- -----------
VIEW 8278 2506
VIEW 8278 2506
view 2 2506
varchar 3 2506
varbinary 1 2506
This has performance implications though. You might want to just calculate the COUNT up front and cache it somewhere rather than recalculating it for every page request.
Your ROW_NUMBER syntax is incorrect. It should be this:
With Results as
(
SELECT colName, Title, ROW_NUMBER() OVER (ORDER BY ...) AS RN
FROM your_table
WHERE colName = 5
)
SELECT * FROM Results
WHERE rn BETWEEN #first AND #last
ORDER BY rn
See the documentation for more information.
I use approach very similar to Martin Smiths (currently selected answer) and at least in the tests I've made it gives better performance results.
; WITH cte as
(
select *,
ROW_NUMBER() over (order by name desc) AS RN
from master..spt_values
)
SELECT name, number, (SELECT COUNT(*) FROM cte) AS [Count]
FROM cte
WHERE RN BETWEEN 20 AND 24
Run this and his queries side by side and compare execution plans.

Resources