3

私はこのようなテーブルを持っています:

     a    |  user_id
----------+-------------
  0.1133  |  2312882332
  4.3293  |  7876123213
  3.1133  |  2312332332
  1.3293  |  7876543213
  0.0033  |  2312222332
  5.3293  |  5344343213
  3.2133  |  4122331112
  2.3293  |  9999942333

1.3293 | 7876543213そして、特定の行(たとえば)を見つけて、最も近い4つの行を選択したいと思います。上に2つ、可能であれば下に2つ。
ソート順は ORDER BY a ASCです。

この場合、私は次のようになります。

  0.0033  |  2312222332
  0.1133  |  2312882332
  2.3293  |  9999942333
  3.1133  |  2312332332

PostgreSQLを使用してこれを達成するにはどうすればよいですか?(ところで、私はPHPを使用しています。)

PS:最後または最初の行の場合、最も近い行は4つ上または4つ下になります。

4

3 に答える 3

6

テストケース:

CREATE TEMP TABLE tbl(a float, user_id bigint);
INSERT INTO tbl VALUES
 (0.1133, 2312882332)
,(4.3293, 7876123213)
,(3.1133, 2312332332)
,(1.3293, 7876543213)
,(0.0033, 2312222332)
,(5.3293, 5344343213)
,(3.2133, 4122331112)
,(2.3293, 9999942333);

クエリ:

WITH x AS (
    SELECT a
          ,user_id
          ,row_number() OVER (ORDER BY a, user_id) AS rn
    FROM   tbl
    ), y AS (
    SELECT rn, LEAST(rn - 3, (SELECT max(rn) - 5 FROM x)) AS min_rn
    FROM   x
    WHERE  (a, user_id) = (1.3293, 7876543213)
    )
SELECT *
FROM   x, y
WHERE  x.rn  > y.min_rn
AND    x.rn <> y.rn
ORDER  BY x.a, x.user_id
LIMIT  4;

質問に示されている結果を返します。(a, user_id)それがユニークだと仮定します。

aユニークであるかどうかは明らかではありません。だから私はuser_idさらにネクタイを壊すために並べ替えます。それが、私がウィンドウ関数row_number()を使用する理由でもありますが、これには使用しません rank()row_number()いずれにせよ正しいツールです。4行が必要です。rank()ソート順にピアが存在する場合、未定義の行数が発生します。

テーブルに少なくとも5行ある限り、これは常に4行を返します。最初/最後の行の近くで、最初/最後の4行が返されます。他のすべての場合の前後の2行。基準行自体は除外されます。


改良された性能

これは、@TimLandscheidtが投稿したものの改良版です。インデックス付きのアイデアが気に入ったら、彼の答えに投票してください。小さなテーブルを気にしないでください。ただし、適切なインデックスが設定されていれば、大きなテーブルのパフォーマンスが向上します。最良の選択は、の複数列のインデックスです(a, user_id)

WITH params(_a, _user_id) AS (SELECT 5.3293, 5344343213) -- enter params once
    ,x AS  (
    (
    SELECT a
          ,user_id
          ,row_number() OVER (ORDER BY a DESC, user_id DESC) AS rn
    FROM   tbl, params p
    WHERE  a < p._a
       OR  a = p._a AND user_id < p._user_id -- a is not defined unique
    ORDER  BY a DESC, user_id DESC
    LIMIT  5  -- 4 + 1: including central row
    )
    UNION ALL -- UNION right away, trim one query level
    (
    SELECT a
          ,user_id
          ,row_number() OVER (ORDER BY a ASC, user_id ASC) AS rn
    FROM   tbl, params p
    WHERE  a > p._a
       OR  a = p._a AND user_id > p._user_id
    ORDER  BY a ASC, user_id ASC
    LIMIT  5
    )
    )
    , y AS (
    SELECT a, user_id
    FROM   x, params p
    WHERE (a, user_id) <> (p._a, p._user_id) -- exclude central row
    ORDER  BY rn  -- no need to ORDER BY a
    LIMIT  4
    )
SELECT *
FROM   y
ORDER  BY a, user_id   -- ORDER result as requested

@Timのバージョンとの主な違い:

  • (a, user_id)だけでなく、検索条件からの質問によるとa。これにより、ウィンドウフレームORDER BYWHERE句が微妙に異なる方法で変更されます。

  • UNIONすぐに、追加のクエリレベルは必要ありません。個別に使用できるようにするには、2つのUNIONクエリを括弧で囲む必要がありますORDER BY

  • 要求に応じて結果を並べ替えます。別のクエリレベルが必要です(ほとんどコストはかかりません)。

  • パラメータは複数の場所で使用されているため、入力を主要なCTEに集中させました。
    繰り返し使用する場合は、このクエリを「そのまま」SQLまたはplpgsql関数にラップできます。

于 2012-03-24T14:29:07.870 に答える
2

そしてもう1つ:

