データベースにテーブルcomments
があるとします。
コメントテーブルには、列id
、text
、show
、comment_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
を返したくありません。
クエリが実行する必要がある手順...
comment_id_no
sをすべて選択します。 (id=3
およびid=4
を返す必要があります)id=4
のみを返す必要があります)注:私は実際にectoを使用してエリクサーでこのクエリを作成しており、サブクエリ関数を使用せずにこれを実行できるようにしたいと考えています。誰かがsqlでこれに答えることができれば、自分で答えを変換できます。誰かがエリクサーでこれに答える方法を知っている場合は、お気軽に答えてください。
_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';
_
私がコメントで言ったように、私は履歴/聴覚的なものでデータテーブルを汚染することを勧めません。
いいえ:@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から推測できるため(たとえそれをアプリケーションを追加することをお勧めします)。
私はあなたが望むと思います:
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
は通常、かなり良いパフォーマンス特性を備えています。
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
)
最新の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
テーブルで自動インクリメントすると想定しています。
デモ を参照してください