Как мне написать функцию в plpg sql, которая сравнивает дату с меткой времени без часового пояса? - PullRequest
1 голос
/ 25 мая 2020

Я хочу написать функцию, которая возвращает таблицу со всеми строками между firstDate и lastDate. Строки имеют тип данных метка времени без часового пояса Они также должны иметь указанный c идентификатор узла.

Это моя функция:

CREATE OR REPLACE FUNCTION get_measurements_by_node_and_date(nodeID INTEGER, firstDate date, lastDate date) 
RETURNS TABLE (measurement_id INTEGER, node_id INTEGER, carbon_dioxide DOUBLE PRECISION, 
                    hydrocarbons DOUBLE PRECISION, temperature DOUBLE PRECISION, 
                    humidity DOUBLE PRECISION, 
                    air_pressure DOUBLE PRECISION, 
                    measurement_timestamp timestamp without time zone ) AS
$$
    DECLARE
       sql_to_execute TEXT;
    BEGIN
        SELECT 'SELECT measurements_lora.id, 
                       measurements_lora.node_id,
                       measurements_lora.carbon_dioxide,
                       measurements_lora.hydrocarbons,
                       measurements_lora.temperature,
                       measurements_lora.humidity,
                       measurements_lora.air_pressure,
                       measurements_lora.measurement_timestamp AS  measure
                  FROM public.measurements_lora 
                  WHERE  measurements_lora.measurement_timestamp <= '||lastDate||'
                  AND    measurements_lora.measurement_timestamp >= '||firstDate||'           
                  AND    measurements_lora.node_id = '||nodeID||' ' 
          INTO sql_to_execute;
        RETURN QUERY EXECUTE sql_to_execute;
    END
$$ LANGUAGE plpgsql; 

Столбец Measure_timestamp имеет тип timestamp без часового пояса и имеет формат yy-mm-dd hh-mm-ss

Когда я запускаю SELECT * FROM get_measurements_by_node_and_date(1, '2020-5-1', '2020-5-24')

, я получаю следующую ошибку:

ERROR:  operator does not exist: timestamp without time zone <= integer
LINE 10: ...   WHERE  measurements_lora.measurement_timestamp <= 2020-05...

Я не понимаю, почему там написано «целое число», потому что я четко определил firstDate и lastDate как тип date.

Ответы [ 2 ]

2 голосов
/ 26 мая 2020

Что сказал Павел.

Более того, ничто не указывает на необходимость PL / pg SQL для начала. Это может сделать простой (подготовленный) оператор SELECT. Или функцию SQL, если вы хотите сохранить ее в базе данных. См .:

И относительно:

со всеми строками между firstDate и lastDate

Определите включающую / исключительную верхнюю / нижнюю границу точно , чтобы избежать неожиданных результатов углового регистра. При сравнении столбца timestamp со столбцом date последний приводится к метке времени, обозначающей первый экземпляр дня: YYYY.MM.DD 00:00:00.

В вашем запросе говорится:

measurement_timestamp <= lastDate AND measurement_timestamp >= firstDate

... который будет включать все firstDate, но исключить все lastDate, за исключением первого (общего) экземпляра в 00: 00 . Обычно не то, что вам нужно. Учитывая вашу формулировку, я полагаю, это именно то, что вам действительно нужно:

CREATE OR REPLACE FUNCTION get_measurements_by_node_and_date(node_id integer
                                                           , firstDate date
                                                           , lastDate date) 
  RETURNS TABLE (measurement_id integer
               , node_id integer
               , carbon_dioxide float8
               , hydrocarbons float8
               , temperature float8
               , humidity float8
               , air_pressure float8
               , measurement_timestamp timestamp)
  LANGUAGE sql STABLE AS
$func$
   SELECT m.id
        , m.node_id
        , m.carbon_dioxide
        , m.hydrocarbons
        , m.temperature
        , m.humidity
        , m.air_pressure
        , m.measurement_timestamp -- AS measure  -- just documentation
   FROM   public.measurements_lora m
   WHERE  m.node_id = _node_id
   AND    m.measurement_timestamp >= firstDate::timestamp
   AND    m.measurement_timestamp < (lastDate + 1)::timestamp  -- ①!
$func$;

① Это включает в себя все lastDate, причем эффективно. Вы можете просто добавить / вычесть значение integer к / из date, чтобы добавить / вычесть дни . Явное приведение к ::timestamp необязательно, поскольку дата будет автоматически приведена в выражении. Но поскольку мы пытаемся устранить путаницу здесь ...

