Comment sélectionner la plupart des lignes du bas?

100

Je peux faire SELECT TOP (200) ... mais pourquoi pas BOTTOM (200)?

Eh bien, pour ne pas entrer dans la philosophie, ce que je veux dire, c'est, comment puis-je faire l'équivalent de TOP (200) mais à l'envers (à partir du bas, comme vous vous attendez à ce que BOTTOM le fasse ...)?

CloudMeta
la source

Réponses:

89
SELECT
    columns
FROM
(
     SELECT TOP 200
          columns
     FROM
          My_Table
     ORDER BY
          a_column DESC
) SQ
ORDER BY
     a_column ASC
Tom H
la source
2
Pourquoi utilisez-vous une table dérivée?
RichardOD
14
Si vous souhaitez renvoyer les lignes dans l'ordre A-> Z, mais sélectionnez les 200 premiers dans l'ordre Z-> A, c'est une façon de le faire. Les autres réponses, suggérant simplement de changer ORDER BY, ne renverront pas les mêmes résultats décrits dans la question, car ils seront dans le désordre (sauf si l'ordre n'a pas d'importance, ce que l'OP n'a pas dit).
Tom H
3
@Tom H. J'ai dû réfléchir pendant quelques secondes à ce que tu voulais dire (je suis debout depuis 14 heures). Au début, je ne pouvais pas voir la différence entre le vôtre et l'ordre par réponses, mais maintenant je peux. Donc +1.
RichardOD
1
Cette réponse et d'autres fonctionnent bien lorsque vous travaillez sur des tables plus petites. Je ne pense pas que cela vaille la peine de classer le tableau entier par colonne lorsque vous êtes simplement intéressé par les quelques lignes du bas.
stabilfish
1
bonne réponse, le meilleur à mon humble avis ... le vrai problème est que je ne peux pas faire cela à partir d'un script ASP, donc je pense que je dois réorganiser l'objRecordset manuellement ou avec la fonction fournie par ASP ....
Andrea_86
98

C'est inutile. Vous pouvez utiliser ORDER BYet simplement changer le tri en DESCpour obtenir le même effet.

Justin Ethier
la source
3
google a dit la même chose et maintenant 9 d'entre vous sont d'accord, assez bien pour moi, merci: D
CloudMeta
9
L'utilisation de DESC renverra les N dernières lignes, mais les lignes renvoyées seront également dans l'ordre inverse des N premières lignes.
RickNZ
11
Que faire s'il n'y a pas d'index sur votre table pour ORDER BY?
Protector one le
8
@Justin: Imaginez avoir une seule colonne, contenant des valeurs varchar. ORDER BY trierait par ordre alphabétique, ce qui n'est (probablement) pas ce que nous voulons.
Protector one
3
Tom H. est la bonne réponse, sinon, vos lignes seront dans l'ordre inverse.
Pierre-Olivier Goulet
39

Désolé, mais je ne pense pas voir de réponses correctes à mon avis.

La TOPfonction x affiche les enregistrements dans un ordre indéfini. De cette définition découle qu'une BOTTOMfonction ne peut pas être définie.

Indépendamment de tout index ou ordre de tri. Lorsque vous effectuez un, ORDER BY y DESCvous obtenez d'abord les lignes avec la valeur y la plus élevée. S'il s'agit d'un ID généré automatiquement, il doit afficher les derniers enregistrements ajoutés au tableau, comme suggéré dans les autres réponses. Toutefois:

  • Cela ne fonctionne que s'il existe une colonne d'identifiant générée automatiquement
  • Cela a un impact significatif sur les performances si vous comparez cela avec la TOPfonction

La bonne réponse devrait être qu'il n'y a pas, et ne peut pas être, un équivalent à TOPpour obtenir les lignes du bas.

