How to simulate the SQL ROW_NUMBER function
If your RDBMS does not support the ROW_NUMBER function, it is still possible to write some queries without it. ROW_NUMBER operates over a partition of the result set, and returns a number for each row in a partition (it is possible to specify numbering schemes besides consecutive integers).
Ranking and windowing functions are available in Microsoft SQL Server 2005, and are part of the newer SQL standards. I expect it will be some time before MySQL implements them. I don’t know about other systems such as PostgreSQL and Firebird. It has been a long time since I worked with Oracle, so I’m unsure about it, and viewing the Oracle documentation requires an account I’m unwilling to create.
An easy solution
One easy solution is a temporary table with an identity (AUTO_INCREMENT in MySQL) column. Insert into the temporary table, then select from it again and use the values from the identity column. This only works when the result set contains a single partition, but it works very well and is very efficient.
When the data has multiple partitions
If the data has multiple partitions, the solution is more complex. Here is one possible solution:
create table fruits (
type varchar(10) not null,
variety varchar(20) not null,
primary key(type, variety));
insert into fruits values
('apple', 'gala'),
('apple', 'fuji'),
('apple', 'limbertwig'),
('orange', 'valencia'),
('orange', 'navel'),
('pear', 'bradford'),
('pear', 'bartlett'),
('cherry', 'bing'),
('cherry', 'chelan');
select l.type, l.variety, count(*) as num
from fruits as l
left outer join fruits as r
on l.type = r.type
and l.variety >= r.variety
group by l.type, l.variety;
+--------+------------+-----+
| type | variety | num |
+--------+------------+-----+
| apple | fuji | 1 |
| apple | gala | 2 |
| apple | limbertwig | 3 |
| cherry | bing | 1 |
| cherry | chelan | 2 |
| orange | navel | 1 |
| orange | valencia | 2 |
| pear | bartlett | 1 |
| pear | bradford | 2 |
+--------+------------+-----+
Here I’ve partitioned the data by the type column, so there are four partitions (apple, cherry, orange, pear). Notice the num column starts at 1 for each partition and counts upward.
The drawback to this solution is the LEFT OUTER JOIN with the >= in the join condition. This effectively makes the join a CROSS JOIN, which is inefficient (O(n2)). It may be a good idea to avoid this except on small data sets.
Another option on MySQL, but only with the MyISAM and BDB storage engines, is to use an AUTO_INCREMENT column as the second column in the primary key, which makes MySQL restart the numbering for each group.
Further Reading:






I appreciate the above article with explanatory example and the below line
“This effectively makes the join a CROSS JOIN, which is inefficient (O(n2)). “
Good…
Shankudada
1 Jan 07 at 6:12 am
Here is a follow-up showing how to number rows using user variables.
Stephen Gornick
26 Mar 07 at 9:41 pm
FYI, the Row_Number function as many other analytic function,
Exists in oracle way back from version 8i, which was something like 10 years ago from now….
Oracle Guy
21 Aug 08 at 4:15 pm
Hi Baron,
I just re-read your ROW_NUMBER articles, and I am happy to contribute another method using GROUP_CONCAT():
select l.type, l.variety,
find_in_set(l.variety, (
select group_concat(i.variety order by i.variety)
from fruits i
where i.type = l.type
)) num
from fruits l;
As always, be cautious for warnings like:
x line(s) were cut by GROUP_CONCAT()
You can try to get rid of them using
SET group_concat_max_len := @@max_allowed_packet
Roland Bouman
5 Sep 08 at 6:59 am
I tried to apply this working with two tables: categories (category_id, parentid) and products (category_id, name). I would like to group products by parent category (parentid) and limit the mumber of products for each group for exemple to 3.
I can’t combine the code above and the join clause (except writing the same request twice creating two temporaries tables).
How should I do in a more smart way ?
sylvie
10 Aug 09 at 6:24 pm
This does not seem to simulate the ROW_NUMBER() function, because if you were to apply this on numerical fields, same values would get the same number. this is a simulation of the RANK() function.
Blecchi
9 Jan 10 at 6:52 am
One more solution,
SET @n := 0;
SET @t := NULL;
SELECT f.*, @n
FROM fruits f
WHERE CASE WHEN f.type @t THEN @n := 0 ELSE 1 END >= 0
AND(@n := @n + 1) >= 0
AND(@t := f.type) IS NOT NULL
Shiva M
15 Feb 10 at 7:21 pm
In my above comment “not equal to” with greater than and less than got gobbled up. Hence replaced it with “!=”.
SET @n := 0;
SET @t := NULL;
SELECT f.*, @n
FROM fruits f
WHERE CASE WHEN f.type != @t THEN @n := 0 ELSE 1 END >= 0
AND(@n := @n + 1) >= 0
AND(@t := f.type) IS NOT NULL
Shiva M
15 Feb 10 at 7:28 pm
[...] Eine weitere Möglichkeit beschreibt Xarpb in seinem Artikel How to simulate the SQL ROW_NUMBER function. [...]
Zeilen in MySQL enumerieren Lars seine Seite
15 Mar 10 at 7:14 pm
If you want a simple way to reproduce the row_number without grouping its easily achieved with a user variable.
SELECT *, @rownum:=@rownum+1 rownum, (SELECT @rownum:=0) dummy
FROM fruits
HAVING rownum <= (pg*lim);
You'll notice I used it for dynamic paging of the result set in my stored proc.
Brian M
14 Sep 10 at 11:43 am
Hi
Thank you for useful articles.
But there is problem!
in below sample (in SQL SERVER):
CREATE TABLE [Test] (
[ID] [bigint] IDENTITY (1, 1) NOT NULL ,
[Date] [char] (10),
[B] [int],
[C] [int]
)
INSERT INTO [TEST].[Test]
VALUES (’1389/01/01′,’1′,’1′)
INSERT INTO [TEST].[Test]
VALUES (’1389/01/01′,’5′,’2′)
INSERT INT[TEST].[Test]
VALUES (’1389/01/02′,’2′,’10′)
INSERT INT[TEST].[Test]
VALUES (’1389/01/02′,’2′,’11′)
INSERT INT[TEST].[Test]
VALUES (’1389/01/03′,’2′,’2′)
INSERT INT[TEST].[Test]
VALUES (’1389/01/03′,’5′,’1′)
INSERT INT[TEST].[Test]
VALUES (’1389/01/01′,’2′,’1′)
INSERT INT[TEST].[Test]
VALUES (’1389/01/02′,’2′,’12′)
INSERT INT[TEST].[Test]
VALUES (’1389/01/03′,’2′,’2′)
when we use:
select l.Date, l.B, l.C, count(*) as num
from Test as l
left outer join Test as r
on l.Date = r.Date
and l.B >= r.B
group by l.Date, l.B, l.C
we have:
…
1389/01/02 2 10 3
1389/01/02 2 11 3
1389/01/02 2 12 3
…
But must be create like :
…
1389/01/02 2 10 1
1389/01/02 2 11 2
1389/01/02 2 12 3
…
No my question:
What solve it? (Create above table)?
Note : I want use SQL only (No use TSQL)!
Note 2 : Cause problem is ‘Like Data in Date and B Field’
Description 1: My english is poor.Excuse me!
Description 2: Date is persian calender!
SMAH1
21 Feb 11 at 2:24 pm