Есть ли способ гарантировать, что предложение WHERE происходит после DISTINCT? - PullRequest
0 голосов
/ 14 февраля 2019

Представьте, что в вашей базе данных есть таблица 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.

Шаги, которые необходимо выполнить для запроса ...

  1. выбрать все самые последние, отдельные comment_id_no с.(должен возвращать id=3 и id=4)
  2. выберите, где show = true (должен возвращать только id=4)

Примечание: я на самом деле пишу этот запросв эликсире с использованием экто и хотел бы иметь возможность сделать это без использования функции подзапроса.Если кто-то может ответить на это в SQL я могу преобразовать ответ сам.Если кто-нибудь знает, как ответить на этот вопрос в эликсире, то не стесняйтесь ответить.

Ответы [ 5 ]

0 голосов
/ 15 февраля 2019

Как я уже говорил в комментариях, я не советую загрязнять таблицы данных историей / слухом.

И нет: "двойное управление версиями", предложенное @Josh_Eller в его комментарии, не являетсяХорошее решение также: не только для ненужного усложнения запросов, но и для того, чтобы быть намного более дорогим с точки зрения обработки и фрагментации табличного пространства.

Помните, что операции UPDATE никогда ничего не обновляют.Вместо этого они пишут новую версию строки и отмечают старую как удаленную.Вот почему вакуумные процессы необходимы для дефрагментации табличных пространств, чтобы восстановить это пространство.

В любом случае, кроме неоптимального, этот подход заставляет вас реализовывать более сложные запросы для чтения и записи данных, хотя на самом делеЯ полагаю, что в большинстве случаев вам нужно будет только выбрать, вставить, обновить или даже удалить одну строку и только в конечном итоге просмотреть ее историю.

Так что лучшее решение (IMHO) - просто реализовать схемуВы действительно нуждаетесь в своей основной задаче и внедряете слуховой аппарат в отдельную таблицу и обслуживаете его триггером.

Это было бы гораздо больше:

  • Надежныйи просто: Поскольку каждый раз вы сосредотачиваетесь на одной вещи (принципы единой ответственности и KISS).

  • Fast: Аудиторские операции могут выполняться в после срабатывания , поэтому каждый раз при выполнении 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 в слуховой таблице (даже если вы собираетесь использовать его в своем приложении, было бы очень желательно добавить его).

0 голосов
/ 14 февраля 2019

Вы можете сделать это без использования подзапроса, используя 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';

Примеры в БД <> Fiddle

0 голосов
/ 14 февраля 2019

Если вы используете 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
    )
0 голосов
/ 14 февраля 2019

Вы должны использовать 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 таблице.Смотрите демо

0 голосов
/ 14 февраля 2019

Я думаю, вы хотите:

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 обычно имеет довольно хорошие рабочие характеристики.

...