Martijn Burger
la source
3
Certes, il ne semble y avoir aucun équivalent, juste des solutions de contournement.
poke
4
TOP n'a rien à voir avec l'ordre dans lequel les éléments ont été ajoutés à une table, cela signifie simplement "Donnez les premiers enregistrements X qui correspondent à ma requête"
Luc
4
Oui, cela vous donne les premiers enregistrements ajoutés à la table qui correspondent à votre requête.
Martijn Burger
4
Je suis d'accord avec Luke. Les tables de base de données sont par définition sans ordre. Il ne faut jamais se fier à l'ordre fourni par le SGBDR lorsque l'instruction select n'a pas de clause ORDER BY. lire ici sur wiki Cependant, le système de base de données ne garantit aucun ordre des lignes à moins qu'une clause ORDER BY ne soit spécifiée dans l'instruction SELECT qui interroge la table.
Zohar Peled
2
Je suis d'accord avec cette réponse, mais dans la pratique, la réponse de Tom résout le problème pour toutes les utilisations pratiques.
Antonio
18

Logiquement,

BOTTOM (x) is all the records except TOP (n - x), where n is the count; x <= n

Par exemple, sélectionnez les 1000 derniers de l'employé:

Dans T-SQL,

DECLARE 
@bottom int,
@count int

SET @bottom = 1000 
SET @count = (select COUNT(*) from Employee)

select * from Employee emp where emp.EmployeeID not in 
(
SELECT TOP (@count-@bottom) Employee.EmployeeID FROM Employee
)
Shadi Namrouti
la source
1
Salut shadi2014, sans l'utilisation de "ORDER BY", votre résultat sera en quelque sorte aléatoire.
bummi
5
bummi, vous avez raison, mais c'est ce qui rend cette réponse correcte. Select TOP lui-même est "aléatoire" en théorie, et c'est l'implémentation correcte pour Select BOTTOM. Dans un tableau de 5000 enregistrements, les 1000 derniers sont tout sauf les
4000
9

Il semblerait que l'une des réponses qui implémentent une clause ORDER BY dans la solution manque le point ou ne comprend pas réellement ce que TOP vous renvoie.

TOP renvoie un jeu de résultats de requête non ordonné qui limite le jeu d'enregistrements aux N premiers enregistrements renvoyés. (D'un point de vue Oracle, cela revient à ajouter un où ROWNUM <(N + 1).

