Найти все строки между наборами в PostgreSQL - PullRequest
1 голос
/ 10 октября 2019

У меня есть таблица с именем tc_fuel, которая получает все данные о топливе от транспортных средств GPS, и я хочу получить последнее "Использование бака" для расчета MPG для всего бака, но показания, когда бак заполнен(100) несколько раз повторяются после 2 или 3 или более строк, поэтому у меня остаются 2 значения по 100 рядом друг с другом, я хочу иметь возможность получить последний начальный и конечный идентификаторы «заполнения».

Что у меня есть:

SELECT
    "tc_fuel".deviceid,
    "tc_fuel"."id",
    "tc_fuel".fuel,
    "tc_fuel"."fuelUsed",
    "tc_fuel"."fuelUsed"
FROM "tc_fuel"
WHERE fuel=100
    AND deviceid=19
ORDER BY ID
DESC LIMIT 2

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

Например, этот автомобиль заправился на полный бак, а затем опустился до 6% бака и заполнил полный бак, я хочу иметь возможностьзахватить все данные последнего танка.

id    | deviceId  | fuel
------+-----------+-------
1     | 19        | 100  <-- This should be starting point 
2     | 19        | 97  
3     | 19        | 100  
4     | 19        | 96
5     | 19        | 94
6     | 19        | .... (keeps dropping)
7     | 19        | 33
8     | 19        | 31
9     | 19        | 30
10    | 19        | ....
11    | 19        | 6
12    | 19        | 5
13    | 19        | 6    <-- This should be end point (will flag this id as processed)
14    | 19        | 100  <-- Starts all over again in next iteration of the php script
15    | 19        | 99
16    | 19        | 98
17    | 19        | 100
18    | 19        | 99
19    | 19        | 97
20    | 19        | 96
21    | 19        | ....

Ответы [ 2 ]

1 голос
/ 10 октября 2019
SELECT min(id) AS first_id,
       max(id) AS last_id,
       deviceid
FROM (SELECT id, deviceid, fuel,
             count(*) FILTER (WHERE refilled)
                OVER (PARTITION BY deviceid ORDER BY id DESC) AS filling
      FROM (SELECT id, deviceid, fuel,
                   fuel < lead(luel, 1, 0)
                             OVER (PARTITION BY deviceid ORDER BY id) AS before_fill
            FROM tc_fuel
           ) AS refill
     ) AS fills
WHERE filling = 1
GROUP BY deviceid;

Сначала я отмечаю запись перед заполнением резервуара.

Затем я помечаю наполнения, считая следующие отметки: 0 будет текущим наполнением, 1 - предыдущим наполнением и так далее.

Наконец, я получаю первый и последний id последнего заполнения перед текущим.

Бросьте WHERE deviceid = 19 во внутренний запрос, чтобы получить данные только для одного устройства.

1 голос
/ 10 октября 2019

Определение «заправка» немного расплывчато. Я предполагаю, что это заполнение, когда стоимость топлива увеличивается более чем на 50 . Замените на номер по вашему выбору. И похоже, что новый танк должен начинаться с fuel = 100 (хотя это странное условие). Я добавил это в качестве комментария - раскомментируйте для активации:

SELECT *
FROM  (
   SELECT *, count(*) FILTER (WHERE fillup) OVER (PARTITION BY device_id ORDER BY id) AS tank
   FROM  (
      SELECT *
           , fuel - lag(fuel, 1, 0) OVER (PARTITION BY device_id ORDER BY id) > 50
             -- AND fuel = 100  -- additional condition(s)?
             AS fillup
      FROM   tbl
      ) sub1
   ) sub2
WHERE  device_id = 19
AND    tank = 1;

db <> fiddle здесь

В подзапросе sub1, вычислите разницу между предыдущим вводом топлива на устройство и текущим - используя оконную функцию lag(). Примечательно, что я использую вариант с 3 параметрами, предоставляя 0 по умолчанию для пропущенных строк, чтобы покрыть первую строку на раздел. Увеличение более чем на 50 указывает на новое заполнение.

В подзапросе sub2 подсчитайте количество заполнений с течением времени с помощью другой оконной функции, назначая таким образом номер «резервуара» каждомуrow.

Во внешнем SELECT выберите свое устройство и номер «заправки» бака. Вуаля.

Если вы переместите условие WHERE device_id = 19 в самый внутренний запрос, вы можете отбросить предложения PARTITION. Быстрее, менее универсально.

Об условии FILTER:

Получить только последний резервуар для данного устройства

Согласно вашему комментарию, определенному как "последний раз, когда резервуар был заполнен от 20 или нижедо 100 ".

Я предполагаю, что более поздние моменты времени соответствуют более высоким значениям id. (Имейте в виду, что при параллельной загрузке записи могут возникнуть сложности в угловых случаях.)

Самый простой способ: просто изменить порядок и отсчитать снизу:

SELECT *
FROM  (
   SELECT *, count(*) FILTER (WHERE fillup) OVER (ORDER BY id DESC) AS tank
  FROM  (
      SELECT *, lag(fuel, 1, 0) OVER (ORDER BY id DESC) = 100
                AND fuel <= 20 AS fillup
      FROM   tbl
      WHERE  device_id = 19
      ) sub1
   ) sub2
WHERE  tank = 0
-- ORDER  BY id  -- optional to get result in ascending order

db <> fiddle здесь

Для этого, вероятно, быстрее проходить по рядам процедурно, так как для этого требуется только один проход, и он может остановиться сразу послепервый танк найден.
Поддержите его индексом tbl(device_id, id DESC).

Пример кода:

...