Связано:

Помимо 1:

Столбец measurement_timestamp имеет тип timestamp without time zone и имеет формат гг-мм-дд чч-мм- ss

Нет. timestamp значения в формате без , точка. Это просто значения временных меток (хранящиеся внутри как количество микросекунд с начала эпохи). Отображение полностью отделено от значения и может быть отрегулировано сотней и одним способом без изменения значения . Избавьтесь от этого заблуждения, чтобы лучше понять, что происходит. См .:

Помимо 2:

О коварной природе SQL BETWEEN:

В сторону 3:

Учитывайте допустимые строчные идентификаторы в Postgres. first_date вместо firstDate. См .:

Связанные:

2 голосов
/ 25 мая 2020

В этом случае лучше сначала написать выполненный запрос. Я пытаюсь сократить ваш пример:

CREATE OR REPLACE FUNCTION public.foo(d date)
 RETURNS TABLE(o integer)
 LANGUAGE plpgsql
AS $function$
declare q text;
begin
  q := 'select 1 from generate_series(1,100) where current_timestamp <= ' || d ;
  raise notice '%', q;
  return query execute q;
end;
$function$

postgres=# select * from foo('2020-05-25');
NOTICE:  00000: select 1 from generate_series(1,100) where current_timestamp <= 2020-05-25
LOCATION:  exec_stmt_raise, pl_exec.c:3826
ERROR:  42883: operator does not exist: timestamp with time zone <= integer
LINE 1: ...om generate_series(1,100) where current_timestamp <= 2020-05...
                                                             ^
HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
QUERY:  select 1 from generate_series(1,100) where current_timestamp <= 2020-05-25
CONTEXT:  PL/pgSQL function foo(date) line 6 at RETURN QUERY

У меня такое же сообщение об ошибке. Таким образом, существует более одной ошибки:

  1. Запрос Dynami c недействителен - предложение where выглядит как

    where current_timestamp <= 2020-05-25

    и, как видите, это недействительно - нет цитат. Вы можете исправить это, когда используете кавычки вручную (но это серьезная ошибка, и не делайте этого, или вы можете использовать функцию quote_literal как where current_timestamp <= ' || quote_literal(d).

    Теперь созданный запрос правильный:

    select 1 from generate_series(1,100) where current_timestamp <= '2020-05-25'
    
  2. Но в этом случае гораздо лучше использовать EXECUTE USING. Когда переменная используется как параметр запроса (а не как имя таблицы или столбца), вы можете использовать USING предложение. Тогда вам не нужно использовать цитирование:

    CREATE OR REPLACE FUNCTION public.foo(d date)
     RETURNS TABLE(o integer)
     LANGUAGE plpgsql
    AS $function$
    declare q text;
    begin
      q := 'select 1 from generate_series(1,100) where current_timestamp <= $1';
      return query execute q using d;
    end;
    $function$
    
  3. , но большая ошибка заключается в использовании динамического c SQL, когда это не нужно (например, ваш пример ). Нет никаких видимых причин, по которым вы используете оператор RETURN QUERY EXECUTE. Можно использовать только RETURN QUERY:

    CREATE OR REPLACE FUNCTION get_measurements_by_node_and_date(nodeID INTEGER, firstDate date, lastDate date) 
    RETURNS TABLE (measurement_id INTEGER, node_id INTEGER, carbon_dioxide DOUBLE PRECISION, 
                   hydrocarbons DOUBLE PRECISION, temperature DOUBLE PRECISION, 
                   humidity DOUBLE PRECISION, 
                   air_pressure DOUBLE PRECISION, 
                   measurement_timestamp timestamp without time zone ) AS
    $$
    BEGIN
       RETURN QUERY SELECT measurements_lora.id, 
                           measurements_lora.node_id,
                           measurements_lora.carbon_dioxide,
                           measurements_lora.hydrocarbons,
                           measurements_lora.temperature,
                           measurements_lora.humidity,
                           measurements_lora.air_pressure,
                           measurements_lora.measurement_timestamp AS  measure
                      FROM public.measurements_lora 
                      WHERE measurements_lora.measurement_timestamp <= lastDate 
                        AND measurements_lora.measurement_timestamp >= firstDate           
                        AND measurements_lora.node_id = nodeID;
    END
    $$ LANGUAGE plpgsql; 
    
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...