假设您的数据库中有一个表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 |
这将隐藏最终用户的评论。
现在发表第二条评论(不是第一条评论的更新)
| 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
。 (应返回id=3
和id=4
)id=4
的地方)注意:我实际上是在使用ecto在elixir中编写此查询,并且希望能够在不使用subquery函数的情况下执行此操作。如果有人可以在sql中回答这个问题,我可以自己转换答案。如果有人知道如何用长生不老药回答这个问题,那么也可以随时回答。
答案 0 :(得分:14)
您可以在不使用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';
答案 1 :(得分:4)
我想你想要
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
通常具有很好的性能特征。
答案 2 :(得分:4)
正如我在评论中告诉我的那样,我不建议用历史/听觉的东西来污染数据表。
并且没有:@Josh_Eller在他的评论中建议的“双重版本”不是 也是一个很好的解决方案:不仅用于不必要地使查询复杂化,而且对于 在处理和表空间碎片方面要昂贵得多。
请记住, UPDATE 操作永远不会更新任何内容。他们代替 编写该行的全新版本,并将旧版本标记为已删除。那是 为什么需要真空处理对表空间进行碎片整理才能 恢复空间。
在任何情况下,除了次优之外,这种方法都会迫使您实施更多 复杂的查询来读写数据,而实际上,我想大多数时候您只需要选择,插入,更新甚至删除单行,最后只需要查询其历史即可。
所以最好的解决方案(IMHO)是简单地实现您实际需要的架构 完成您的主要任务并在单独的表格中放置听觉 由触发器维护。
这会更多:
稳健而简单:因为您每次都专注于单个事物(单个 责任和KISS原则)。
快速:听觉操作可以在之后触发器中执行,因此 每次您执行 INSERT , UPDATE 或 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_audit 中。您可以改用OLD寄存器,而仅 在UPDATE(和DELETE)操作中定义触发器以免触发。
但是我更喜欢这种方法,不仅因为它为我们提供了额外的冗余( 意外删除-如果允许或意外触发 禁用-在主表上,那么我们将能够从中恢复所有数据 听觉),还因为它简化了(并优化了)查询 历史记录。
现在,您只需要以完全透明的方式插入,更新或选择(如果您开发了更多这种模式(甚至通过插入带有空值的行...,甚至删除也可以删除)),就像不需要听觉系统。而且,当您需要这些数据时,您只需要查询听觉表即可。
注意:此外,您可能还想添加一个创建时间戳(ctime)。在这种情况下,防止在 BEFORE 触发器中对其进行修改会很有趣,因此我省略了它(为简便起见),因为您已经可以从 mtime s在听觉表中(即使您打算在应用程序中使用它,也建议将其添加)。
答案 3 :(得分:2)
如果您运行的是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
)
答案 4 :(得分:1)
您必须使用group by
获取最新的ID,并使用注释表的联接来过滤出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
表中自动递增。
参见demo