2

studentIDとを持つテーブルから、まったく同じクラスを受講する生徒のペアを見つけなければなりませんcourseID

studentID | courseID
    1           1
    1           2
    1           3
    2           1
    3           1
    3           2
    3           3 

クエリは を返し(1, 3)ます。また、結果にはや
などの重複する行があってはなりません。(1,3)(3,1)

4

5 に答える 5

2
select courses,group_concat(studentID) from
(select studentID, 
group_concat(courseID order by courseID) as courses
from Table1 group by studentID) abc
group by courses having courses like('%,%');

フィドル

于 2013-07-29T06:58:36.027 に答える
1

テストケース:

やや現実的なテスト ケースを作成しました。

CREATE TEMP TABLE student_course (
   student_id integer
  ,course_id integer
  ,PRIMARY KEY (student_id, course_id)
);
INSERT INTO student_course
SELECT *
FROM (VALUES (1, 1), (1, 2), (1, 3), (2, 1), (3, 1), (3, 2), (3, 3)) v
      -- to include some non-random values in test
UNION  ALL
SELECT DISTINCT student_id, normal_rand((random() * 30)::int, 1000, 35)::int
FROM   generate_series(4, 5000) AS student_id;
DELETE FROM student_course WHERE random() > 0.9; -- create some dead tuples
ANALYZE student_course; -- needed for temp table

normal_rand()を使用してダミー テーブルに値の正規分布を設定していることに注意してください。それはtablefuncモジュールに同梱されており、とにかくそれをさらに下で使用するので...

また、さまざまなテスト ケースをシミュレートするためにベンチマークで操作する数値が強調されていることに注意してください。

プレーン SQL

質問はかなり基本的で不明確です。一致するコースを持つ最初の 2 人の学生を見つけますか? それとも全部見つけますか?それらのカップルまたは同じコースを共有する学生のグループを見つけますか? Craig の回答:同じコースを共有しているすべてのカップル
を見つけます。

C1 -クレイグの最初のクエリ

単純な SQL CTE と配列によるグループ化を使用し、わずかにフォーマットします。

WITH student_coursearray(student_id, courses) AS (
   SELECT student_id, array_agg(course_id ORDER BY course_id)
   FROM   student_course
   GROUP  BY student_id
   )
SELECT a.student_id, b.student_id
FROM   student_coursearray a
JOIN   student_coursearray b ON (a.courses = b.courses)
WHERE  a.student_id < b.student_id
ORDER  BY a.student_id, b.student_id;

Craig の回答の2 番目のクエリは、すぐにレースから脱落しました。数行以上になると、パフォーマンスが急速に低下します。CROSS JOIN毒です。

E1 - 改良版

大きな弱点が 1 つあります。ORDER BY集計ごとのパフォーマンスが悪いためORDER BY、サブクエリで次のように書き直しました。

WITH cte AS (
   SELECT student_id, array_agg(course_id) AS courses
   FROM  (SELECT student_id, course_id FROM student_course ORDER BY 1, 2) sub
   GROUP  BY student_id
   )
SELECT a.student_id, b.student_id
FROM   cte a
JOIN   cte b USING (courses)
WHERE  a.student_id < b.student_id
ORDER  BY 1,2;

E2 - 質問の別の解釈

一般的により有用なケースは、同じコースを共有しているすべての学生
を見つけることだと思います。
そのため、コースが一致する学生の配列を返します。

WITH s AS (
   SELECT student_id, array_agg(course_id) AS courses
   FROM  (SELECT student_id, course_id FROM student_course ORDER BY 1, 2) sub
   GROUP  BY student_id
   )
SELECT array_agg(student_id)
FROM   s
GROUP  BY courses
HAVING count(*) > 1
ORDER    BY array_agg(student_id);

F1 - 動的 PL/pgSQL 関数

これを汎用的かつ高速にするために、動的 SQL を使用してplpgsql 関数にラップしました。

CREATE OR REPLACE FUNCTION f_same_set(_tbl regclass, _id text, _match_id text)
  RETURNS SETOF int[] AS
$func$
BEGIN

RETURN QUERY EXECUTE format(
   $f$
   WITH s AS (
      SELECT %1$I AS id, array_agg(%2$I) AS courses
      FROM   (SELECT %1$I, %2$I FROM %3$s ORDER BY 1, 2) s
      GROUP  BY 1
      )
   SELECT array_agg(id)
   FROM   s
   GROUP  BY courses
   HAVING count(*) > 1
   ORDER    BY array_agg(id)
   $f$
   ,_id, _match_id, _tbl
   );
END
$func$  LANGUAGE plpgsql;

電話:

SELECT * FROM f_same_set('student_course', 'student_id', 'course_id');

数値列を持つすべてのテーブルで機能します。他のデータ型に拡張するのも簡単です。

crosstab()

追加のtablefuncモジュール によって提供される比較的少数courses(そして任意に多数の) 学生に対しては、PostgreSQL の別のオプションです。ここでより一般的な情報: PostgreSQL クロス集計クエリcrosstab()

シンプルなケース

リンクされた回答で説明されているのと同じように、質問の簡単な例の簡単なケース:

SELECT array_agg(student_id)
FROM   crosstab('
     SELECT student_id, course_id, TRUE
     FROM   student_course
     ORDER  BY 1'

   ,'VALUES (1),(2),(3)'
   )
AS t(student_id int, c1 bool, c2 bool, c3 bool)
GROUP  BY c1, c2, c3
HAVING count(*) > 1;

F2 - 動的クロス集計関数

単純なケースでは、クロス集計バリアントの方が高速だったので、動的 SQL を使用して plpgsql 関数を作成し、テストに含めました。機能的にはF1と同じです。

CREATE OR REPLACE FUNCTION f_same_set_x(_tbl regclass, _id text, _match_id text)
  RETURNS SETOF int[] AS
$func$
DECLARE
   _ids int[];   -- for array of match_ids (course_id in example)
BEGIN

-- Get list of match_ids
EXECUTE format(
   'SELECT array_agg(DISTINCT %1$I ORDER BY %1$I) FROM %2$s',_match_id, _tbl)
INTO _ids;

-- Main query
RETURN QUERY EXECUTE format(
   $f$
   SELECT array_agg(%1$I)
   FROM   crosstab('SELECT %1$I, %2$I, TRUE FROM %3$s ORDER BY 1'
                  ,'VALUES (%4$s)')
      AS t(student_id int, c%5$s  bool)
   GROUP  BY c%6$s
   HAVING count(*) > 1
   ORDER    BY array_agg(student_id)
   $f$
   ,_id
   ,_match_id
   ,_tbl
   ,array_to_string(_ids, '),(')     -- values
   ,array_to_string(_ids, ' bool,c') -- column def list
   ,array_to_string(_ids, ',c')      -- names
   );
END
$func$  LANGUAGE plpgsql;

電話:

SELECT * FROM f_same_set_x('student_course', 'student_id', 'course_id');

基準

小規模な PostgreSQL テスト サーバーでテストしました。〜 6 年前の AMD Opteron サーバー上の Debian Linux 上の PostgreSQL 9.1.9。上記の設定と提示された各クエリを使用して、5 つのテスト セットを実行しました。ベスト オブ 5 とEXPLAIN ANALYZE.

上記のテストケースの太字の数字にこれらの値を使用して入力しました。

番号 生徒数/最大 番号 コース数 / 標準偏差 (より明確な course_id の結果)
1. 1000 / 30 / 35
2. 5000 / 30 / 50
3. 10000 / 30 / 100
4. 10000 / 10 / 10
5. 10000 / 5 / 5

C1
1. 合計実行時間: 57 ミリ秒
2. 合計実行時間: 315 ミリ秒
3. 合計実行時間: 663 ミリ秒
4. 合計実行時間: 543 ミリ秒
5. 合計実行時間: 2345 ミリ秒(!) - 多くのペアで劣化する

E1
1. 合計実行時間: 46 ミリ秒
2. 合計実行時間: 251 ミリ秒
3. 合計実行時間: 529 ミリ秒
4. 合計実行時間: 338 ミリ秒
5. 合計実行時間: 734 ミリ秒

E2
1. 合計実行時間: 45 ミリ秒
2. 合計実行時間: 245 ミリ秒
3. 合計実行時間: 515 ミリ秒
4. 合計実行時間: 218 ミリ秒
5. 合計実行時間: 143 ミリ秒

F1 勝利者
1. 合計実行時間: 14 ミリ秒
2. 合計実行時間: 77 ミリ秒
3. 合計実行時間: 166 ミリ秒
4. 合計実行時間: 80 ミリ秒
5. 合計実行時間: 54 ミリ秒

F2
1. 合計実行時間: 62 ミリ秒
2. 合計実行時間: 336 ミリ秒
3. 合計実行時間: 1053 ミリ秒 (!) crosstab() は多くの異なる値で劣化します
4. 合計実行時間: 195 ミリ秒
5. 合計実行時間: 105 ミリ秒 (!)しかし、より少ない個別の値でうまく機能します

動的 SQL を使用した PL/pgSQL 関数、サブクエリ内の行の並べ替えは明らかに勝利者です。

于 2013-07-30T08:14:44.390 に答える