web-dev-qa-db-ja.com

DISTINCTの後にWHERE句が発生するようにする方法はありますか?

データベースにテーブルcommentsがあるとします。

コメントテーブルには、列idtextshowcomment_id_noがあります。

ユーザーがコメントを入力すると、データベースに行が挿入されます

| id |  comment_id_no | text | show | inserted_at |
| -- | -------------- | ---- | ---- | ----------- |
| 1  | 1              | hi   | true | 1/1/2000    |

ユーザーがそのコメントを更新したい場合、新しい行をデータベースに挿入します

| id |  comment_id_no | text | show | inserted_at |
| -- | -------------- | ---- | ---- | ----------- |
| 1  | 1              | hi   | true | 1/1/2000    |
| 2  | 1              | hey  | true | 1/1/2001    |

同じcomment_id_noを保持していることに注意してください。これにより、コメントの履歴を確認できるようになります。

ユーザーは、コメントを表示したくないと判断しました

| id |  comment_id_no | text | show  | inserted_at |
| -- | -------------- | ---- | ----- | ----------- |
| 1  | 1              | hi   | true  | 1/1/2000    |
| 2  | 1              | hey  | true  | 1/1/2001    |
| 3  | 1              | hey  | false | 1/1/2002    |

これにより、コメントがエンドユーザーに表示されなくなります。

ここで、2番目のコメントが作成されます(最初のコメントは更新されません)。

| id |  comment_id_no | text | show  | inserted_at |
| -- | -------------- | ---- | ----- | ----------- |
| 1  | 1              | hi   | true  | 1/1/2000    |
| 2  | 1              | hey  | true  | 1/1/2001    |
| 3  | 1              | hey  | false | 1/1/2002    |
| 4  | 2              | new  | true  | 1/1/2003    |

私ができることは、一意のcommend_id_noのすべての最新バージョンを選択することです。ここで、showはtrueに等しいです。ただし、クエリがid=2を返したくありません。

クエリが実行する必要がある手順...

  1. 最新の個別のcomment_id_nosをすべて選択します。 (id=3およびid=4を返す必要があります)
  2. show = trueを選択します(id=4のみを返す必要があります)

注:私は実際にectoを使用してエリクサーでこのクエリを作成しており、サブクエリ関数を使用せずにこれを実行できるようにしたいと考えています。誰かがsqlでこれに答えることができれば、自分で答えを変換できます。誰かがエリクサーでこれに答える方法を知っている場合は、お気軽に答えてください。

25
RobStallion

_LEFT JOIN_を使用して、サブクエリを使用せずにこれを行うことができます。

_SELECT  c.id, c.comment_id_no, c.text, c.show, c.inserted_at
FROM    Comments AS c
        LEFT JOIN Comments AS c2
            ON c2.comment_id_no = c.comment_id_no
            AND c2.inserted_at > c.inserted_at
WHERE   c2.id IS NULL
AND     c.show = 'true';
_

他のすべてのアプローチには何らかのサブクエリが必要になると思います。これは通常、ランキング関数で行われます。

_SELECT  c.id, c.comment_id_no, c.text, c.show, c.inserted_at
FROM    (   SELECT  c.id, 
                    c.comment_id_no, 
                    c.text, 
                    c.show, 
                    c.inserted_at,
                    ROW_NUMBER() OVER(PARTITION BY c.comment_id_no 
                                      ORDER BY c.inserted_at DESC) AS RowNumber
            FROM    Comments AS c
        ) AS c
WHERE   c.RowNumber = 1
AND     c.show = 'true';
_

Postgresqlでタグ付けしたので、DISTINCT ON ()を使用することもできます。

_SELECT  *
FROM    (   SELECT  DISTINCT ON (c.comment_id_no) 
                    c.id, c.comment_id_no, c.text, c.show, c.inserted_at
            FROM    Comments AS c 
            ORDER By c.comment_id_no, inserted_at DESC
        ) x
WHERE   show = 'true';
_

DB <>フィドルの例

14
GarethD

私がコメントで言ったように、私は履歴/聴覚的なものでデータテーブルを汚染することを勧めません。

いいえ:@Josh_Ellerのコメントで提案されている「ダブルバージョン管理」も良い解決策ではありません。クエリを不必要に複雑にするだけでなく、処理とテーブルスペースの断片化の点ではるかにコストがかかるためです。