Toute solution qui utilise une commande peut renvoyer des lignes qui sont également renvoyées par la clause TOP (puisque cet ensemble de données n'était pas ordonné en premier lieu), en fonction des critères utilisés dans l'ordre par

L'utilité de TOP est qu'une fois que l'ensemble de données atteint une certaine taille N, il arrête de récupérer les lignes. Vous pouvez avoir une idée de ce à quoi ressemblent les données sans avoir à les récupérer toutes.

Pour implémenter BOTTOM avec précision, il faudrait extraire l'ensemble de données sans ordre, puis restreindre l'ensemble de données aux N enregistrements finaux. Cela ne sera pas particulièrement efficace si vous avez affaire à d'énormes tables. Cela ne vous donnera pas nécessairement non plus ce que vous pensez demander. La fin de l'ensemble de données peut ne pas être nécessairement "les dernières lignes insérées" (et ne le sera probablement pas pour la plupart des applications DML intensives).

De même, les solutions qui implémentent un ORDER BY sont malheureusement potentiellement désastreuses lorsqu'il s'agit de grands ensembles de données. Si j'ai, disons, 10 milliards de disques et que je veux les 10 derniers, il est assez stupide de commander 10 milliards de disques et de sélectionner les 10 derniers.

Le problème ici, c'est que BOTTOM n'a pas le sens auquel on pense en le comparant à TOP.

Lorsque des enregistrements sont insérés, supprimés, insérés, supprimés encore et encore et encore, des espaces apparaissent dans le stockage et plus tard, des lignes sont insérées, si possible. Mais ce que nous voyons souvent, lorsque nous sélectionnons TOP, semble être des données triées, car elles peuvent avoir été insérées tôt dans l'existence de la table. Si la table ne subit pas de nombreuses suppressions, elle peut sembler être ordonnée. (par exemple, les dates de création peuvent remonter aussi loin que la création de la table elle-même). Mais la réalité est que s'il s'agit d'une table lourde en suppression, les N lignes TOP peuvent ne pas ressembler du tout à cela.

Donc, l'essentiel ici (jeu de mots) est que quelqu'un qui demande les enregistrements BOTTOM N ne sait pas vraiment ce qu'il demande. Ou, du moins, ce qu'ils demandent et ce que BOTTOM signifie réellement ne sont pas la même chose.

Donc - la solution peut répondre aux besoins commerciaux réels du demandeur ... mais ne répond pas aux critères pour être le BOTTOM.

user9323238
la source
1
Excellente explication. Un vote favorable pour apporter plus de lumière sur le sujet.
rohrl77
Mon cas d'utilisation est le suivant. J'ai exécuté une grosse insertdéclaration pour mettre des lignes dans une grande table non indexée. (Je remplis la table avant de commencer à l'indexer.) J'ai perdu ma session client à cause d'un redémarrage ou autre, et maintenant je veux voir si mes lignes nouvellement ajoutées s'y trouvent. Si la ligne «du bas» du tableau est l'une de mes dernières, je sais que l'opération est terminée. Si la ligne `` du bas '' est autre chose, eh bien il n'y a aucune garantie et je dois scanner tout le tableau pour m'en assurer ... mais je pourrais très probablement gagner du temps en vérifiant rapidement le `` bas '' comme vous pouvez le ' Haut'.
Ed Avis le
Bonne explication, mais cela implique toujours l'existence d'un fond, qui nécessite juste que les données soient lues / récupérées à l'envers. Dans le cas (admitedly edge) d'une insertion abandonnée sur une nouvelle table, vérifier le dernier enregistrement inséré (le bas) sans tout récupérer serait utile. Existe-t-il une raison technique pour laquelle les données du tableau ne peuvent pas être récupérées dans l'ordre inverse?
James
3

La réponse actuellement acceptée par "Justin Ethier" n'est pas une réponse correcte comme le souligne "Protector one".

Autant que je sache, pour l'instant, aucune autre réponse ou commentaire ne fournit l'équivalent de BOTTOM (x) que l'auteur de la question a demandé.

Tout d'abord, considérons un scénario où cette fonctionnalité serait nécessaire:

SELECT * FROM Split('apple,orange,banana,apple,lime',',')

Cela renvoie une table d'une colonne et cinq enregistrements:

  • Pomme
  • Orange
  • banane
  • Pomme
  • citron vert

Comme vous pouvez le voir: nous n'avons pas de colonne ID; nous ne pouvons pas commander par la colonne retournée; et nous ne pouvons pas sélectionner les deux derniers enregistrements en utilisant SQL standard comme nous pouvons le faire pour les deux premiers enregistrements.

Voici ma tentative de fournir une solution:

SELECT * INTO #mytemptable FROM Split('apple,orange,banana,apple,lime',',')
ALTER TABLE #mytemptable ADD tempID INT IDENTITY
SELECT TOP 2 * FROM #mytemptable ORDER BY tempID DESC
DROP TABLE #mytemptable

Et voici une solution plus complète:

SELECT * INTO #mytemptable FROM Split('apple,orange,banana,apple,lime',',')
ALTER TABLE #mytemptable ADD tempID INT IDENTITY
DELETE FROM #mytemptable WHERE tempID <= ((SELECT COUNT(*) FROM #mytemptable) - 2)
ALTER TABLE #mytemptable DROP COLUMN tempID
SELECT * FROM #mytemptable
DROP TABLE #mytemptable

Je ne prétends en aucun cas que c'est une bonne idée à utiliser en toutes circonstances, mais cela donne les résultats escomptés.

Tomosius
la source
1

Tout ce que vous avez à faire est d'inverser votre ORDER BY. Ajoutez ou supprimez- DESCy.

Justin Swartsel
la source
1

Le problème avec la commande dans l'autre sens est qu'elle ne fait souvent pas bon usage des indices. Il n'est pas non plus très extensible si jamais vous avez besoin de sélectionner un certain nombre de lignes qui ne sont pas au début ou à la fin. Une autre manière est la suivante.

DECLARE @NumberOfRows int;
SET @NumberOfRows = (SELECT COUNT(*) FROM TheTable);

SELECT col1, col2,...
FROM (
    SELECT col1, col2,..., ROW_NUMBER() OVER (ORDER BY col1) AS intRow
    FROM TheTable
) AS T
WHERE intRow > @NumberOfRows - 20;
Paul
la source
2
1) Si changer la direction de votre clause ORDER BY "ne fait pas bon usage des indices", alors obtenez un SGBDR décent! Le SGBDR ne doit jamais se soucier de savoir s'il fait avancer ou reculer l'index. 2) Vous êtes préoccupé par l'utilisation des index, mais votre solution attache une séquence à chaque ligne de la table ... C'est une façon de garantir qu'un index approprié ne sera PAS utilisé.
Désillusionné
1

