Comment sélectionner la nième ligne dans une table de base de données SQL?


399

Je suis intéressé à apprendre quelques méthodes (idéalement) indépendantes de la base de données pour sélectionner la n ème ligne dans une table de base de données. Il serait également intéressant de voir comment cela peut être réalisé en utilisant la fonctionnalité native des bases de données suivantes:

  • serveur SQL
  • MySQL
  • PostgreSQL
  • SQLite
  • Oracle

Je fais actuellement quelque chose comme ce qui suit dans SQL Server 2005, mais je serais intéressé à voir d'autres approches plus agnostiques:

WITH Ordered AS (
SELECT ROW_NUMBER() OVER (ORDER BY OrderID) AS RowNumber, OrderID, OrderDate
FROM Orders)
SELECT *
FROM Ordered
WHERE RowNumber = 1000000

Crédit pour le SQL ci-dessus: le blog de Firoz Ansari

Mise à jour: voir la réponse de Troels Arvin concernant la norme SQL. Troels, avez-vous des liens que nous pouvons citer?


3
Oui. Voici un lien vers des informations sur la norme ISO SQL: troels.arvin.dk/db/rdbms/links/#standards
Troels Arvin

13
Juste pour souligner que par la définition d'une relation, les lignes d'une table n'ont pas d'ordre, donc la Nième ligne d'une table ne peut pas être sélectionnée. Ce qui peut être sélectionné est la Nième ligne d'un ensemble de lignes renvoyé par (le reste de) une requête, ce que votre exemple et toutes les autres réponses accomplissent. Pour la plupart, cela peut être simplement de la sémantique, mais cela indique le problème sous-jacent de la question. Si vous devez revenir OrderNo N, introduisez une colonne OrderSequenceNo dans le tableau et générez-la à partir d'un générateur de séquence indépendant lors de la création d'une nouvelle commande.
Damir Sudarevic

2
La norme SQL définit l'option offset x fetch first y rows only. Actuellement pris en charge par (au moins) Postgres, Oracle12, DB2.
a_horse_with_no_name

Réponses:


349

Il existe des moyens de le faire dans des parties facultatives de la norme, mais de nombreuses bases de données prennent en charge leur propre façon de le faire.

Un très bon site qui parle de cela et d'autres choses est http://troels.arvin.dk/db/rdbms/#select-limit .

Fondamentalement, PostgreSQL et MySQL prennent en charge le non standard:

SELECT...
LIMIT y OFFSET x 

Oracle, DB2 et MSSQL prennent en charge les fonctions de fenêtrage standard:

SELECT * FROM (
  SELECT
    ROW_NUMBER() OVER (ORDER BY key ASC) AS rownumber,
    columns
  FROM tablename
) AS foo
WHERE rownumber <= n

