POSTGRES - оценить ошибку (под) выражения как NULL - PullRequest
0 голосов
/ 08 мая 2018

У меня есть данные датчика в таблице Postgres measurements со столбцами id, timestamp, s0, s1, s2, ...

Кроме того, есть индекс по столбцам (id, timestamp). Я хочу разрешить динамические математические выражения (в приведенном ниже примере: sin(s3)*0.1000/s5) для вычисления производных значений.

SELECT
  timestamp,
  trunc((sin(s3) * 0.1000/s5)::numeric, 3) AS "calculated"
FROM measurements
WHERE id = 42
ORDER BY timestamp DESC
LIMIT 10000;

Очевидно, что это может привести к ошибке «деление на ноль», которая приведет к сбою запроса. Есть ли способ отловить эту ошибку и вернуть, например, NULL для расчетного значения, в котором возникнет ошибка?

Вдохновлен

Я уже пытался определить postgres функцию eval_numeric(sensors int[], formula text), которая анализирует формулу и возвращает NULL в случае исключения. Третья строка приведенного выше оператора SQL теперь выглядит как

trunc(eval_numeric(ARRAY[s3,s5],'sin(var1)*0.1/var2'), 3) AS "calculated"

Это дает желаемое поведение, но время выполнения, сообщаемое EXPLAIN ANALYZE, увеличивается в 20 раз (~ 20 мс -> ~ 400 мс). Есть еще идеи?


UPDATE

Динамическое выражение для оценки происходит от пользователя веб-приложения. Таким образом, приведенная выше формула является лишь примером (может потребоваться проверка отрицательного аргумента для квадратного корня). Я предпочел бы иметь общую возможность проверки ошибок и предпочел бы, чтобы в математическом выражении не было логики. Это было бы проще для конечного пользователя, и я мог бы проверить допустимую математику, например. с математическим парсером, тем самым предотвращая внедрение SQL.

Ответы [ 2 ]

0 голосов
/ 08 мая 2018

Вы также можете использовать выражение CASE:

SELECT
    timestamp,
    CASE WHEN s5 != 0
         THEN trunc((sin(s3) * 0.1000/s5)::numeric, 3)
         ELSE NULL AS "calculated",
FROM measurements
WHERE id = 42
ORDER BY timestamp DESC
LIMIT 10000;

Это имеет потенциальную выгоду, что вы можете заменить значение на что угодно, включая NULL.

Другой вариант, если вам не нужны строки, которые могли бы вызвать деление на ноль, это просто добавить проверку на s5 в предложение WHERE и отфильтровать эти строки до того, как произойдет деление.

0 голосов
/ 08 мая 2018

Можете ли вы изменить выражение на это?

SELECT timestamp,
       trunc((sin(s3) * 0.1000/nullif(s5, 0))::numeric, 3) AS "calculated",
FROM measurements
WHERE id = 42
ORDER BY timestamp DESC
LIMIT 10000;

Это самый простой способ выполнить то, что вы хотите.

...