2

ユーザーとプロジェクトの間に多対多の関係があるとします。1人のユーザーが複数のプロジェクトに属し、1つのプロジェクトに複数のユーザーがいる場合があります。この関係はテーブルにエンコードされていますuser_projects

create table user_projects
(
proj_id int references projs(id) not null,
user_id int references users(id) not null,
primary key (proj_id, user_id)
);

これが私の問題です。ユーザーのセット(user1、user2、...)が与えられた場合、与えられたユーザーのセットがすべてのユーザーのサブセットであるすべてのプロジェクトを選択したいと思います。

たとえば、以下のデータを挿入してから、ユーザー1と2のすべてのプロジェクトを要求した場合、クエリはプロジェクト1のみを返す必要があります。

insert into user_projects values (1, 1);
insert into user_projects values (1, 2);
insert into user_projects values (1, 3);
insert into user_projects values (2, 1);
insert into user_projects values (2, 3);

(最良の解決策がたまたま非標準である場合、私はPostgreSQLを使用しています。)

編集:明確にするために、ユーザーのセットは、返すプロジェクトのリストの制約として解釈する必要があります。セット{u1、u2}は、プロジェクトのリストに、少なくともユーザーu1とu2を持つプロジェクトのみを含める必要があることを意味します。セット{u1}は、少なくともユーザーu1を持つすべてのプロジェクトが返されることを意味し、限定的なケースとして、空のセットはすべてのプロジェクトが返されることを意味します。

4

6 に答える 6

5
Select project_ID 
from user_projects
where user_ID in (1,2)
group by project_ID
Having count(*) = 2

2 人のユーザーがいて、それらが一意 (主キー) であることがわかっているので、同じプロジェクトに 2 つのレコードがある場合、それが必要なレコードであることがわかります。

あなたの質問は、ユーザーの GIVEN が送信されたことを示しています。そのため、どのユーザーが何人いるかがわかります。上記の SQL は、これらの既知のパラメーターを受け入れるように更新できるため、2 人のユーザーだけに限定されず、動的なままになります。

where user_ID in (userlist)
having count(*) = (cntuserList)

-----------ユーザーのセットが空の場合の状況を処理するには-----

Select P.project_ID 
from Projects P
LEFT JOIN user_projects UP
where (UP.user_ID in (1,2) OR UP.USER_ID is null)
group by project_ID
Having count(*) = 2

これが何をするかです。すべてのプロジェクトを返し、そのプロジェクトに所属するユーザーがいる場合はそれらを識別します。セットにユーザーが含まれている場合、返されるプロジェクトのリストはそのセットによってフィルター処理され、セット全体が hasing 句によってプロジェクト内にあることが保証されます。

セットが空の場合、LEFT join と userID is null ステートメントを使用すると、セットが空かどうかに関係なく、ユーザーがリストされていないプロジェクトが保持されます。having 句は、セット内で定義したユーザーの数までセットをさらに減らします。または、ユーザーが割り当てられていないすべてのプロジェクトを返すことを示す 0 を指定します。

まだ説明していないもう 1 つのエッジ ケースは、セットで定義したよりも多くのユーザーがプロジェクトに含まれている場合にどうなるかです。現在、このプロジェクトは返還されます。しかし、私はそれがあなたが望んでいたものであるとは確信していません.

余談ですが、考えさせてくれてありがとう。私はもうコードに深く入り込むことができません。そのため、私は時々ここでトロールして、私が助けられるかどうかを確認しています!

于 2013-01-25T17:29:42.410 に答える
2

この種の関係分割は、多くの場合、次のように表現できます。SELECT FROM a WHERE NOT EXISTS ( b WHERE NOT EXISTS (c))

WITH users AS (
        SELECT generate_series (1,2)::integer AS user_id
        )
SELECT DISTINCT up.proj_id
FROM user_projects up
   -- all the projects, but
   -- NOT the ones that miss (at least) one of the users
WHERE NOT EXISTS (
        SELECT *
        FROM users us
          -- The projects that miss (at least) one of the users
        WHERE NOT EXISTS (
                SELECT *
                FROM user_projects nx
                WHERE nx.user_id = us.user_id AND nx.proj_id = up.proj_id
                )
        )
        ;
于 2013-01-26T16:01:06.893 に答える
2

これがもう1つの解決策です。一見より簡単に見えます。

select  proj_id
from    user_projects
group by proj_id
having  array_agg ( user_id ) @> array [1, 2]

@Thilo が気づいたように、ユーザーが割り当てられていないプロジェクトが存在する可能性があります。したがって、ユーザーの入力セットが空の場合、クエリは projs テーブルからすべてのプロジェクトを返す必要があります。改善されたソリューションは次のとおりです。

select      p.proj_id
from        projs           p
left join   user_projects   up
    on      p.proj_id = up.proj_id
group by    p.proj_id
having      array_agg ( up.user_id ) @> array (
    select  u
    from    generate_series ( 1, 2 )
    where   false   /* an empty set */
    )
;

評価されたソリューションのパフォーマンスをしばらくテストしてきました。小さなデータ セット (user_projects の 1,670 行) をクエリするときに大きな違いがなかった限り、テーブル user_projects に 1,667,000 行があった場合
(列 proj_id と user_id は 1 から 1,000,000 までのランダムな値で埋められました。 1 つのプロジェクトで平均 2 ユーザー、最大 11 ユーザー):

  • array_agg メソッド (projs および user_projects からの読み取り) は、通常 24 秒 (場合によってはそれ以下) で結果が得られます。
  • Wildplasser のアプローチ: 常に 31 秒。
  • Thilo のクエリに時間がかかりすぎたため、キャンセルすることにしました。
  • インデックスに強く依存する xQbert の「count」メソッドは、何倍も高速で、ほとんど常に0.5 秒しかかかりませんでした。ただし、ユーザーの空の入力セットを処理するには、書き直す必要があります。

[テストは、最新ではない PC 上の Postgresql 9.2.2 で実行されましたが、新しい PC 上の Postgresql 8.4 では比率は同様でした]。

于 2013-01-26T13:30:06.390 に答える
1

同じ量のコードで任意のユーザー セットを使用できる、より一般的な回答です。まず、ユーザー セットでテーブルを作成します。

CREATE TEMP TABLE user_set ( 
  u int
);
INSERT INTO user_set VALUES (1), (2);

FROMこのテーブルは、以下の句に入れることができる任意の関数で置き換えることができます。

実際のプロジェクトを選択します。

SELECT DISTINCT 
    proj_id 
FROM 
    user_projects 
WHERE 
    true = ALL (
        -- Select all required users and test if they are a member of the project
        SELECT u IN (
            -- Select all user ids of this project
            SELECT 
                user_id 
            FROM 
                user_projects AS up 
            WHERE 
                up.proj_id = user_projects.proj_id
        )
        FROM 
            user_set
   )

そしてフィドル

于 2013-01-25T17:39:36.643 に答える
0

このようなものが動作するはずです:

SELECT u.proj_id
FROM user_projects u
   JOIN user_projects u2 on u.proj_id = u2.proj_id
WHERE u.user_id = 1 and u2.user_id = 2

そして、ここにFiddleがあります。

幸運を。

于 2013-01-25T17:29:47.210 に答える
0

次のように、複数の JOIN ブロックを使用できます。

 SELECT Up1.project_id
   FROM user_projects as up1
   JOIN user_projects as up2 on up1.project_id=up2.project_id
  WHERE up1.user_id=1
    AND up2.user_id=2;

目的のセット内のすべてのユーザーに対して、新しい JOIN ブロックを作成する必要があります。

于 2013-01-25T17:35:36.690 に答える