(que je viens de copier du site lié ci-dessus car je n'utilise jamais ces bases de données)

Mise à jour: à partir de PostgreSQL 8.4, les fonctions de fenêtrage standard sont prises en charge, alors attendez-vous à ce que le deuxième exemple fonctionne également pour PostgreSQL.

Mise à jour: SQLite a ajouté la prise en charge des fonctions de fenêtre dans la version 3.25.0 le 2018-09-15 afin que les deux formulaires fonctionnent également dans SQLite.


3
MySQL utilise également la syntaxe OFFSET et LIMIT. Firebird utilise les mots clés FIRST et SKIP, mais ils sont placés juste après SELECT.
Doug

7
Cela ne devrait-il pas être WHERE rownumber = nuniquement pour obtenir la nième ligne?
Steve Bennett

MySQL prend en charge les fonctions de fenêtre depuis la version 8. MariaDB depuis la version 10.2
Paul Spiegel

102

PostgreSQL prend en charge les fonctions de fenêtrage telles que définies par la norme SQL, mais elles sont maladroites, donc la plupart des gens utilisent (la non standard) LIMIT/OFFSET :

SELECT
    *
FROM
    mytable
ORDER BY
    somefield
LIMIT 1 OFFSET 20;

Cet exemple sélectionne la 21e ligne. OFFSET 20demande à Postgres de sauter les 20 premiers enregistrements. Si vous ne spécifiez pas de ORDER BYclause, rien ne garantit quel enregistrement vous récupérerez, ce qui est rarement utile.


31

Je ne suis pas sûr de tout le reste, mais je sais que SQLite et MySQL n'ont aucun ordre de ligne "par défaut". Dans ces deux dialectes, au moins, l'extrait de code suivant saisit la 15e entrée de the_table, en triant par date / heure à laquelle il a été ajouté:

SELECT * FROM the_table ORDER BY added DESC LIMIT 1,15

(bien sûr, vous devez avoir un champ DATETIME ajouté et le définir à la date / heure à laquelle l'entrée a été ajoutée ...)


Cela semble être le meilleur moyen de limiter la requête avec une valeur de décalage en ligne. Mais ne devrions-nous pas utiliser 0,14 ici? 1,15 quittera la première ligne.
Gladiator

Mais que signifie le 15? Je sais que le 1 dit d'obtenir un record. La virgule est pas utilisé dans l'exemple que je vérifié 1keydata.com/sql/sql-limit.html
committedandroider

1
En fait, à partir d'ici php.about.com/od/mysqlcommands/g/Limit_sql.htm , si vous vouliez saisir la 15e entrée, ne feriez-vous PAS LIMIT 14, 1 (0e est le premier élément, 1 de longueur
commitandroider

il devrait être CHOISI * À PARTIR DE LA_table ORDRE PAR Ajouté
LIM

25

SQL 2005 et versions ultérieures ont cette fonctionnalité intégrée. Utilisez la fonction ROW_NUMBER (). Il est excellent pour les pages Web avec une navigation de style << Précédente et Suivante >>:

Syntaxe:

SELECT
    *
FROM
    (
        SELECT
            ROW_NUMBER () OVER (ORDER BY MyColumnToOrderBy) AS RowNum,
            *
        FROM
            Table_1
    ) sub
WHERE
    RowNum = 23

Je préfère cette solution, car elle semble plus simple.
FoxArc

18

Je soupçonne que c'est extrêmement inefficace mais c'est une approche assez simple, qui a fonctionné sur un petit ensemble de données sur lequel je l'ai essayé.

select top 1 field
from table
where field in (select top 5 field from table order by field asc)
order by field desc

Cela obtiendrait le 5ème élément, changer le deuxième numéro supérieur pour obtenir un nième élément différent

Serveur SQL uniquement (je pense) mais devrait fonctionner sur les anciennes versions qui ne prennent pas en charge ROW_NUMBER ().


Je vais l'utiliser car ROW_NUMBER () ne fonctionne pas dans SQL 2000 (oui, nous avons toujours un client sur SQL 2000) Plus précisément, je vais remplacer le '5' par une variable itérateur d'une boucle et utiliser celui de copier et de modifier tour à tour chaque ligne d'un tableau. Peut-être que quelqu'un verra ce commentaire et le trouvera utile
Inversus


14

Vérifiez-le sur SQL Server:

Select top 10 * From emp 
EXCEPT
Select top 9 * From emp

Cela vous donnera le 10ème rang de table emp!


Vous avez déjà fourni une réponse à cette question ici Supprimez cette réponse qui vous semble incorrecte.Si vous pensez que les deux réponses sont correctes, puis postez les deux réponses au même endroit
SpringLearner

11

Contrairement à ce que prétendent certaines réponses, le standard SQL n'est pas muet sur ce sujet.

Depuis SQL: 2003, vous pouvez utiliser des "fonctions de fenêtre" pour ignorer les lignes et limiter les jeux de résultats.

Et dans SQL: 2008, une approche légèrement plus simple avait été ajoutée, utilisant
OFFSET skip ROWS FETCH FIRST n ROWS ONLY

Personnellement, je ne pense pas que l'ajout de SQL: 2008 était vraiment nécessaire, donc si j'étais ISO, je l'aurais gardé hors d'un standard déjà assez grand.


C'est bien qu'il existe une norme, facilite la vie des gens comme moi, et si gentil de la part de Microsoft de faire des choses de manière standard :)
user230910

7

Lorsque nous travaillions dans MSSQL 2000, nous faisions ce que nous appelions le "triple-flip":

ÉDITÉ

DECLARE @InnerPageSize int
DECLARE @OuterPageSize int
DECLARE @Count int

SELECT @Count = COUNT(<column>) FROM <TABLE>
SET @InnerPageSize = @PageNum * @PageSize
SET @OuterPageSize = @Count - ((@PageNum - 1) * @PageSize)

IF (@OuterPageSize < 0)
    SET @OuterPageSize = 0
ELSE IF (@OuterPageSize > @PageSize)
    SET @OuterPageSize = @PageSize

DECLARE @sql NVARCHAR(8000)

SET @sql = 'SELECT * FROM
(
    SELECT TOP ' + CAST(@OuterPageSize AS nvarchar(5)) + ' * FROM
    (
        SELECT TOP ' + CAST(@InnerPageSize AS nvarchar(5)) + ' * FROM <TABLE> ORDER BY <column> ASC
    ) AS t1 ORDER BY <column> DESC
) AS t2 ORDER BY <column> ASC'

PRINT @sql
EXECUTE sp_executesql @sql

Ce n'était pas élégant et ce n'était pas rapide, mais cela a fonctionné.


Supposons que vous ayez 25 lignes et que vous vouliez la troisième page de 10 pages, soit les lignes 21 à 25. La requête la plus intérieure obtient les 30 premières lignes (lignes 1-25). La requête du milieu obtient les 10 dernières lignes (lignes 25-16). La requête externe les réorganise et renvoie les lignes 16-25. C'est clairement faux si vous vouliez les lignes 21-25.
Bill Karwin

Maintenant, cela ne fonctionne pas si nous voulons une page du milieu. Disons que nous avons 25 lignes et que nous voulons la deuxième page, c'est-à-dire les lignes 11-20. La requête interne obtient les 2 * 10 premiers = 20 lignes, ou les lignes 1-20. La requête du milieu obtient les 15 dernières lignes: 25 - ((2-1) * 10) = 15, ce qui donne les lignes 20-6. La dernière requête inverse l'ordre et renvoie les lignes 6 à 20. Cette technique ne fonctionne pas, sauf si le nombre total de lignes est un multiple de la taille de page souhaitée.
Bill Karwin

La meilleure conclusion est peut-être que nous devrions mettre à niveau toutes les instances MS SQL Server 2000 restantes. :-) Nous sommes presque 2012, et ce problème a été résolu de meilleures façons depuis de nombreuses années!
Bill Karwin

