2009-02-02 10 views
4

Le problème que j'essaie de résoudre est que j'ai une table comme ceci:SQL - min() obtient la valeur la plus basse, max() la plus haute, et si je veux la 2ème (ou la 5ème ou la nième) valeur la plus basse?

a et b se réfèrent au point sur une table différente. la distance est la distance entre les points.

| id | a_id | b_id | distance | delete | 
| 1 | 1 | 1 | 1  | 0 | 
| 2 | 1 | 2 | 0.2345 | 0 | 
| 3 | 1 | 3 | 100  | 0 | 
| 4 | 2 | 1 | 1343.2 | 0 | 
| 5 | 2 | 2 | 0.45 | 0 | 
| 6 | 2 | 3 | 110  | 0 | 
.... 

La colonne importante que je recherche est a_id. Si je voulais garder le placard b pour chacun un, je pouvais faire quelque chose comme ceci:

update mytable set delete = 1 from (select a_id, min(distance) as dist from table group by a_id) as x where a_gid = a_gid and distance > dist; 
delete from mytable where delete = 1; 

Ce qui me donnerait une table de résultat comme celui-ci:

| id | a_id | b_id | distance | delete | 
| 1 | 1 | 1 | 1  | 0 | 
| 5 | 2 | 2 | 0.45 | 0 | 
.... 

-à-dire je besoin d'une ligne pour chaque valeur de a_id, et cette ligne doit avoir la plus petite valeur de distance pour chaque a_id.

Cependant, je veux garder les 10 points les plus proches pour chaque a_gid. Je pourrais faire ceci avec une fonction de plpgsql mais je suis curieux de savoir s'il y a une manière plus SQL-y. Min() et max() renvoient le plus petit et le plus grand, s'il y avait une fonction d'agrégat comme nth(), qui renverrait la nième plus grande/plus petite valeur alors je pourrais le faire de la même manière que ci-dessus. J'utilise PostgeSQL.

Répondre

4

Essayez ceci:

SELECT * 
FROM (
    SELECT a_id, (
     SELECT b_id 
     FROM mytable mib 
     WHERE mib.a_id = ma.a_id 
     ORDER BY 
      dist DESC 
     LIMIT 1 OFFSET s 
     ) AS b_id 
    FROM (
     SELECT DISTINCT a_id 
     FROM mytable mia 
     ) ma, generate_series (1, 10) s 
    ) ab 
WHERE b_id IS NOT NULL 

Contrôlé le PostgreSQL 8.3

0

Est-ce que PostgreSQL a la fonction analytique rank()? Si oui, essayez:

select a_id, b_id, distance 
from 
(select a_id, b_id, distance, rank() over (partition by a_id order by distance) rnk 
    from mytable 
) where rnk <= 10; 
+0

Postgres n'a pas cette fonction, mais vous êtes sur la bonne voie et votre suggestion m'aide à trouver la réponse. – Rory

+0

Maintenant, dans PostgreSQL 9.1, il y a une fonction rank() dans Window Functions. Voir http://www.postgresql.org/docs/9.1/static/tutorial-window.html – Stefan

4

J'aime postgres, donc il a fallu un défi à la seconde où j'ai vu cette question.

Ainsi, pour la table:

Table "pg_temp_29.foo" 
Column | Type | Modifiers 
--------+---------+----------- 
value | integer | 

Avec les valeurs suivantes:

SELECT value FROM foo ORDER BY value; 
value 
------- 
    0 
    1 
    2 
    3 
    4 
    5 
    6 
    7 
    8 
    9 
    14 
    20 
    32 
(13 rows) 

Vous pouvez faire:

SELECT value FROM foo ORDER BY value DESC LIMIT 1 OFFSET X 

où X = 0 pour la valeur la plus élevée, 1 pour le deuxième plus haut, 2 ... Et ainsi de suite.

Ceci peut être incorporé dans une sous-requête pour récupérer la valeur nécessaire. Donc, pour utiliser l'ensemble de données fournies dans la question initiale que nous pouvons obtenir les a_ids avec les dix distances les plus bas en faisant:

SELECT a_id, distance FROM mytable 
WHERE id IN 
    (SELECT id FROM mytable WHERE t1.a_id = t2.a_id 
    ORDER BY distance LIMIT 10); 
ORDER BY a_id, distance; 

a_id | distance 
------+---------- 
    1 | 0.2345 
    1 |  1 
    1 |  100 
    2 |  0.45 
    2 |  110 
    2 | 1342.2 
+0

Dang, j'aurais dû vous donner un exemple de l'ensemble de données que vous avez fourni. – Elijah

+0

Cela ne fonctionnera pas, car je veux la valeur la plus basse pour chaque valeur de a_id. – Rory

+0

Si vous supprimez le DESC (pour la descente), vous obtiendrez une liste des valeurs les plus faibles: SELECT valeur FROM foo ORDER BY valeur LIMIT 1 OFFSET 1; De toute façon, mon espoir était de démontrer le principal avec un jeu de données plus simple. – Elijah

0

Ce SQL devrait vous trouver le salaire Nth plus bas devrait fonctionner dans SQL Server, MySQL, DB2, Oracle, Teradata, et presque tous les autres SGBDR: (note: faible performance en raison de sous-requête)

SELECT * /*This is the outer query part */ 
FROM mytable tbl1 
WHERE (N-1) = (/* Subquery starts here */ 
SELECT COUNT(DISTINCT(tbl2.distance)) 
FROM mytable tbl2 
WHERE tbl2.distance < tbl1.distance) 

La chose la plus importante à comprendre dans la requête ci-dessus est que la sous-requête est évaluée chaque fois qu'une ligne est traitée par la requête externe.En d'autres termes, la requête interne ne peut pas être traitée indépendamment de la requête externe car la requête interne utilise également la valeur tbl1. Pour trouver la Nième valeur la plus basse, nous trouvons simplement la valeur qui a exactement N-1 valeurs inférieures à elle-même.

Questions connexes