58

I understand that in Postgres pure, you can pass an integer array into a function but that this isn't supported in the .NET data provider Npgsql.

I currently have a DbCommand into which I load a call to a stored proc, add in a parameter and execute scalar to get back an Id to populate an object with.

This now needs to take n integers as arguments. These are used to create child records linking the newly created record by it's id to the integer arguments.

Ideally I'd rather not have to make multiple ExecuteNonQuery calls on my DbCommand for each of the integers, so I'm about to build a csv string as a parameter that will be split on the database side.

I normally live in LINQ 2 SQL savouring the Db abstraction, working on this project with manual data access it's all just getting a bit dirty, how do people usually go about passing these kinds of parameters into postgres?

brichins
  • 3,825
  • 2
  • 39
  • 60
Tristan Warner-Smith
  • 9,631
  • 6
  • 46
  • 75
  • 5
    For those who don't read past the selected answers: Array parameters *are* supported with the .NET Npgsql provider (though I can't say if this was true when the question was asked). See my [answer below](http://stackoverflow.com/a/7733714/957950). – brichins Sep 30 '14 at 13:40

4 Answers4

79

See: http://www.postgresql.org/docs/9.1/static/arrays.html

If your non-native driver still does not allow you to pass arrays, then you can:

  • pass a string representation of an array (which your stored procedure can then parse into an array -- see string_to_array)

    CREATE FUNCTION my_method(TEXT) RETURNS VOID AS $$ 
    DECLARE
           ids INT[];
    BEGIN
           ids = string_to_array($1,',');
           ...
    END $$ LANGUAGE plpgsql;
    

    then

    SELECT my_method(:1)
    

    with :1 = '1,2,3,4'

  • rely on Postgres itself to cast from a string to an array

    CREATE FUNCTION my_method(INT[]) RETURNS VOID AS $$ 
           ...
    END $$ LANGUAGE plpgsql;
    

    then

    SELECT my_method('{1,2,3,4}')
    
  • choose not to use bind variables and issue an explicit command string with all parameters spelled out instead (make sure to validate or escape all parameters coming from outside to avoid SQL injection attacks.)

    CREATE FUNCTION my_method(INT[]) RETURNS VOID AS $$ 
           ...
    END $$ LANGUAGE plpgsql;
    

    then

    SELECT my_method(ARRAY [1,2,3,4])
    
vladr
  • 65,483
  • 18
  • 129
  • 130
  • Thanks! No I hadn't, I was relying on what my colleagues had told me. I'll give it a shot. – Tristan Warner-Smith Feb 20 '09 at 23:15
  • 1
    Do you have any proof regarding lack of native support of prepared statements in PG? I couldn't find any mentions of that fact in the [manual](http://www.postgresql.org/docs/9.2/static/sql-prepare.html). As for logging, I guess that server logs the queries as they are executed not as they are passed from the client thus no "EXECUTE" statements in the log. – Ihor Kaharlichenko Aug 13 '12 at 14:33
  • 1
    @IhorKaharlichenko the wording was poor, and was off-topic. Edited the reply. Re. the original claim, drivers will not use `PREPARE` unless server-side prepared statements are enabled (sometimes explicitly) and, in some cases, unless a certain number-of-executions threshold has been exceeded for a command. If command logging is enabled in `postgresql.conf` then you will see the actual `SELECT`/`INSERT`/etc. commands when server-side prepared statements are NOT used. – vladr Aug 13 '12 at 18:11
74

I realize this is an old question, but it took me several hours to find a good solution and thought I'd pass on what I learned here and save someone else the trouble. Try, for example,

    SELECT * FROM some_table WHERE id_column = ANY(@id_list)

where @id_list is bound to an int[] parameter by way of

    command.Parameters.Add("@id_list", NpgsqlDbType.Array|NpgsqlDbType.Integer).Value = my_id_list;

where command is a NpgsqlCommand (using C# and Npgsql in Visual Studio).

brichins
  • 3,825
  • 2
  • 39
  • 60
  • not sure if this will get answered, but your answer seemed to directly address situation. In my C# code I'm building a list of IDs to filter a DGV's DataTable. The ID list looks like `1234,2345,3456`. I was adding parameter the "usual" way: `cmd.Parameters.Add(new NpgsqlParameter("idList", idFilter));` but was getting an error on filling the dataadapter with the dataset, "operator does not exist: integer = text'"". I found your code and plugged it in, but I'm getting a different error: "Can't write type System.String as an array of System.Int32'". Suggestions? – marky Apr 07 '20 at 17:34
  • @marky it sounds like you are trying to assign the `idList` parameter a string literal (`"123,456"`) instead of an integer array (`[123, 456]`). The point of the Npgsql tooling is that you are not hand-rolling a SQL statement with injected text values, you are building a strongly-typed query with type-safe parameters that expects typed objects. – brichins Apr 07 '20 at 20:54
  • Okay, that makes sense, so how do I go from a string of numbers (`"1234,2345, 3456"`) to an integer array? Probably better yet, how do I build the list as an integer array (current code: `idToAddToList = dt.Rows[rowIndex][0].ToString();`) and pass that to the `GetDgvData()` function and add that array as a parameter? Can I use `List` in this case? – marky Apr 07 '20 at 21:27
  • Well, I got a `List<>` of Ids to `GetDgvData()`, but I can't figure out what to do with it at that point to get the values into the query. – marky Apr 07 '20 at 21:55
  • 1
    @marky the second code block in the answer specifies an `int[]` or `Array` if you prefer that syntax. Stop calling `.ToString()` and just push the integers from your `dt` column into an array, then pass that. I don't think Npgsql supports a `List` type, but if you're more comfortable with Lists, you could continue to build a `List` first and then call `.ToArray()`, but the process is nearly identical so I'd suggest skipping the intermediate step. Then your parameter (which I called `my_id_list`) is bound as shown above. – brichins Apr 07 '20 at 22:52
  • What if @id_list is an optional parameter or its null ? in that case how do we handle it – amit agarwal Aug 22 '23 at 06:55
2

You can always use a properly formatted string. The trick is the formatting.

command.Parameters.Add("@array_parameter", string.Format("{{{0}}}", string.Join(",", array));

Note that if your array is an array of strings, then you'll need to use array.Select(value => string.Format("\"{0}\", value)) or the equivalent. I use this style for an array of an enumerated type in PostgreSQL, because there's no automatic conversion from the array.

In my case, my enumerated type has some values like 'value1', 'value2', 'value3', and my C# enumeration has matching values. In my case, the final SQL query ends up looking something like (E'{"value1","value2"}'), and this works.

jhonkola
  • 3,385
  • 1
  • 17
  • 32
2

Full Coding Structure

postgresql function

CREATE OR REPLACE FUNCTION admin.usp_itemdisplayid_byitemhead_select(
    item_head_list int[])
    RETURNS TABLE(item_display_id integer) 
    LANGUAGE 'sql'

    COST 100
    VOLATILE 
    ROWS 1000
    
AS $BODY$ 
        SELECT vii.item_display_id from admin.view_item_information as vii
where vii.item_head_id = ANY(item_head_list);
    $BODY$;

Model

public class CampaignCreator
    {
        public int item_display_id { get; set; }
        public List<int> pitem_head_id { get; set; }
    }

.NET CORE function

DynamicParameters _parameter = new DynamicParameters();
                _parameter.Add("@item_head_list",obj.pitem_head_id);
                
                string sql = "select * from admin.usp_itemdisplayid_byitemhead_select(@item_head_list)";
                response.data = await _connection.QueryAsync<CampaignCreator>(sql, _parameter);