重写一个 pl/pgSQL 函数

Rewriting a pl/pgSQL function

我想简化一个函数,我必须计算两个事件之间的持续时间。我的数据库有三个 table 用于函数。

  1. EVENTS TABLE - table 重新组合所有事件。我们感兴趣的事件是 device_type 1(= 进入)和 device_type 2(= 退出)的事件

  2. DURATIONS TABLE - 这是函数运行时填充的 table。它会在eventstable中找到前两个具有相同card nr和device type 1和device type 2的事件,计算两个事件之间的持续时间并保存在duration table中。

  3. PROPERTIES - 此 table 用作参考,并将更新并用于将来的插入。

DDL 脚本

   -- events
        CREATE TABLE IF NOT EXISTS events (
        id bigint NOT NULL autoincrement start 1 increment 1 PRIMARY KEY,
        odb_created_at timestamp without time zone NOT NULL,
        event_time timestamp without time zone NOT NULL,
        device_type integer NOT NULL,
        event_type integer NOT NULL,
        ticket_type integer NOT NULL,
        card_nr character varying(100),
        count integer DEFAULT 1 NOT NULL,
        manufacturer character varying(200),
        carpark_id bigint
    ); 

     -- durations
    CREATE TABLE IF NOT EXISTS durations (
        id bigint NOT NULL autoincrement start 1 increment 1 PRIMARY KEY,
        odb_created_at timestamp without time zone NOT NULL,
        event_id_arrival bigint,
        event_id_departure bigint,
        event_time_arrival timestamp without time zone,
        event_time_departure timestamp without time zone,
        card_nr character varying(100),
        ticket_type integer,
        duration integer,
        manufacturer character varying(200),
        carpark_id bigint
    );

    --properties
    create or replace TABLE PROPERTIES (
        PROP_KEY VARCHAR(80) NOT NULL,
        PROP_VALUE VARCHAR(250),
        primary key (PROP_KEY)
    );


INSERT INTO public.events (id, odb_created_at, event_time, device_type, event_type, ticket_type, card_nr, count, manufacturer, carpark_id) VALUES(188160996, '2021-10-02 04:28:26.338', '2021-10-01 09:14:41.32', 1, 2, 11, '03998988030897300007782', 1, 'XX', 1852);
INSERT INTO public.events (id, odb_created_at, event_time, device_type, event_type, ticket_type, card_nr, count, manufacturer, carpark_id) VALUES(188160790, '2021-10-02 04:28:26.248', '2021-10-01 09:31:10.94', 2, 2, 11, '03998988030897300007782', 1, 'XX', 1852);
INSERT INTO public.events (id, odb_created_at, event_time, device_type, event_type, ticket_type, card_nr, count, manufacturer, carpark_id) VALUES(188146489, '2021-10-02 04:26:55.069', '2021-10-01 10:03:01.57', 1, 2, 500, '01479804030429500089598', 1, 'XX', 1563);
INSERT INTO public.events (id, odb_created_at, event_time, device_type, event_type, ticket_type, card_nr, count, manufacturer, carpark_id) VALUES(188146069, '2021-10-02 04:26:54.852', '2021-10-01 11:49:58.45', 2, 2, 500, '01479804030429500089598', 1, 'XX', 1563);
INSERT INTO public.events (id, odb_created_at, event_time, device_type, event_type, ticket_type, card_nr, count, manufacturer, carpark_id) VALUES(188161161, '2021-10-02 04:28:26.372', '2021-10-01 18:44:33.62', 1, 2, 11, '03998988030897300007782', 1, 'XX', 1852);
INSERT INTO public.events (id, odb_created_at, event_time, device_type, event_type, ticket_type, card_nr, count, manufacturer, carpark_id) VALUES(188160950, '2021-10-02 04:28:26.329', '2021-10-01 18:45:51.903', 2, 2, 11, '03998988030897300007782', 1, 'XX', 1852);
INSERT INTO public.events (id, odb_created_at, event_time, device_type, event_type, ticket_type, card_nr, count, manufacturer, carpark_id) VALUES(188161227, '2021-10-02 04:28:26.374', '2021-10-01 23:21:18.58', 1, 2, 11, '04139733030897300003136', 1, 'XX', 1852);
INSERT INTO public.events (id, odb_created_at, event_time, device_type, event_type, ticket_type, card_nr, count, manufacturer, carpark_id) VALUES(188160974, '2021-10-02 04:28:26.334', '2021-10-01 23:24:03.29', 2, 2, 11, '04139733030897300003136', 1, 'XX', 1852);
INSERT INTO public.events (id, odb_created_at, event_time, device_type, event_type, ticket_type, card_nr, count, manufacturer, carpark_id) VALUES(188239864, '2021-10-03 04:24:43.345', '2021-10-02 06:49:55.97', 1, 2, 11, '01719400030897300061410', 1, 'XX', 1852);
INSERT INTO public.events (id, odb_created_at, event_time, device_type, event_type, ticket_type, card_nr, count, manufacturer, carpark_id) VALUES(188239649, '2021-10-03 04:24:43.308', '2021-10-02 07:02:08.72', 2, 2, 11, '01719400030897300061410', 1, 'XX', 1852);

