databasepostgresqlplpgsqlpsqldynamic-sql

Query string argument of EXECUTE is null


EDIT

It seems my issue is when this select statement returns null (which is the case I'm trying to handle - when it returns null, I want my new value to be -999). How can I go about doing this if it errors out whenever a null is found?

ORIGINAL

I have read every other SO post I could find regarding this error, but none of which seemed to address the root of my issue.

The error is pretty straightforward - one of my arguments within my EXECUTE statement is null. Great. However, I print out each of the values that make up my EXECUTE statement right before it gets called, and I can clearly see that none of the values are null.

Code:

CREATE FUNCTION inform_icrm_prob_flow_query(tablename text, location_id int,
                                            product_date_str text, lead_time_start int,
                                            lead_time_end int, first_member_id int,
                                            last_member_id int, dest_file text)
RETURNS void AS $$
DECLARE
  count int;
  product_date TIMESTAMPTZ;
  interval_lead_time_start text;
  interval_lead_time_end text;
  curr_value double precision;
  query text;
BEGIN
  product_date := product_date_str::TIMESTAMPTZ;
  count := first_member_id;
  curr_value := 0;

  interval_lead_time_start :=  ''''|| product_date ||'''::timestamptz +
                               interval '''||lead_time_start||' hours''';
  interval_lead_time_end :=  ''''|| product_date ||'''::timestamptz +
                             interval '''||lead_time_end||' hours'' -
                             interval ''6 hours''';

  --create our temporary table and populate it's date column
  EXECUTE 'CREATE TEMPORARY TABLE temp_table_icrm_prob_flow AS 
             SELECT * FROM generate_series('||interval_lead_time_start || ',' || 
                                            interval_lead_time_end || ', ''6 hours'') 
                           AS date_valid';
  LOOP
    EXIT WHEN count > last_member_id;
    IF NOT EXISTS(
      SELECT 'date_valid'
      FROM information_schema.columns
      WHERE table_name='temp_table_icrm_prob_flow'
        and column_name='value'||count||'') 
    THEN
      EXECUTE 'ALTER TABLE temp_table_icrm_prob_flow ADD COLUMN value' || count 
               || ' double precision DEFAULT -999';
    END IF;

    raise notice 'tablename: %', tablename;
    raise notice 'location_id: %', location_id;
    raise notice 'product_date: %', product_date;
    raise notice 'count: %', count;

    query := 'SELECT value FROM '|| tablename ||' 
              INNER JOIN temp_table_icrm_prob_flow
              ON (temp_table_icrm_prob_flow.date_valid = '|| tablename ||'.date_valid)
              WHERE '|| tablename ||'.id_location = '|| location_id ||'
                AND '|| tablename ||'.date_product = '''|| product_date ||'''
                AND '|| tablename ||'.id_member = '|| count ||'';

    EXECUTE query INTO curr_value;

    EXECUTE 'UPDATE temp_table_icrm_prob_flow 
             SET value'|| count ||' = COALESCE('|| curr_value ||', -999)';

    count := count + 1;
  END LOOP;

  EXECUTE 'ALTER TABLE temp_table_icrm_prob_flow DROP COLUMN date_valid';

  EXECUTE 'COPY temp_table_icrm_prob_flow TO '''||dest_file||''' DELIMITER '','' CSV';

  EXECUTE 'DROP TABLE temp_table_icrm_prob_flow';
END;
$$ LANGUAGE plpgsql;

Output:

NOTICE:  tablename: inform_tseries_data_basin_proc_fcst_prob_flow
NOTICE:  location_id: 38
NOTICE:  product_date: 2015-02-05 12:00:00+00
NOTICE:  count: 1
ERROR:  query string argument of EXECUTE is null
CONTEXT:  PL/pgSQL function inform_icrm_prob_flow_query(text,integer,text,integer,integer,integer,integer,text) line 38 at EXECUTE

If none of the variables I am passing in are null, and the only other thing referenced is a temp table that I know exists, what could be causing this error?

Note: when changing my query to:

query := 'SELECT value FROM '|| tablename ||' WHERE '|| tablename ||'.id_location = '|| location_id ||' AND '|| tablename ||'.date_product = '''|| product_date ||''' AND '|| tablename ||'.id_member = '|| count ||' AND temp_table_icrm_prob_flow.date_va    lid = '|| tablename ||'.date_valid';

I get the following error:

NOTICE:  tablename: inform_tseries_data_basin_proc_fcst_prob_flow
NOTICE:  location_id: 38
NOTICE:  product_date: 2015-02-05 12:00:00+00
NOTICE:  count: 1
ERROR:  missing FROM-clause entry for table "temp_table_icrm_prob_flow"
LINE 1: ..._data_basin_proc_fcst_prob_flow.id_member = 1 AND temp_table...
                                                         ^
QUERY:  SELECT value FROM inform_tseries_data_basin_proc_fcst_prob_flow WHERE inform_tseries_data_basin_proc_fcst_prob_flow.id_location = 38 AND inform_tseries_data_basin_proc_fcst_prob_flow.date_product = '2015-02-05 12:00:00+00' AND inform_tseries_data_basin_proc_fcst_prob_flow.id_member = 1 AND temp_table_icrm_prob_flow.date_valid = inform_tseries_data_basin_proc_fcst_prob_flow.date_valid
CONTEXT:  PL/pgSQL function inform_icrm_prob_flow_query(text,integer,text,integer,integer,integer,integer,text) line 35 at EXECUTE

Solution

  • Sorry for small offtopic. Your code is pretty unreadable (and SQL injecttion vulnerable). There are some techniques, that you can use:

    1. Use clause USING of EXECUTE statement for usual parameters.

      DO $$
      DECLARE
        tablename text := 'mytab';
        from_date date := CURRENT_DATE;
      BEGIN
         EXECUTE 'INSERT INTO ' || quote_ident(tablename) || ' VALUES($1)'
            USING from_date;
      END
      $$;
      

      This code will be safe (due using quote_ident function), little bit faster (due using binary value of from_date variable - removed multiple string<->date conversions and little bit more readable (because string expression is shorter).

    2. Use function format. The building query string will be shorter and more readable (table aliases helps too):

      query := format('
      SELECT value
         FROM %I _dtn
              INNER JOIN temp_table_icrm_prob_flow t ON t.date_valid = _dtn.date_valid
        WHERE _dtn.id_location = $1
          AND _dtn.date_product = $2
          AND _dtd.id_member = $3'
                       , tablename);
       EXECUTE query INTO curr_value USING location_id, product_date, count;
      

    Using variables named like important SQL keywords and identifier is wrong idea - names count, values are wrong.

    The error message is clean - you are using the identifier temp_table_icrm_prob_flow.date_valid, but the table temp_table_icrm_prob_flow is not mentioned in query. The query missing JOIN part.