有没有办法确保在DISTINCT之后发生WHERE子句?

时间:2019-02-14 12:43:30

标签: sql postgresql elixir 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    |

这将隐藏最终用户的评论。

现在发表第二条评论(不是第一条评论的更新)

| 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_no。 (应返回id=3id=4
  2. 选择show = true(应仅返回id=4的地方)
  

注意:我实际上是在使用ecto在elixir中编写此查询,并且希望能够在不使用subquery函数的情况下执行此操作。如果有人可以在sql中回答这个问题,我可以自己转换答案。如果有人知道如何用长生不老药回答这个问题,那么也可以随时回答。

5 个答案:

答案 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';

Examples on DB<>Fiddle

答案 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