举个例子:

  1. 活动
  2. 函数运行,插入持续时间
  3. 属性已更新

这是我的函数

    CREATE OR REPLACE FUNCTION public.calculateduration()
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE

     arrived_entry RECORD;
     departed_entry RECORD;  

     durationLimitDays INTEGER := -1;
     durationLimitDate TIMESTAMP := '1970-01-01 00:00:00';
     
     cursorQuery text;
     cursorEvent refcursor;
     
     maxEventTime TIMESTAMP;
     
BEGIN

    -- start date   = DURATION.LIMIT.DATE
    -- end date     = now - DURATION.LIMIT.DAYS
    SELECT PROP_VALUE INTO durationLimitDays FROM properties WHERE prop_key = 'DURATION.LIMIT.DAYS';
    SELECT PROP_VALUE INTO durationLimitDate FROM properties WHERE prop_key = 'DURATION.LIMIT.DATE';
    RAISE NOTICE 'Parameter duration limit days ''%'' , duration limit date ''%''', durationLimitDays, durationLimitDate;
    
    --  AND e.event_time < to_char(now(), ''YYYY-MM-DD'')::date - (''42 month''::interval)
    cursorQuery:='SELECT e.id, e.card_nr, e.event_time, e.ticket_type, e.device_type, e.manufacturer, e.carpark_id
                  FROM events e 
                  WHERE e.event_time >= ''' || durationLimitDate || '''
                  AND e.device_type IN (1,2) AND event_type=2
                  AND e.manufacturer like ''DESIGNA_ABACUS%''
                  AND NOT EXISTS (SELECT d.event_id_arrival FROM durations d WHERE d.event_id_arrival = e.id)
                  AND NOT EXISTS (SELECT d.event_id_departure FROM durations d WHERE d.event_id_departure = e.id)
                  ORDER BY e.card_nr, e.event_time, e.carpark_id';
                  -- AND e.event_time < ''2016-01-05 00:00:00'' 
    OPEN cursorEvent SCROLL FOR EXECUTE cursorQuery;

    LOOP
        FETCH cursorEvent INTO arrived_entry;
        EXIT WHEN NOT FOUND;
        IF arrived_entry.device_type=1 THEN
            FETCH cursorEvent INTO departed_entry;
            EXIT WHEN NOT FOUND;
            
            -- same card number and same car park
            IF arrived_entry.card_nr=departed_entry.card_nr AND arrived_entry.carpark_id=departed_entry.carpark_id THEN
                IF departed_entry.device_type=2 THEN
                    EXECUTE 'INSERT INTO durations VALUES (nextval(''durations_id_seq''), ''' || current_timestamp || ''',' || arrived_entry.id || ',' || departed_entry.id || ',''' || arrived_entry.event_time || ''',''' || departed_entry.event_time 
                                                              || ''',''' || arrived_entry.card_nr || ''',' || arrived_entry.ticket_type || ',' 
                                                              || date_part('epoch', departed_entry.event_time::timestamp - arrived_entry.event_time::timestamp) || ', ''' || arrived_entry.manufacturer || ''',' 
                                                              || arrived_entry.carpark_id || ')';
                ELSE 
                    -- repeated entry found - refresh entry with the repeated one 
                    -- RAISE NOTICE 'Unexpected entry after entry found at event id ''%'' and card number''%''', arrived_entry.id, arrived_entry.card_nr;
                    FETCH PRIOR FROM cursorEvent INTO arrived_entry;
                END IF;
            ELSE
                -- card number or car park changed - refresh to changed card number / car park
                -- RAISE NOTICE 'Unexpected card number or car park change found at event id ''%'' and card number''%''', arrived_entry.id, arrived_entry.card_nr;
                FETCH PRIOR FROM cursorEvent INTO arrived_entry;
            END IF;
        END IF;
    END LOOP;
    CLOSE cursorEvent;
    
    -- update duration limit date ( = MAX(event_time) - durationLimitDays)
    EXECUTE 'SELECT MAX(event_time) FROM events WHERE event_time >= ''' || durationLimitDate || '''' INTO maxEventTime;
    SELECT (maxEventTime - (durationLimitDays ||' day')::interval) INTO durationLimitDate;
    EXECUTE 'UPDATE properties SET PROP_VALUE=''' || durationLimitDate || ''' WHERE prop_key =''DURATION.LIMIT.DATE''';
    RAISE NOTICE 'Add new DURATION.LIMIT.DATE to ''%''', durationLimitDate;
        
