使用plpgsql存储过程返回一个表

时间:2017-01-25 19:55:48

标签: postgresql stored-procedures plpgsql identifier mapserver

我正在创建一个存储过程来复制在mapserver上的mapfile中找到的查询的输出。

查询:

select g.gid, g.geom, g.basin, a.\"DATE\", a.\"VALUE\" 
from sarffg_basins_00_regional_3sec as g 
   join \"%prod_table%\" as a on g.basin = a.\"BASIN\" 
where a.\"DATE\" = '%prod_date%'

由于SO帖子长度限制,我无法完整地发布输出,但重要的是这个查询给了我带有列名的输出。调整语法以匹配在sql shell中运行的传统sql:

select g.gid, 
       g.geom, 
       g.basin, 
       a."DATE", 
       a."VALUE" from sarffg_basins_00_regional_3sec 
       as g 
       join "FFGS_PROCESSED_PRODUCT_ASM_SACSMA_UPPER_BASIN_TIMESERIES" 
       as a on g.basin = a."BASIN" where a."DATE" = '2017-01-12 18:00:00+00';

给出了列

gid        |   geom      |     basin      |          DATE          | VALUE

我的存储过程具有以下返回标头:

RETURNS table (
    gid integer,
    geom geometry(MultiPolygon,4326),
    basin double precision,
    date timestamptz,
    value double precision
    )

返回声明:

    RETURN QUERY
    EXECUTE 'SELECT g.'||quote_ident('gid')||',
                    g.'||quote_ident('geom')||',
                    g.'||quote_ident('basin')||',
                    a.'||quote_ident('DATE')||',
                    a.'||quote_ident('VALUE')||'
                    FROM sarffg_basins_00_regional_3sec AS g JOIN '||quote_ident(prod_table)||' AS a
                    ON g.'||quote_ident('basin')||' = a.'||quote_ident('BASIN')||'
                    WHERE a.'||quote_ident('DATE')||' = '''||adj_timestamp||'''';

这给了我相同的输出,但没有列名。我认为这是我的mapfile错误日志中出现此错误的原因:

Query error. Error executing query: ERROR:  column "VALUE" does not exist
LINE 1: select "VALUE",encode(ST_AsBinary(ST_Force2D("geom"),'NDR'),...

我知道prod_table中存在“VALUE”列,特别是考虑到在psql shell中调用此函数时可以正常工作。

如何使输出具有列标题?

编辑:地图文件上的更多上下文。

mapfile中的整个查询如下:

geom from (select g.gid, g.geom, g.basin, a.\"DATE\", a.\"VALUE\" from sarffg_basins_00_regional_3sec as g join \"%prod_table%\" as a on g.basin = a.\"BASIN\" where a.\"DATE\" = '%prod_date%') as subquery using unique gid using srid=4326

我基本上需要使用存储过程复制此功能,无论是完全还是仅仅子查询。我可以在这里找到各种尝试:Syntax error at or near "USING"。似乎我最接近试图只复制子查询 - 即

geom from (select * from ingest_ffgs_prod_composite_csv('%prod_table%', 1484438400)) as subquery using unique gid using srid=4326
编辑2:既然我已经花了更多时间考虑它,我不确定当查询只从我的存储过程返回的子查询中选择“geom”时,为什么“VALUE”将是缺少的列。也许这不是导致错误的原因。

编辑3:根据要求,这是整个功能。

CREATE OR REPLACE FUNCTION ingest_ffgs_prod_composite_csv(prod_table text, epoch_seconds bigint, original boolean DEFAULT false, roll_back boolean DEFAULT true)
RETURNS table (
    gid integer,
    geom geometry(MultiPolygon,4326),
    basin double precision,
    date timestamptz,
    VALUE double precision
    )
AS $$
DECLARE
    c01n text := 'BASIN';           c01t text := 'double precision';
    c02n text := 'MAP01';           c02t text := 'double precision';
    c03n text := 'MAP03';           c03t text := 'double precision';
    c04n text := 'MAP06';           c04t text := 'double precision';
    c05n text := 'MAP24';           c05t text := 'double precision';
    c06n text := 'GMAP06';          c06t text := 'double precision';
    c07n text := 'GMAP24';          c07t text := 'double precision';
    c08n text := 'ASMU06';          c08t text := 'double precision';
    c09n text := 'ASML06';          c09t text := 'double precision';
    c10n text := 'ASMT06';          c10t text := 'double precision';
    c11n text := 'FFG01';           c11t text := 'double precision';
    c12n text := 'FFG03';           c12t text := 'double precision';
    c13n text := 'FFG06';           c13t text := 'double precision';
    c14n text := 'PREVFFG01';       c14t text := 'double precision';
    c15n text := 'PREVFFG03';       c15t text := 'double precision';
    c16n text := 'PREVFFG06';       c16t text := 'double precision';
    c17n text := 'FMAP01';          c17t text := 'double precision';
    c18n text := 'FMAP03';          c18t text := 'double precision';
    c19n text := 'FMAP06';          c19t text := 'double precision';
    c20n text := 'IFFT01';          c20t text := 'double precision';
    c21n text := 'IFFT03';          c21t text := 'double precision';
    c22n text := 'IFFT06';          c22t text := 'double precision';
    c23n text := 'PFFT01';          c23t text := 'double precision';
    c24n text := 'PFFT03';          c24t text := 'double precision';
    c25n text := 'PFFT06';          c25t text := 'double precision';
    c26n text := 'FFFT01';          c26t text := 'double precision';
    c27n text := 'FFFT03';          c27t text := 'double precision';
    c28n text := 'FFFT06';          c28t text := 'double precision';
    c29n text := 'PET06';           c29t text := 'double precision';
    tablename_csv text := 'temp_table_csv';
    tablename_ts  text := 'temp_table_ts';
    tablename_ret text := 'temp_table_ret';
    full_timestamp timestamptz;
    adj_timestamp_text text;
    adj_timestamp timestamptz;
    year  double precision;
    month double precision;
    day   double precision;
    hour  double precision;
    year_text  text;
    month_text text;
    day_text   text;
    hour_text  text;
    csv_base_path text;
    csv_filename  text;
    csv_file_full_path text;
    ret_rec record;
    product text;
    interval text;
    curr_basin int;
    curr_val double precision;
    rec record;
    existing_table boolean;
    existing_data  boolean := false;
    ret_geom geometry(MultiPolygon,4326);
BEGIN
    -- parse prod_table name to determine which product and time interval we need to build a timeseries for
    -------------------------------------------------------------------------------------------------------
    product  := split_part(prod_table, '_', 4);
    interval := rtrim(split_part(prod_table, '_', 6), 'HR');
    IF interval = 'UPPE' THEN -- asm interval reads as 'UPPE'. Default to 6 hours
            interval := '06';
    END IF;

    raise notice 'product: %', product;
    raise notice 'interval: %', interval;

    -- convert epoch seconds to timestamp for parsing
    -------------------------------------------------
    full_timestamp := to_timestamp(epoch_seconds);
    year  := date_part('year',  full_timestamp);
    month := date_part('month', full_timestamp);
    day   := date_part('day',   full_timestamp);

    IF roll_back THEN
            hour  := date_part('hour', full_timestamp) - (date_part('hour', full_timestamp)::integer % interval::int);
    ELSE
            hour := date_part('hour',   full_timestamp);
    END IF;

    year_text  := year;
    month_text := month;
    day_text   := day;
    hour_text  := hour;

    IF month < 10 THEN
            month_text := '0' || month;
    END IF;
    IF day < 10 THEN
            day_text := '0' || day;
    END IF;
    IF hour < 10 THEN
            hour_text := '0' || hour;
    END IF;

    adj_timestamp_text := year_text || '-' || month_text || '-' || day_text || ' ' || hour_text || ':00:00+00';
    adj_timestamp := adj_timestamp_text::timestamptz;

    raise notice 'year: %', year_text;
    raise notice 'month: %', month_text;
    raise notice 'day: %', day_text;
    raise notice 'hour: %', hour_text;

    -- check if table with the desired data already exists within the database
    --------------------------------------------------------------------------
    EXECUTE format('SELECT EXISTS (
                            SELECT 1
                            FROM pg_tables
                            WHERE schemaname = ''public''
                            AND   tablename  = '''||prod_table||''')') INTO existing_table;

    IF existing_table THEN
            EXECUTE format('SELECT EXISTS (
                                    SELECT 1
                                    FROM '||quote_ident(prod_table)||'
                                    WHERE '||quote_ident('DATE')||' = '''||adj_timestamp||''')') INTO existing_data;
    END IF;

    raise notice 'existing_table: %', existing_table;
    raise notice 'existing_data: %', existing_data;





    IF NOT existing_data THEN -- need to manually create tables from csv file

            -- construct full path to csv file
            ----------------------------------
            csv_base_path := '/SARFFG/EXP/DATA/EXPORTS/REGIONAL/'||year_text||'/'||month_text||'/'||day_text||'/COMPOSITE_CSV';

            IF original THEN
                    csv_filename  := year_text||month_text||day_text||'-'||hour_text||'00_ffgs_prod_composite_table_01hr_regional_original.csv';
            ELSE
                    csv_filename  := year_text||month_text||day_text||'-'||hour_text||'00_ffgs_prod_composite_table_01hr_regional.csv';
            END IF;

            csv_file_full_path := csv_base_path||'/'||csv_filename;
                              -----------------------------------------------
            raise notice 'csv file: %', csv_file_full_path;

            -- create temporary table to store CSV contents
            -----------------------------------------------
            EXECUTE format('CREATE TEMP TABLE '||tablename_csv||' ('||c01n||' '||c01t||',
                                                               '||c02n||' '||c02t||',
                                                               '||c03n||' '||c03t||',
                                                               '||c04n||' '||c04t||',
                                                               '||c05n||' '||c05t||',
                                                               '||c06n||' '||c06t||',
                                                               '||c07n||' '||c07t||',
                                                               '||c08n||' '||c08t||',
                                                               '||c09n||' '||c09t||',
                                                               '||c10n||' '||c10t||',
                                                               '||c11n||' '||c11t||',
                                                               '||c12n||' '||c12t||',
                                                               '||c13n||' '||c13t||',
                                                               '||c14n||' '||c14t||',
                                                               '||c15n||' '||c15t||',
                                                               '||c16n||' '||c16t||',
                                                               '||c17n||' '||c17t||',
                                                               '||c18n||' '||c18t||',
                                                               '||c19n||' '||c19t||',
                                                               '||c20n||' '||c20t||',
                                                               '||c21n||' '||c21t||',
                                                               '||c22n||' '||c22t||',
                                                               '||c23n||' '||c23t||',
                                                               '||c24n||' '||c24t||',
                                                               '||c25n||' '||c25t||',
                                                               '||c26n||' '||c26t||',
                                                               '||c27n||' '||c27t||',
                                                               '||c28n||' '||c28t||',
                                                               '||c29n||' '||c29t||') ON COMMIT DROP');

            EXECUTE format('COPY '||tablename_csv||' FROM '''||csv_file_full_path||''' DELIMITER '','' CSV HEADER');



            -- create temp timeseries table
            -------------------------------
            EXECUTE format('CREATE TEMP TABLE '||tablename_ts||' (date timestamptz, basin int, value double precision) ON COMMIT DROP');

            FOR rec in SELECT * FROM temp_table_csv
            LOOP
                    curr_basin := rec.basin;
                    IF product = 'MAP' THEN
                            IF interval = '01' THEN
                                    curr_val := rec.map01;
                            ELSIF interval = '03' THEN
                                    curr_val := rec.map03;
                            ELSIF interval = '06' THEN
                                    curr_val := rec.map06;
                            ELSIF interval = '24' THEN
                                    curr_val := rec.map24;
                            ELSE
                                    raise warning 'interval not found';
                                    EXIT;
                            END IF;
                    ELSIF product = 'ASM' THEN
                            IF interval = '06' THEN
                                    curr_val := rec.asmu06;
                            ELSE
                                    raise warning 'interval not found';
                                    EXIT;
                            END IF;
                    ELSIF product = 'FFG' THEN
                            IF interval = '01' THEN
                                    curr_val := rec.ffg01;
                            ELSIF interval = '03' THEN
                                    curr_val := rec.ffg03;
                            ELSIF interval = '06' THEN
                                    curr_val := rec.ffg06;
                            ELSE
                                    raise warning 'interval not found';
                                    EXIT;
                            END IF;
                    ELSIF product = 'FFT' THEN
                            IF interval = '01' THEN
                                    curr_val := rec.fft01;
                            ELSIF interval = '03' THEN
                                    curr_val := rec.fft03;
                            ELSIF interval = '06' THEN
                                    curr_val := rec.fft06;
                            ELSE
                                    raise warning 'interval not found';
                                    EXIT;
                            END IF;
                    ELSE
                            raise warning 'product not found';
                            EXIT;
                    END IF;

                    INSERT INTO temp_table_ts (date, basin, value) VALUES (adj_timestamp, curr_basin, curr_val);
            END LOOP;

            RETURN QUERY
            EXECUTE 'SELECT g.gid,
                            g.geom,
                            g.basin,
                            a.date,
                            a.value
                            FROM sarffg_basins_00_regional_3sec
                            AS g
                            JOIN '||tablename_ts||'
                            AS a
                            ON g.'||quote_ident('basin')||' = a.'||quote_ident('basin')||'
                            WHERE a.'||quote_ident('date')||' = '''||adj_timestamp||'''';
    ELSE

            RETURN QUERY
            EXECUTE format('SELECT g.gid,
                                   g.geom,
                                   g.basin,
                                   a."DATE",
                                   a."VALUE"
                                   FROM sarffg_basins_00_regional_3sec AS g
                                   JOIN %I AS a ON g.basin = a."BASIN"
                                   WHERE a."DATE" = $1', prod_table)
                                   using adj_timestamp;

    END IF;

END;
$$ LANGUAGE plpgsql;

编辑4:经过一些更多的尽职调查后,我又回想起问题来自于输出中缺少列名。我的颜色标记明确引用了“VALUE”,但显然没有列名,它不知道哪个是“VALUE”。

将我的查询更改为

select gid, geom, basin, "DATE", "VALUE" from ingest_ffgs_prod_composite_csv('%prod_table%', 1484438400)

给我我的专栏名称,但遗憾的是没有解决我的问题。

1 个答案:

答案 0 :(得分:1)

你的问题太混乱了。不过,我确实注意到了引用标识符的问题。该函数在返回类型中声明:

...
RETURNS table (
    ...
    date timestamptz,
    VALUE double precision
    )
...

但是函数调用不匹配:

select gid, geom, basin, "DATE", "VALUE" from ingest_ffgs_prod_composite_csv(...

这完全解释了您的错误消息:

ERROR:  column "VALUE" does not exist

因为没有列"VALUE"。只有value(双引或不引用)。

所有未加引号的标识符都在Postgres中转换为小写。因此,VALUEvalue(但不是 "VALUE""Value")与 相同,作为标识符 。但不是字符串。使用quote_ident()传递给format()'%I'时,案例很重要,因为它已被转义。所以:

a.'||quote_ident('DATE')||',
a.'||quote_ident('VALUE')||'

生成a."DATE", a."VALUE",与a.DATE, a.VALUE不同。

此外,PL / pgSQL或SQL函数的 body 中的列名在外部不可见。只有声明的返回类型才对函数调用很重要。

我通常建议不要将datevalue作为列名。 Both are allowed in Postgres, but reserved words in standard SQL.

专门使用合法的小写不带引号的标识符来避免这种混淆。