WITH prec_rows AS
  (SELECT a,
          user_id,
          ROW_NUMBER() OVER (ORDER BY a DESC) AS rn
   FROM tbl
   WHERE a < 1.3293
   ORDER BY a DESC LIMIT 4),
     succ_rows AS
  (SELECT a,
          user_id,
          ROW_NUMBER() OVER (ORDER BY a ASC) AS rn
   FROM tbl
   WHERE a > 1.3293
   ORDER BY a ASC LIMIT 4)
SELECT a, user_id
FROM
  (SELECT a,
          user_id,
          rn
   FROM prec_rows
   UNION ALL SELECT a,
                    user_id,
                    rn
   FROM succ_rows) AS s
ORDER BY rn, a LIMIT 4;

AFAIRWITHはメモリテーブルをインスタンス化するため、このソリューションの焦点は、そのサイズを可能な限り制限することです(この場合は8行)。

于 2012-03-25T01:56:34.100 に答える
0
set search_path='tmp';

DROP TABLE lutser;
CREATE TABLE lutser
        ( val float
        , num bigint
        );
INSERT INTO lutser(val, num)
VALUES ( 0.1133  ,  2312882332  )
      ,( 4.3293  ,  7876123213  )
      ,( 3.1133  ,  2312332332  )
      ,( 1.3293  ,  7876543213  )
      ,( 0.0033  ,  2312222332  )
      ,( 5.3293  ,  5344343213  )
      ,( 3.2133  ,  4122331112  )
      ,( 2.3293  ,  9999942333  )
        ;

WITH ranked_lutsers AS (
        SELECT val, num
        ,rank() OVER (ORDER BY val) AS rnk
        FROM lutser
        )
SELECT that.val, that.num
        , (that.rnk-this.rnk) AS relrnk
FROM ranked_lutsers that
JOIN ranked_lutsers this ON (that.rnk BETWEEN this.rnk-2 AND this.rnk+2)
WHERE this.val = 1.3293
        ;

結果:

DROP TABLE
CREATE TABLE
INSERT 0 8
  val   |    num     | relrnk 
--------+------------+--------
 0.0033 | 2312222332 |     -2
 0.1133 | 2312882332 |     -1
 1.3293 | 7876543213 |      0
 2.3293 | 9999942333 |      1
 3.1133 | 2312332332 |      2
(5 rows)

アーウィンが指摘したように、中央の行は出力に必要ありません。また、rank()の代わりにrow_number()を使用する必要があります。

WITH ranked_lutsers AS (
        SELECT val, num
        -- ,rank() OVER (ORDER BY val) AS rnk
        , row_number() OVER (ORDER BY val, num) AS rnk
        FROM lutser
) SELECT that.val, that.num
        , (that.rnk-this.rnk) AS relrnk
FROM ranked_lutsers that
JOIN ranked_lutsers this ON (that.rnk BETWEEN this.rnk-2 AND this.rnk+2 )
WHERE this.val = 1.3293
AND that.rnk <> this.rnk
        ;

結果2:

  val   |    num     | relrnk 
--------+------------+--------
 0.0033 | 2312222332 |     -2
 0.1133 | 2312882332 |     -1
 2.3293 | 9999942333 |      1
 3.1133 | 2312332332 |      2
(4 rows)

UPDATE2:リストの一番上または一番下にいる場合でも、常に4つを選択します。これにより、クエリが少し醜くなります。(しかし、アーウィンほど醜いわけではありません;-)

WITH ranked_lutsers AS (
        SELECT val, num
        -- ,rank() OVER (ORDER BY val) AS rnk
        , row_number() OVER (ORDER BY val, num) AS rnk
        FROM lutser
) SELECT that.val, that.num
        , ABS(that.rnk-this.rnk) AS srtrnk
        , (that.rnk-this.rnk) AS relrnk
FROM ranked_lutsers that
JOIN ranked_lutsers this ON (that.rnk BETWEEN this.rnk-4 AND this.rnk+4 )
-- WHERE this.val = 1.3293
WHERE this.val = 0.1133
AND that.rnk <> this.rnk
ORDER BY srtrnk ASC
LIMIT 4
        ;

出力:

  val   |    num     | srtrnk | relrnk 
--------+------------+--------+--------
 0.0033 | 2312222332 |      1 |     -1
 1.3293 | 7876543213 |      1 |      1
 2.3293 | 9999942333 |      2 |      2
 3.1133 | 2312332332 |      3 |      3
(4 rows)

更新:ネストされたCTEを備えたバージョン(外部​​結合を備えています!!!)。便宜上、テーブルに主キーを追加しました。これはとにかく私見の良いアイデアのように聞こえます。

WITH distance AS (
        WITH ranked_lutsers AS (
        SELECT id
        , row_number() OVER (ORDER BY val, num) AS rnk
        FROM lutser
        ) SELECT l0.id AS one
        ,l1.id AS two
        , ABS(l1.rnk-l0.rnk) AS dist
        -- Warning: Cartesian product below
        FROM ranked_lutsers l0
        , ranked_lutsers l1 WHERE l0.id <> l1.id

        )
SELECT lu.*
FROM lutser lu
JOIN distance di
ON lu.id = di.two
WHERE di.one= 1
ORDER by di.dist
LIMIT 4 
        ;
于 2012-03-24T14:29:56.323 に答える