END;
$function$
;

我怎样才能以更简单的方式重写它:

  1. 在我们的 select 中,我们应该只计算 events.event_time >) durationLimitDate 来自 properties
  2. 的持续时间
  3. 我需要确保 event_id_arrival 和 event_id_departure 不在目标 table 中以避免重复
  4. 将计算插入持续时间 table 后,我必须更新属性中的 durationLimitDate。知道 durationLimitDate = (Max(event_time) - durationLimitDays))

先试试

INSERT INTO durations (id, odb_created_at, event_id_arrival, event_id_departure, event_time_arrival, event_time_departure, card_nr, ticket_type, duration, manufacturer, carpark_id)

SELECT nextval('durations_id_seq'), 
       current_timestamp, 
       arrived_entry.id, 
       departed_entry.id, 
       arrived_entry.event_time,
       departed_entry.event_time,
       arrived_entry.card_nr, 
       arrived_entry.ticket_type, 
       date_part('epoch', departed_entry.event_time::timestamp - arrived_entry.event_time::timestamp), 
       arrived_entry.manufacturer, 
       arrived_entry.carpark_id

FROM (SELECT e.id, e.card_nr, e.event_time, e.ticket_type, e.manufacturer, e.carpark_id
      FROM events e 
      LEFT JOIN durations d ON d.event_id_arrival = e.id
      WHERE e.event_time >= TO_TIMESTAMP((SELECT PROP_VALUE FROM properties WHERE prop_key = 'DURATION.LIMIT.DATE'),'YYYY-MM-DD HH24:MI:SS.MS')
            AND e.device_type = 1
            AND event_type = 2
            AND e.manufacturer LIKE 'DESIGNA_ABACUS%'
            AND d.id IS NULL) AS arrived_entry
            
INNER JOIN (SELECT e.id, e.card_nr, e.carpark_id, e.event_time
            FROM events e 
            LEFT JOIN durations d ON d.event_id_departure = e.id
            WHERE e.event_time >= TO_TIMESTAMP((SELECT PROP_VALUE FROM properties WHERE prop_key = 'DURATION.LIMIT.DATE'),'YYYY-MM-DD HH24:MI:SS.MS')
                  AND e.device_type = 2 
                  AND event_type = 2
                  AND e.manufacturer LIKE 'DESIGNA_ABACUS%'
                  AND d.id IS NULL) AS departed_entry ON arrived_entry.card_nr = departed_entry.card_nr AND arrived_entry.carpark_id = departed_entry.carpark_id; 

