I am using C# in VS 2008 to retrieve data from a PostgreSQL stored procedure that has two input parameters and two output parameters. When I created the procedure, PostgreSQL told me that I had to specify that it returns a record.
In VS2008, my first attempt to use the procedure involved creating an OdbcCommand
object of type CommandType.StoredProcedure
and giving it four parameters, two with direction of Input and two of direction Output. The command executed without error, first using ExecuteNonQuery()
and then using ExecuteReader()
, but the values of the output parameters were null. I called the reader's GetValues()
function, and found that the result was a single object containing the string "{3,4}"
.
Then, following a suggestion from StackOverflow, I changed the command text to: {call closest_idle_cover(?, ?, ?, ?)}
This also worked, and GetValues()
gave me an array of two objects of type int, one with 3 and the other with 4. This was quite a bit better, since I wouldn't have to parse a string. But the output parameters still have null values, and indeed, the command works just as well if I only pass in the two input parameters.
So, although I have a solution that works, I remain curious: How can I get the values into my output parameters?
Here's the PostgreSQL stored procedure:
CREATE OR REPLACE FUNCTION plant_genie.closest_idle_cover(IN int, IN int, OUT int, OUT int)
RETURNS record AS
$BODY$
DECLARE
current_x ALIAS FOR $1;
current_y ALIAS FOR $2;
target_x ALIAS FOR $3;
target_y ALIAS FOR $4;
coverLocations ic_storage_locations%rowtype;
BEGIN
target_x := 3;
target_y := 4;
SELECT INTO coverLocations *
FROM ic_storage_locations
WHERE inner_cover IS NOT NULL
ORDER BY sqrt(pow(current_x - ic_storage_locations.x_coordinate, 2) +
pow(current_y - ic_storage_locations.y_coordinate, 2))
LIMIT 1;
IF FOUND THEN
INSERT INTO op_messages (message) VALUES ('Found a cover location record.');
target_x := coverLocations.x_coordinate;
target_y := coverLocations.y_coordinate;
ELSE
INSERT INTO op_messages (message) VALUES ('Could not find a cover location record.');
END IF;
END;
$BODY$ LANGUAGE 'plpgsql' VOLATILE COST 100;
You are using OUT
parameters but then also a RETURNS record
clause, without having an explicit RETURN
statement in the body of the function. That combination does not work. A more elegant solution than using OUT
parameters is to define the output table format - it is more obvious what is going on:
CREATE OR REPLACE FUNCTION plant_genie.closest_idle_cover(current_x int, current_y int)
RETURNS TABLE (target_x int, target_y int) AS $BODY$
DECLARE
coverLocations ic_storage_locations%rowtype;
BEGIN
SELECT INTO coverLocations *
FROM ic_storage_locations
WHERE inner_cover IS NOT NULL
ORDER BY pow(current_x - ic_storage_locations.x_coordinate, 2) +
pow(current_y - ic_storage_locations.y_coordinate, 2)
LIMIT 1;
IF FOUND THEN
INSERT INTO op_messages (message) VALUES ('Found a cover location record.');
RETURN QUERY SELECT coverLocations.x_coordinate, coverLocations.y_coordinate;
ELSE
INSERT INTO op_messages (message) VALUES ('Could not find a cover location record.');
END IF;
END; $BODY$ LANGUAGE 'plpgsql' STRICT;
So if you call this function you are returned a record if at least 1 ic_storage_location is not null:
SELECT * FROM plant_genie.closest_idle_cover(1, 2);
You can deal with that in your C# code like you would any other data you pull out of the database.
A few observations:
SQRT()
function call, which is computationally expensive. Just working with the squared sums has the same property of ordering records by distance from the current location. STRICT
because it requires values for both parameters to work properly. COST
values yourself, unless you really know what you are doing.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.