La réponse "Tom H" ci-dessus est correcte et cela fonctionne pour moi en obtenant les 5 dernières lignes.

SELECT [KeyCol1], [KeyCol2], [Col3]
FROM
(SELECT TOP 5 [KeyCol1],
       [KeyCol2],
       [Col3]
  FROM [dbo].[table_name]
  ORDER BY [KeyCol1],[KeyCol2] DESC) SOME_ALAIS
  ORDER BY [KeyCol1],[KeyCol2] ASC

Merci.

utilisateur3598017
la source
0

essaye ça.

declare @floor int --this is the offset from the bottom, the number of results to exclude
declare @resultLimit int --the number of results actually retrieved for use
declare @total int --just adds them up, the total number of results fetched initially

--following is for gathering top 60 results total, then getting rid of top 50. We only keep the last 10
set @floor = 50 
set @resultLimit = 10
set @total = @floor + @resultLimit

declare @tmp0 table(
    --table body
)

declare @tmp1 table(
    --table body
)

--this line will drop the wanted results from whatever table we're selecting from
insert into @tmp0
select Top @total --what to select (the where, from, etc)

--using floor, insert the part we don't want into the second tmp table
insert into @tmp1
select top @floor * from @tmp0

--using select except, exclude top x results from the query
select * from @tmp0
except 
select * from @tmp1
HumbleWebDev
la source
qu'est-ce qui fait votre code pour l'OP? Veuillez ajouter un peu plus d'explications sur la façon dont vous essayez de résoudre le problème
TechSpider
J'ai fait une modification. J'espère que cela explique un peu mieux, en ajoutant plus de commentaires. L'idée principale est de sélectionner top x dans une table, puis de sélectionner top x - nombre voulu, puis d'utiliser une instruction except pour exclure les résultats non édités.
HumbleWebDev
0

J'ai trouvé une solution à ce problème qui ne vous oblige pas à connaître le nombre de lignes renvoyées.

Par exemple, si vous souhaitez obtenir tous les emplacements enregistrés dans une table, à l'exception du dernier 1 (ou 2, ou 5 ou 34)

SELECT * 
FROM
    (SELECT ROW_NUMBER() OVER (ORDER BY CreatedDate) AS Row, * 
    FROM Locations
    WHERE UserId = 12345) AS SubQuery
WHERE Row > 1 -- or 2, or 5, or 34
rouge
la source
0

Interroger une sous-requête simple triée par ordre décroissant, suivie d'un tri sur la même colonne par ordre croissant fait l'affaire.

SELECT * FROM 
    (SELECT TOP 200 * FROM [table] t2 ORDER BY t2.[column] DESC) t1
    ORDER BY t1.[column]
Sheppe
la source
0
SELECT TOP 10*from TABLE1 ORDER BY ID DESC

Où ID est la clé primaire de TABLE1.

Euh. Binod Mehta
la source
0

Tout d'abord, créez un index dans une sous-requête selon l'ordre d'origine de la table en utilisant:

ROW_NUMBER () OVER (ORDER BY (SELECT NULL) ) AS RowIndex

Ensuite, classez la table en ordre décroissant de la RowIndexcolonne que vous avez créée dans la requête principale:

ORDER BY RowIndex DESC

Et enfin utiliser TOPavec votre quantité de lignes souhaitée:

    SELECT TOP 1 * --(or 2, or 5, or 34)
    FROM   (SELECT ROW_NUMBER() OVER (ORDER BY  (SELECT NULL) ) AS RowIndex, * 
            FROM MyTable) AS SubQuery
    ORDER BY RowIndex DESC
Thiago Marques
la source