Как получить строки с максимальным обновлением datetime, используя GROUP BY и HAVING с SQLAlchemy и Postgresql - PullRequest
0 голосов
/ 29 марта 2019

Я иду из SQLite в Postgresql.Это сделало один из моих запросов не работает.Мне не ясно, почему этот запрос разрешен в SQLite, но не в Postgresql.Данный запрос приведен ниже в функции find_recent_by_section_id_list().

Я пытался переписать запрос несколькими способами, но меня смущает то, что этот запрос работал, когда я работал с SQLite.

Установки: Flask, SQLAlchemy, Flask-SQLAlchemy и Postgresql.

class SectionStatusModel(db.Model):

    __tablename__ = "sectionstatus"
    _id = db.Column(db.Integer, primary_key=True)
    update_datetime = db.Column(db.DateTime, nullable=False)
    status = db.Column(db.Integer, nullable=False, default=0)
    section_id = db.Column(db.Integer, db.ForeignKey("sections._id"), nullable=False)

    __table_args__ = (
        UniqueConstraint("section_id", "update_datetime", name="section_time"),
    )


    @classmethod
    def find_recent_by_section_id_list(
        cls, section_id_list: List
    ) -> List["SectionStatusModel"]:

        return (
            cls.query.filter(cls.section_id.in_(section_id_list))
            .group_by(cls.section_id)
            .having(func.max(cls.update_datetime) == cls.update_datetime)
        )

Я ожидаю, что этот запрос вернет последние статусы раздела для каждого раздела, однако я получаю следующую ошибку:

E       sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) column "sectionstatus._id" must appear in the GROUP BY clause or be used in an aggregate function
E       LINE 1: SELECT sectionstatus._id AS sectionstatus__id, sectionstatus...
E                      ^
E       
E       [SQL: SELECT sectionstatus._id AS sectionstatus__id, sectionstatus.update_datetime AS sectionstatus_update_datetime, sectionstatus.status AS sectionstatus_status, sectionstatus.section_id AS sectionstatus_section_id 
E       FROM sectionstatus 
E       WHERE sectionstatus.section_id IN (%(section_id_1)s, %(section_id_2)s) GROUP BY sectionstatus.section_id 
E       HAVING max(sectionstatus.update_datetime) = sectionstatus.update_datetime]
E       [parameters: {'section_id_1': 1, 'section_id_2': 2}]
E       (Background on this error at: http://sqlalche.me/e/f405)

Это вывод набора тестов.

1 Ответ

2 голосов
/ 10 апреля 2019

Запрос разрешен в SQLite, поскольку он позволяет SELECT элементам списка ссылаться на несгруппированные столбцы вне агрегатных функций или без того, чтобы указанные столбцы были функционально зависимы от выражений группировки. Неагрегированные значения выбираются из произвольной строки в группе.

Кроме того, в sidenote задокументировано, что специальная обработка «пустых» столбцов в агрегированном запросе происходит, когда агрегат равен min() или max() 1 :

Когда агрегатные функции min() или max() используются в агрегированном запросе, все пустые столбцы в наборе результатов получают значения из входной строки, которая также содержит минимум или максимум.

Это относится только к простым запросам, и снова возникает неоднозначность, если более 1 строки имеют одинаковые min / max или запрос содержит более 1 вызова min() / max().

Это делает SQLite несоответствующим в этом отношении, по крайней мере, со стандартом SQL: 2003 (я уверен, что это не сильно изменилось в более новых версиях):

7.12 <спецификация запроса>

Функция

Укажите таблицу, полученную из результата <выражения таблицы>.

Формат

<query specification> ::=
    SELECT [ <set quantifier> ] <select list> <table expression>

...

Правила соответствия

...

3) Без функции T301 «Функциональные зависимости» в соответствующем языке SQL, если T является сгруппированной таблицей, то в каждом <значении-выражении>, содержащемся в <списке выбора>, каждая <ссылка на столбец>, которая ссылается на столбец T должен ссылаться на столбец группировки или указываться в агрегированном аргументе <спецификации функции набора>.

Большинство других СУБД SQL, таких как Postgresql, более точно следуют стандарту в этом отношении и требуют, чтобы список SELECT агрегированного запроса состоял только из выражений группировки, агрегатных выражений или что любые разгруппированные столбцы являются функционально зависимыми на сгруппированных столбцах.

В Postgresql требуется другой подход, чтобы получить такой результат . Есть много замечательных постов , которые освещают эту тему, но вот краткое изложение одного подхода, специфичного для Postgresql Используя расширение DISTINCT ON в сочетании с ORDER BY, вы можете достичь тех же результатов:

@classmethod
def find_recent_by_section_id_list(
        cls, section_id_list: List) -> List["SectionStatusModel"]:
    return (
        cls.query
        .filter(cls.section_id.in_(section_id_list))
        .distinct(cls.section_id)
        # Use _id as a tie breaker, in order to avoid non-determinism
        .order_by(cls.section_id, cls.update_datetime.desc(), cls._id)
    )

Естественно, это потом сломается в SQLite, так как он не поддерживает DISTINCT ON. Если вам нужно решение, которое работает в обоих случаях, используйте подход оконной функции row_number().


1: обратите внимание, что это означает, что ваше предложение HAVING на самом деле не сильно фильтруется, поскольку разгруппированное значение всегда будет выбираться из строки, содержащей максимальное значение. Это просто присутствие этого max(update_datetime), которое делает трюк.

...