@Bill Karwin: notez les IF / ELSE IFblocs sous le OuterPageSizecalcul - aux pages 1 et 2, ils ramèneront la OuterPageSizevaleur à 10. À la page 3 (lignes 21-25), le calcul renverra correctement 5, et sur toutes les pages 4 et supérieures, le résultat négatif du calcul sera remplacé par 0 (bien qu'il serait probablement plus rapide de renvoyer une ligne de données vide immédiatement à ce point).
Adam V

Oh je vois maintenant. Eh bien, je maintiens mon opinion que l'utilisation de MS SQL Server 2000 aujourd'hui ne vaut pas la peine.
Bill Karwin

6

SERVEUR SQL


Sélectionnez le nième enregistrement du haut

SELECT * FROM (
SELECT 
ID, NAME, ROW_NUMBER() OVER(ORDER BY ID) AS ROW
FROM TABLE 
) AS TMP 
WHERE ROW = n

sélectionner le nième enregistrement du bas

SELECT * FROM (
SELECT 
ID, NAME, ROW_NUMBER() OVER(ORDER BY ID DESC) AS ROW
FROM TABLE 
) AS TMP 
WHERE ROW = n

5

Oracle:

select * from (select foo from bar order by foo) where ROWNUM = x

1
where ROWNUM = xne fonctionnera que pour x = 1 dans Oracle DB. ie where ROWNUM = 2ne retournera aucune ligne.
aff

5

Dans Oracle 12c, vous pouvez utiliser OFFSET..FETCH..ROWS option avecORDER BY

Par exemple, pour obtenir le 3e enregistrement du haut:

SELECT * 
FROM   sometable
ORDER BY column_name
OFFSET 2 ROWS FETCH NEXT 1 ROWS ONLY;

4

Voici une solution rapide de votre confusion.

SELECT * FROM table ORDER BY `id` DESC LIMIT N, 1

Ici, vous pouvez obtenir la dernière ligne en remplissant N = 0, l'avant-dernière en N = 1, le quatrième en dernier en remplissant N = 3 et ainsi de suite.

C'est une question très courante au cours de l'entretien et c'est très simple.

En outre, si vous voulez un montant, un ID ou un ordre de tri numérique, vous pouvez utiliser la fonction CAST dans MySQL.

SELECT DISTINCT (`amount`) FROM cart ORDER BY CAST( `amount` AS SIGNED ) DESC LIMIT 4 , 1

Ici En remplissant N = 4, vous pourrez obtenir le cinquième dernier enregistrement du montant le plus élevé de la table CART. Vous pouvez adapter votre nom de champ et de table et proposer une solution.


3

AJOUTER:

LIMIT n,1

Cela limitera les résultats à un seul résultat à partir du résultat n.


3

Par exemple, si vous souhaitez sélectionner chaque 10e ligne dans MSSQL, vous pouvez utiliser;

SELECT * FROM (
  SELECT
    ROW_NUMBER() OVER (ORDER BY ColumnName1 ASC) AS rownumber, ColumnName1, ColumnName2
  FROM TableName
) AS foo
WHERE rownumber % 10 = 0

Prenez simplement le MOD et changez le numéro 10 ici n'importe quel nombre que vous voulez.


3

Pour SQL Server, une façon générique de procéder par numéro de ligne est la suivante:

SET ROWCOUNT @row --@row = the row number you wish to work on.

Par exemple:

set rowcount 20   --sets row to 20th row

select meat, cheese from dbo.sandwich --select columns from table at 20th row

set rowcount 0   --sets rowcount back to all rows

Cela renverra les informations de la 20e ligne. Assurez-vous de mettre le nombre de lignes 0 par la suite.


2

LIMIT n, 1 ne fonctionne pas dans MS SQL Server. Je pense que c'est à peu près la seule base de données majeure qui ne prend pas en charge cette syntaxe. Pour être honnête, il ne fait pas partie du standard SQL, bien qu'il soit si largement pris en charge qu'il devrait l'être. Dans tout sauf SQL Server LIMIT fonctionne très bien. Pour SQL Server, je n'ai pas pu trouver de solution élégante.


1
À l'exception d'Oracle, DB2, bien à peu près toutes les bases de données de niveau entreprise dans le monde entier. PostgreSQL est la seule base de données capable d'entreprise qui prend en charge le mot clé LIMIT, et c'est principalement parce qu'étant open source, il doit être accessible par la foule ignorante d'ACID MySQL.
David

3
@AlexD Cette "réponse" a été publiée dans l'ancien temps de Stackoverflow avant la mise en œuvre des commentaires. J'aurais posté cela comme un commentaire à une autre réponse, mais à l'époque, les commentaires n'existaient pas.
Kibbee

2

Voici une version générique d'un sproc que j'ai récemment écrit pour Oracle qui permet la pagination / tri dynamique - HTH

-- p_LowerBound = first row # in the returned set; if second page of 10 rows,
--                this would be 11 (-1 for unbounded/not set)
-- p_UpperBound = last row # in the returned set; if second page of 10 rows,
--                this would be 20 (-1 for unbounded/not set)

OPEN o_Cursor FOR
SELECT * FROM (
SELECT
    Column1,
    Column2
    rownum AS rn
FROM
(
    SELECT
        tbl.Column1,
        tbl.column2
    FROM MyTable tbl
    WHERE
        tbl.Column1 = p_PKParam OR
        tbl.Column1 = -1
    ORDER BY
        DECODE(p_sortOrder, 'A', DECODE(p_sortColumn, 1, Column1, 'X'),'X'),
        DECODE(p_sortOrder, 'D', DECODE(p_sortColumn, 1, Column1, 'X'),'X') DESC,
        DECODE(p_sortOrder, 'A', DECODE(p_sortColumn, 2, Column2, sysdate),sysdate),
        DECODE(p_sortOrder, 'D', DECODE(p_sortColumn, 2, Column2, sysdate),sysdate) DESC
))
WHERE
    (rn >= p_lowerBound OR p_lowerBound = -1) AND
    (rn <= p_upperBound OR p_upperBound = -1);

2

Mais vraiment, tout cela n'est-il pas vraiment juste des astuces de salon pour une bonne conception de base de données? Les quelques fois où j'ai eu besoin de fonctionnalités comme celle-ci, c'était pour une simple requête unique pour faire un rapport rapide. Pour tout travail réel, l'utilisation de ces astuces est source de problèmes. Si la sélection d'une ligne particulière est nécessaire, il suffit d'avoir une colonne avec une valeur séquentielle et d'en finir.


2

Pour SQL Server, ce qui suit renverra la première ligne de la table donnant.

declare @rowNumber int = 1;
    select TOP(@rowNumber) * from [dbo].[someTable];
EXCEPT
    select TOP(@rowNumber - 1) * from [dbo].[someTable];

Vous pouvez parcourir les valeurs avec quelque chose comme ceci:

WHILE @constVar > 0
BEGIN
    declare @rowNumber int = @consVar;
       select TOP(@rowNumber) * from [dbo].[someTable];
    EXCEPT
       select TOP(@rowNumber - 1) * from [dbo].[someTable];  

       SET @constVar = @constVar - 1;    
END;

1

Dans Sybase SQL Anywhere:

SELECT TOP 1 START AT n * from table ORDER BY whatever

N'oubliez pas la COMMANDE PAR ou cela n'a aucun sens.


1

T-SQL - Sélection d'un numéro d'enregistrement dans une table

select * from
 (select row_number() over (order by Rand() desc) as Rno,* from TableName) T where T.Rno = RecordNumber

Where  RecordNumber --> Record Number to Select
       TableName --> To be Replaced with your Table Name

Par exemple, pour sélectionner le 5 e enregistrement d'une table Employé, votre requête doit être

select * from
 (select row_number() over (order by Rand() desc) as Rno,* from Employee) T where T.Rno = 5

1
SELECT * FROM emp a
WHERE  n = (SELECT COUNT( _rowid)
              FROM emp b
             WHERE a. _rowid >= b. _rowid);

1
SELECT
    top 1 *
FROM
    table_name
WHERE
    column_name IN (
        SELECT
            top N column_name
        FROM
            TABLE
        ORDER BY
            column_name
    )
ORDER BY
    column_name DESC

J'ai écrit cette requête pour trouver la Nème ligne. Un exemple avec cette requête serait

SELECT
    top 1 *
FROM
    Employee
WHERE
    emp_id IN (
        SELECT
            top 7 emp_id
        FROM
            Employee
        ORDER BY
            emp_id
    )
ORDER BY
    emp_id DESC

0

incroyable que vous puissiez trouver un moteur SQL exécutant celui-ci ...

WITH sentence AS
(SELECT 
    stuff,
    row = ROW_NUMBER() OVER (ORDER BY Id)
FROM 
    SentenceType
    )
SELECT
    sen.stuff
FROM sentence sen
WHERE sen.row = (ABS(CHECKSUM(NEWID())) % 100) + 1

0

Rien d'extraordinaire, pas de fonctions spéciales, si vous utilisez Caché comme je le fais ...

SELECT TOP 1 * FROM (
  SELECT TOP n * FROM <table>
  ORDER BY ID Desc
)
ORDER BY ID ASC

Étant donné que vous avez une colonne ID ou une colonne d'horodatage, vous pouvez faire confiance.


0

C'est ainsi que je le ferais dans DB2 SQL, je crois que le RRN (numéro d'enregistrement relatif) est stocké dans la table par l'O / S;