问题:

  1. event_id_arrival > 许多重复项,最多 600 个。我需要确保 event_id_arrival 和 event_id_departure 不在目标 table 中以避免重复
  2. 将计算插入持续时间 table 后,我必须更新属性中的 durationLimitDate。知道 durationLimitDate = (Max(event_time) - durationLimitDays))

您可以编写如下查询来将行插入 durations:

,而不是遍历记录

检查查询,因为我无法尝试,因为您将表结构共享为图像。

WITH cte AS (SELECT e.id, e.card_nr, e.event_time, e.ticket_type, e.manufacturer, e.carpark_id, e.device_type,
                    ROW_NUMBER() OVER (ORDER BY e.card_nr, e.carpark_id, e.event_time, e.device_type) AS rn
             FROM events e 
             LEFT JOIN durations d ON d.event_id_arrival = e.id OR d.event_id_departure = e.id
             WHERE e.event_time >= (SELECT PROP_VALUE::timestamp FROM properties WHERE prop_key = 'DURATION.LIMIT.DATE')
                   AND e.device_type IN (1, 2)
                   AND event_type = 2
                   AND e.manufacturer LIKE 'DESIGNA_ABACUS%'
                   AND d.id IS NULL
     ),
     
     arrived_entry AS (SELECT *
                       FROM cte
                       WHERE device_type = 1
     ),
     
     departed_entry AS (SELECT *
                       FROM cte
                       WHERE device_type = 2
     )
     
INSERT INTO durations (id, odb_created_at, event_id_arrival, event_id_departure, 
                       event_time_arrival, event_time_departure,
                       card_nr, ticket_type, duration, manufacturer, carpark_id)       
     
SELECT nextval('durations_id_seq'), 
       current_timestamp, 
       arrived_entry.id, 
       departed_entry.id, 
       arrived_entry.event_time, 
       departed_entry.event_time,
       arrived_entry.card_nr, 
       arrived_entry.ticket_type, 
       date_part('epoch', departed_entry.event_time::timestamp - arrived_entry.event_time::timestamp), 
       arrived_entry.manufacturer, 
       arrived_entry.carpark_id
FROM arrived_entry
INNER JOIN departed_entry ON arrived_entry.card_nr = departed_entry.card_nr 
                             AND arrived_entry.carpark_id = departed_entry.carpark_id
                             AND arrived_entry.rn + 1 = departed_entry.rn;

您可以使用 durationLimitDate 变量的值替换此查询 (SELECT PROP_VALUE FROM properties WHERE prop_key = 'DURATION.LIMIT.DATE')

Pl/PgSQL 函数计算时间():

CREATE OR REPLACE FUNCTION calculateduration() RETURNS void AS $function$

DECLARE

     durationLimitDays INTEGER := -1;
     durationLimitDate TIMESTAMP := '1970-01-01 00:00:00';
     
