How to write a function that returns text or integer values?

前端 未结 3 902
名媛妹妹
名媛妹妹 2020-12-11 07:34

I\'m using PostgreSQL 9.2.4.

postgres=# select version();

                           version
--------------------------------------------------------         


        
相关标签:
3条回答
  • 2020-12-11 08:13

    What you probably need

    Most likely you need one function to return text and another one to return integer or a function that returns boolean to indicate success. All of this is trivial and I'll refer you to the excellent manual on CREATE FUNCTION or code examples in similar questions on SO.

    What you actually asked

    How to write a function that returns text or integer values?

    ... in the sense that we have one return type being either text or integer. Not as trivial, but also not impossible as has been suggested. The key word is: polymorphic types.

    Building on this simple table:

    CREATE TABLE tbl(
      tbl_id int,
      txt    text,
      nr     int
    );
    

    This function returns either integer or text (or any other type if you allow it), depending on the input type.

    CREATE FUNCTION f_insert_data(_id int, _data anyelement, OUT _result anyelement)
      RETURNS anyelement AS
    $func$
    BEGIN
    
    CASE pg_typeof(_data) 
    WHEN 'text'::regtype THEN
        INSERT INTO tbl(tbl_id, txt) VALUES(_id, _data)
        RETURNING txt
        INTO _result;
    
    WHEN 'integer'::regtype THEN
        INSERT INTO tbl(tbl_id, nr) VALUES(_id, _data)
        RETURNING nr
        INTO _result;
    
    ELSE
        RAISE EXCEPTION 'Unexpected data type: %', pg_typeof(_data)::text;
    END CASE;
    
    END
    $func$
    LANGUAGE plpgsql;
    

    Call:

    SELECT f_insert_data(1, 'foo'::text);  -- explicit cast needed.
    SELECT f_insert_data(1, 7);
    

    Simple case

    One function that returns TRUE / FALSE to indicate whether a row has been inserted, only one input parameter of varying type:

    CREATE FUNCTION f_insert_data2(_id int, _data anyelement)
      RETURNS boolean AS
    $func$
    BEGIN
    
    CASE pg_typeof(_data)
    WHEN 'text'::regtype THEN
       INSERT INTO tbl(tbl_id, txt) VALUES(_id, _data);
    
    WHEN 'integer'::regtype THEN
       INSERT INTO tbl(tbl_id, nr) VALUES(_id, _data);
    
    ELSE
       RAISE EXCEPTION 'Unexpected data type: >>%<<', pg_typeof(_data)::text;
    END CASE;
    
    IF FOUND THEN RETURN TRUE;
    ELSE RETURN FALSE;
    END IF;
    
    END
    $func$
    LANGUAGE plpgsql;
    

    The input type can be replaced with a text parameter for most purposes, which can be cast to and from any other type.

    0 讨论(0)
  • 2020-12-11 08:23

    It sounds like you're solving a problem by creating a bigger problem.

    You don't need a function for this at all. Do it on the client side by checking the affected rows count that's returned by every DML query, or use INSERT ... RETURNING.

    You didn't mention your client language, so here's how to do it in Python with psycopg2. The same approach applies in other languages with syntax variations.

    #!/usr/bin/env python
    import psycopg2
    
    # Connect to the db
    conn = psycopg2.connect("dbname=regress")
    curs = conn.cursor()
    
    # Set up the table to use
    curs.execute("""
    DROP TABLE IF EXISTS so17587735;
    
    CREATE TABLE so17587735 (
        id serial primary key,
        blah text not null
    );
    """);
    
    # Approach 1: Do the insert and check the rowcount:
    curs.execute("""
    INSERT INTO so17587735(blah) VALUES ('whatever');
    """);
    if curs.rowcount != 1:
        raise Exception("Argh, insert affected zero rows, wtf?")
    print("Inserted {0} rows as expected".format(curs.rowcount))
    
    # Approach 2: Use RETURNING
    curs.execute("""
    INSERT INTO so17587735(blah) VALUES ('bored') RETURNING id;
    """);
    returned_rows = curs.fetchall();
    if len(returned_rows) != 1:
        raise Exception("Got unexpected row count {0} from INSERT".format(len(returned_rows)))
    
    print("Inserted row id is {0}".format(returned_rows[0][0]))
    

    In the case of PL/PgSQL calling INSERT you can use the GET DIAGNOSTICS command, the FOUND variable, or RETURN QUERY EXECUTE INSERT ... RETURNING .... Using GET DIAGNOSTICS:

    CREATE OR REPLACE FUNCTION blah() RETURNS void AS $$
    DECLARE
        inserted_rows integer;
    BEGIN
        INSERT INTO some_table VALUES ('whatever');
        GET DIAGNOSTICS inserted_rows = ROW_COUNT;
        IF inserted_rows <> 1 THEN
            RAISE EXCEPTION 'Failed to insert rows; expected 1 row, got %', inserted_rows;
        END IF;
    END;
    $$ LANGUAGE plpgsql VOLATILE;
    

    or if you must return values and must for some reason use PL/PgSQL:

    CREATE OR REPLACE FUNCTION blah() RETURNS SETOF integer AS $$
    BEGIN
        RETURN QUERY EXECUTE INSERT INTO some_table VALUES ('whatever') RETURNING id;
    END;
    $$ LANGUAGE plpgsql VOLATILE;
    

    (assuming the key is id)

    which would be the same as:

    CREATE OR REPLACE FUNCTION blah() RETURNS SETOF integer AS $$
    INSERT INTO some_table VALUES ('whatever') RETURNING id;
    $$ LANGUAGE sql;
    

    or just

    INSERT INTO some_table VALUES ('whatever') RETURNING id;
    

    In other words: Why wrap this in a function? It doesn't make sense. Just check the row-count client side, either with RETURNING or by using the client driver's affected-rows count for INSERT.

    0 讨论(0)
  • 2020-12-11 08:25

    A function can only return one type. In your case, you could create a composite type with two fields, one integer and one text, and return that.

    0 讨论(0)
提交回复
热议问题