SELECT * FROM (                        
   SELECT RRN(FOO) AS RRN, FOO.*
   FROM FOO                         
   ORDER BY RRN(FOO)) BAR             
 WHERE BAR.RRN = recordnumber

0
select * from 
(select * from ordered order by order_id limit 100) x order by 
x.order_id desc limit 1;

Sélectionnez d'abord les 100 premières lignes en triant par ordre croissant, puis sélectionnez la dernière ligne en triant par ordre décroissant et limitez à 1. Cependant, il s'agit d'une instruction très coûteuse car elle accède aux données deux fois.


0

Il me semble que, pour être efficace, vous devez 1) générer un nombre aléatoire entre 0 et un de moins que le nombre d'enregistrements de la base de données, et 2) pouvoir sélectionner la ligne à cette position. Malheureusement, différentes bases de données ont différents générateurs de nombres aléatoires et différentes façons de sélectionner une ligne à une position dans un jeu de résultats - vous spécifiez généralement le nombre de lignes à ignorer et le nombre de lignes que vous souhaitez, mais cela se fait différemment pour différentes bases de données. Voici quelque chose qui fonctionne pour moi dans SQLite:

select * 
from Table 
limit abs(random()) % (select count(*) from Words), 1;

Cela dépend de pouvoir utiliser une sous-requête dans la clause limit (qui dans SQLite est LIMIT <recs to skip>, <recs to take>) La sélection du nombre d'enregistrements dans une table devrait être particulièrement efficace, faisant partie de la base de données. métadonnées, mais cela dépend de la mise en œuvre de la base de données. De plus, je ne sais pas si la requête va réellement générer le jeu de résultats avant de récupérer le Nième enregistrement, mais j'espère que ce n'est pas nécessaire. Notez que je ne spécifie pas une clause "order by". Il pourrait être préférable de "classer par" quelque chose comme la clé primaire, qui aura un index - obtenir le Nième enregistrement à partir d'un index pourrait être plus rapide si la base de données ne peut pas obtenir le Nième enregistrement de la base de données elle-même sans construire l'ensemble de résultats .


0

La réponse la plus appropriée que j'ai vue sur cet article pour le serveur SQL

WITH myTableWithRows AS (
    SELECT (ROW_NUMBER() OVER (ORDER BY myTable.SomeField)) as row,*
    FROM myTable)
SELECT * FROM myTableWithRows WHERE row = 3
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.