[〜#〜] update [〜#〜]操作は何も更新しないことに注意してください。代わりに、行のまったく新しいバージョンを書き込み、古いものを削除済みとしてマークします。そのため、テーブルスペースをデフラグしてそのスペースを回復するには、バキュームプロセスが必要です。

いずれにせよ、次善の場合を除いて、そのアプローチではデータの読み取りと書き込みを行うためのより複雑なクエリを実装する必要がありますが、実際には、ほとんどの場合、単一の行を選択、挿入、更新、または削除するだけで、最終的には最終的にのみ必要になると思います。 、その歴史を調べてください。

したがって、最良の解決策(IMHO)は、メインタスクに実際に必要なスキーマを実装し、別のテーブルに傍聴者を実装し、トリガーによって維持することです。

これははるかに多くなります:

  • 堅牢でシンプル:毎回1つのことに集中するため(単一の責任とKISS原則)。

  • Fast:afterトリガーで聴覚操作を実行できるため、[〜#〜]挿入[〜#〜][〜#〜]更新[〜#〜]、または[〜#〜] delete [〜#〜]データベースエンジンは、その結果が勝ったことを知っているため、トランザクション内の可能なロックはまだ解放されています変わらない。

  • 効率的:つまりもちろん、更新によって新しい行が挿入され、古い行は削除済みとしてマークされます。しかし、これはデータベースエンジンによって低レベルで行われ、それ以上に、聴覚データは完全に断片化されません(そこに書き込むだけなので、更新しないでください)。したがって、全体的な断片化は常にはるかに少なくなります。

とはいえ、それをどのように実装するのですか?

次の単純なスキーマを想定します。

create table comments (
    text text,
    mtime timestamp not null default now(),
    id serial primary key
);

create table comments_audit ( -- Or audit.comments if using separate schema
    text text,
    mtime timestamp not null,
    id integer,
    rev integer not null,
    primary key (id, rev)
);

...そしてこの関数とトリガー:

create or replace function fn_comments_audit()
returns trigger
language plpgsql
security definer
    -- This allows you to restrict permissions to the auditory table
    -- because the function will be executed by the user who defined
    -- it instead of whom executed the statement which triggered it.
as $$
DECLARE
BEGIN

    if TG_OP = 'DELETE' then
        raise exception 'FATAL: Deletion is not allowed for %', TG_TABLE_NAME;
        -- If you want to allow deletion there are a few more decisions to take...
        -- So here I block it for the sake of simplicity ;-)
    end if;

    insert into comments_audit (
        text
        , mtime
        , id
        , rev
    ) values (
        NEW.text
        , NEW.mtime
        , NEW.id
        , coalesce (
            (select max(rev) + 1 from comments_audit where id = new.ID)
            , 0
        )
    );

    return NULL;

END;
$$;

create trigger tg_comments_audit
    after insert or update or delete
    on public.comments
    for each row
    execute procedure fn_comments_audit()
;

そして、それだけです。

このアプローチでは、常に現在のcommentsデータがcomments_audit。代わりに、OLDレジスタを使用して、UPDATE(およびDELETE)操作でのみトリガーを定義して、それを回避することもできます。

しかし、私はこのアプローチを好むだけでなく、余分な冗長性(偶発的な削除-許可された場合、または誤って無効にされたトリガー-マスターテーブル)を提供するためだけではなく、監査テーブルからすべてのデータを回復できます)また、必要なときに履歴のクエリを簡素化(および最適化)するためでもあります。

これで、聴覚システムではない場合とまったく同じように、完全に透過的な方法で挿入、更新、または選択(または、このスキーマをもう少し開発した場合、つまりnullを含む行を挿入することで削除)するだけで済みます。そして、そのデータが必要な場合は、代わりに聴覚テーブルをクエリするだけで済みます。

注:さらに、作成タイムスタンプ(ctime)を含めることもできます。この場合、[〜#〜] before [〜#〜]トリガーで変更されないようにするのは興味深いので、省略しました(再び簡単にするために)すでに聴覚テーブルのmtimesから推測できるため(たとえそれをアプリケーションを追加することをお勧めします)。

4
bitifet

私はあなたが望むと思います:

select c.*
from comments c
where c.inserted_at = (select max(c2.inserted_at)
                       from comments c2
                       where c2.comment_id_no = c.comment_id_no
                      ) and
      c.show = 'true';

これがselect distinctとどう関係しているのかわかりません。コメントの最後のバージョンが必要で、それを表示できるかどうかを確認するだけです。

編集:

Postgresでは、次のようにします。

select c.*
from (select distinct on (comment_id_no) c.*
      from comments c
      order by c.comment_id_no, c.inserted_at desc
     ) c
where c.show

distinct onは通常、かなり良いパフォーマンス特性を備えています。

4
Gordon Linoff

Postgres 8.4以降を実行している場合、ROW_NUMBER()が最も効率的なソリューションです。

SELECT *
FROM (
    SELECT c.*, ROW_NUMBER() OVER(PARTITION BY comment_id_no ORDER BY inserted_at DESC) rn
    FROM comments c
    WHERE c.show = 'true'
) x WHERE rn = 1

そうでなければ、これはWHERE NOT EXISTS条件。これにより、最新のコメントが確実に表示されます。

SELECT c.*
FROM comments c
WHERE 
    c.show = 'true '
    AND NOT EXISTS (
        SELECT 1 
        FROM comments c1 
        WHERE c1.comment_id_no = c.comment_id_no AND c1.inserted_at > c.inserted_at
    )
2
GMB

最新のIDを取得するにはgroup byを使用し、コメントテーブルに結合してshow = falseの行を除外する必要があります。

select c.* 
from comments c inner join (
  select comment_id_no, max(id) maxid
  from comments
  group by comment_id_no 
) g on g.maxid = c.id
where c.show = 'true'

idは一意であり、commentsテーブルで自動インクリメントすると想定しています。
デモ を参照してください

1
forpas