От строк к столбцам с помощью Peewee ORM - PullRequest
2 голосов
/ 05 февраля 2020

У меня есть объект Peewee, который выглядит следующим образом:

class Status(peewee.Model):
    host = peewee.ForeignKeyField(
        Host,
        backref='checks',
        on_delete='CASCADE')
    check_date = peewee.DateTimeField()
    status = peewee.TextField()

Он записывает результаты выполнения некоторых проверок службы на нескольких хостах. Каждая строка содержит один результат для одного хоста, состоящий из даты проверки и статуса. Получившаяся таблица выглядит примерно так:

+----+---------+----------------------------+---------+
| id | host_id | check_date                 | status  |
+----+---------+----------------------------+---------+
|  1 | 123     | 2020-02-04 17:52:28.716036 | UP      |
|  2 | 321     | 2020-02-04 17:52:28.716036 | REFUSED |
|  3 | 555     | 2020-02-04 17:52:28.716036 | UP      |
...
| 50 | 123     | 2020-02-04 21:21:48.319062 | TIMEOUT |
| 51 | 321     | 2020-02-04 21:21:48.319062 | UNKNOWN |
| 52 | 555     | 2020-02-04 21:21:48.319062 | UP      |
+----+---------+----------------------------+---------+

Я хочу создать сводный вид, например:

+----------------------------+-----+---------+---------+---------+-------+
| check_date                 | UP  | REFUSED | TIMEOUT | UNKNOWN | TOTAL |
+----------------------------+-----+---------+---------+---------+-------+
| 2020-02-04 17:52:28.716036 | 221 | 34      | 10      | 2       | 267   |
| 2020-02-04 21:21:48.319062 | 230 | 30      | 15      | 4       | 279   |
+----------------------------+-----+---------+---------+---------+-------+

Я могу сделать это в SQL, например:

select
  check_date, 
  count(*) filter (where status = 'UP') as UP,
  count(*) filter (where status = 'REFUSED') as REFUSED,
  count(*) filter (where status = 'TIMEOUT') as TIMEOUT,
  count(*) filter (where status = 'UNKNOWN') as UNKNOWN,
  count(*) as TOTAL
from status
group by check_date

Как мне структурировать аналогичный запрос, используя peewee ? Я знаю, что есть доступ к sql функциям через пространство имен peewee.fn, но я не уверен, возможно ли структурировать эти filter подзапросы с использованием этого синтаксиса.

Я решил это для теперь, начав с:

status_summary = (
    Status.select(Status.check_date,
                  Status.status,
                  peewee.fn.Count(Status.id).alias('count'))
    .group_by(Status.check_date, Status.status)
    .order_by(Status.check_date, Status.status)
)

, который получает меня:

+----------------------------+---------+-------+
| check_date                 | status  | count |
+----------------------------+---------+-------+
| 2020-02-04 17:52:28.716036 | UP      | 221   |
| 2020-02-04 17:52:28.716036 | REFUSED | 34    |
| 2020-02-04 17:52:28.716036 | TIMEOUT | 10    |
| 2020-02-04 17:52:28.716036 | UNKNOWN | 34    |
| 2020-02-04 21:21:48.319062 | UP      | 230   |
| 2020-02-04 21:21:48.319062 | REFUSED | 30    |
| 2020-02-04 21:21:48.319062 | TIMEOUT | 15    |
| 2020-02-04 21:21:48.319062 | UNKNOWN | 4     |
+----------------------------+---------+-------+

который я затем обрабатываю в Python, используя itertools.groupby:

status_summary = itertools.groupby(status_summary, lambda x: x.check_date)
status_summary = [
    {'date': date, 'summary': {x.status: x.count for x in results}}
    for date, results in status_summary
]

который достает меня:

[
  {
    "date": "2020-02-04 17:52:28.716036",
    "summary": {
      "OPEN": 538,
      "REFUSED": 13,
      "TIMEOUT": 41,
      "UNKNOWN": 4,
      "UNREACHABLE": 2
    }
  },
  {
    "date": "2020-02-04 17:55:22.655965",
    "summary": {
      "OPEN": 533,
      "REFUSED": 15,
      "TIMEOUT": 42,
      "UNKNOWN": 5,
      "UNREACHABLE": 3
    }
  },
  {
    "date": "2020-02-04 18:51:31.937254",
    "summary": {
      "OPEN": 541,
      "REFUSED": 11,
      "TIMEOUT": 41,
      "UNKNOWN": 4,
      "UNREACHABLE": 1
    }
  },
  {
    "date": "2020-02-04 21:21:48.319062",
    "summary": {
      "OPEN": 544,
      "REFUSED": 9,
      "TIMEOUT": 39,
      "UNKNOWN": 4,
      "UNREACHABLE": 2
    }
  },
  {
    "date": "2020-02-05 00:11:23.377746",
    "summary": {
      "OPEN": 547,
      "REFUSED": 8,
      "TIMEOUT": 37,
      "UNKNOWN": 5,
      "UNREACHABLE": 1
    }
  }
]

Это фактически то, чего я хочу, но я чувствую, что процесс попадания сюда был излишне сложным.

1 Ответ

2 голосов
/ 05 февраля 2020

Я бы использовал CASE, но COUNT / FILTER должен работать на Postgres.

select
  check_date, 
  count(*) filter (where status = 'UP') as UP,
  count(*) filter (where status = 'REFUSED') as REFUSED,
  count(*) filter (where status = 'TIMEOUT') as TIMEOUT,
  count(*) filter (where status = 'UNKNOWN') as UNKNOWN,
  count(*) as TOTAL
from status
group by check_date

Peewee должен поддерживать что-то вроде этого:

(Status
 .select(
     fn.date_trunc('day', Status.check_date).alias('date'),
     fn.COUNT(Status.id).filter(Status.status == 'UP').alias('up'),
     fn.COUNT(Status.id).filter(Status.status == 'REFUSED').alias('refused'))
 .group_by(fn.date_trunc('day', Status.check_date)))

Но на самом деле вы можете просто опираться на GROUP BY, чтобы дать вам что-то намного проще:

(Status
 .select(
     fn.date_trunc('day', Status.check_date).alias('date'),
     Status.status,
     fn.COUNT(Status.id).alias('count'))
 .group_by(fn.date_trunc('day', Status.check_date), Status.status))

Это дает вам строку для каждой даты + для каждого статуса, но немного более гибко (так как вы не жестко кодируете все свои статусы).

...