BEGIN

    -- start date   = DURATION.LIMIT.DATE
    -- end date     = now - DURATION.LIMIT.DAYS
    SELECT PROP_VALUE INTO durationLimitDays FROM properties WHERE prop_key = 'DURATION.LIMIT.DAYS';
    SELECT PROP_VALUE::timestamp INTO durationLimitDate FROM properties WHERE prop_key = 'DURATION.LIMIT.DATE';
    RAISE NOTICE 'Parameter duration limit days ''%'' , duration limit date ''%''', durationLimitDays, durationLimitDate;
    
    WITH cte AS (SELECT e.id, e.card_nr, e.event_time, e.ticket_type, e.manufacturer, e.carpark_id, e.device_type,
                    ROW_NUMBER() OVER (ORDER BY e.card_nr, e.carpark_id, e.event_time, e.device_type) AS rn
             FROM events e 
             LEFT JOIN durations d ON d.event_id_arrival = e.id OR d.event_id_departure = e.id
             WHERE e.event_time >= durationLimitDate
                   AND e.device_type IN (1, 2)
                   AND event_type = 2
                   AND e.manufacturer LIKE 'DESIGNA_ABACUS%'
                   AND d.id IS NULL)
         
    INSERT INTO durations (id, odb_created_at, event_id_arrival, event_id_departure, 
                       event_time_arrival, event_time_departure,
                       card_nr, ticket_type, duration, manufacturer, carpark_id)       
         
    SELECT nextval('durations_id_seq'), 
           current_timestamp, 
           arrived_entry.id, 
           departed_entry.id, 
           arrived_entry.event_time, 
           departed_entry.event_time,
           arrived_entry.card_nr, 
           arrived_entry.ticket_type, 
           date_part('epoch', departed_entry.event_time::timestamp - arrived_entry.event_time::timestamp), 
           arrived_entry.manufacturer, 
           arrived_entry.carpark_id
    FROM (SELECT * FROM cte WHERE cte.device_type = 1) AS arrived_entry
    INNER JOIN (SELECT * FROM cte WHERE cte.device_type = 2) AS departed_entry ON arrived_entry.card_nr = departed_entry.card_nr 
                                                                              AND arrived_entry.carpark_id = departed_entry.carpark_id
                                                                              AND arrived_entry.rn + 1 = departed_entry.rn;
    
    -- update duration limit date ( = MAX(event_time) - durationLimitDays)
    SELECT (MAX(event_time) - (durationLimitDays ||' day')::interval) INTO durationLimitDate FROM events WHERE event_time >= durationLimitDate;
    UPDATE properties SET PROP_VALUE = durationLimitDate WHERE PROP_KEY ='DURATION.LIMIT.DATE';
    RAISE NOTICE 'Add new DURATION.LIMIT.DATE to ''%''', durationLimitDate;
        
END;
$function$

LANGUAGE plpgsql;

SQL 函数计算时间():

CREATE OR REPLACE FUNCTION calculateduration() RETURNS void AS $function$

    WITH cte AS (SELECT e.id, e.card_nr, e.event_time, e.ticket_type, e.manufacturer, e.carpark_id, e.device_type,
                    ROW_NUMBER() OVER (ORDER BY e.card_nr, e.carpark_id, e.event_time, e.device_type) AS rn
             FROM events e 
             LEFT JOIN durations d ON d.event_id_arrival = e.id OR d.event_id_departure = e.id
             WHERE e.event_time >= (SELECT PROP_VALUE::timestamp FROM properties WHERE prop_key = 'DURATION.LIMIT.DATE')
                   AND e.device_type IN (1, 2)
                   AND event_type = 2
                   AND e.manufacturer LIKE 'DESIGNA_ABACUS%'
                   AND d.id IS NULL)
         
    INSERT INTO durations (id, odb_created_at, event_id_arrival, event_id_departure, 
                       event_time_arrival, event_time_departure,
                       card_nr, ticket_type, duration, manufacturer, carpark_id)       
         
    SELECT nextval('durations_id_seq'), 
           current_timestamp, 
           arrived_entry.id, 
           departed_entry.id, 
           arrived_entry.event_time, 
           departed_entry.event_time,
           arrived_entry.card_nr, 
           arrived_entry.ticket_type, 
           date_part('epoch', departed_entry.event_time::timestamp - arrived_entry.event_time::timestamp), 
           arrived_entry.manufacturer, 
           arrived_entry.carpark_id
    FROM (SELECT * FROM cte WHERE cte.device_type = 1) AS arrived_entry
    INNER JOIN (SELECT * FROM cte WHERE cte.device_type = 2) AS departed_entry ON arrived_entry.card_nr = departed_entry.card_nr 
                                                                              AND arrived_entry.carpark_id = departed_entry.carpark_id
                                                                              AND arrived_entry.rn + 1 = departed_entry.rn;
    
    UPDATE properties 
    SET PROP_VALUE = (SELECT (MAX(event_time) - ((SELECT PROP_VALUE FROM properties WHERE prop_key = 'DURATION.LIMIT.DAYS') ||' day')::interval) FROM events WHERE event_time >= (SELECT PROP_VALUE::timestamp FROM properties WHERE prop_key = 'DURATION.LIMIT.DATE')) 
    WHERE PROP_KEY ='DURATION.LIMIT.DATE';

$function$

LANGUAGE sql;