Reverend Phil has asked for the wisdom of the Perl Monks concerning the following question:

Hi folks. I've looked through a slew of DBI/stored procedure nodes here and elsewhere on the web. I've gone through the DBI and DBD::ODBC documentation. I've gotten most of the way there...

I have a stored procedure which inserts data into a table, and returns the auto-generated key field. I can successfully call this to insert the data. If I try to get the return value, I'm hosed.

I've gone and individually used bind_param_inout on each of my fields that I'm inserting, but I can't for the life of me figure out how I'm supposed to bind an output value. The problem is, the field I'm returning is not one of the fields I'm inserting.. so it does not have an index to bind to.

The code is really not so important, and a mess right now. The error, however, might shed some light.

If I bind each of the parameters individually, I get:
DBD::ODBC::st execute failed: [Microsoft][ODBC SQL Server Driver][SQL +Server]Formal parameter '@PONumber' was defined as OUTPUT but the act +ual parameter not declared OUTPUT. (SQL-42000)(DBD: st_execute/SQLExe +cute err=-1) at store_test.pl line 46.
Bind Param is called in the following manner:
$sth->bind_param_inout(1,\$a, 255) or die $sth->errstr;
..where 1 is the index (1 not 0 based) of the parameter in the list of expected parameters.

I'm probably missing something silly.. but then again, it seems like stored procedures have been a recurring question here, and on other sites, with often incomplete answers.

Thanks in advance,
-=rev=-

Replies are listed 'Best First'.
Re: Yet Another Stored Procedure Question
by hossman (Prior) on Jun 23, 2003 at 23:23 UTC

    This is covered in DBI::FAQ ... but the answer isn't all that helpfull. the basic idea is that:

    • DBI doesn't provide anyway to call any DB specific Stored procs, but you can run any SQL, which might include SQL that calls a stored proc.
    • any SQL which sets a value can be used to set a value that you have bound using bind_param_inout

    In some databases, you could do this like so...

    my $rv = 0; my $dbh = DBI->connect(...); my $sth = $dbh->prepare('{?=call name_of_sproc()}'); $sth->bind_param_inout(1, \$rv, 4); $sth->execute(); $sth->finish(); print "return value: $rv\n";

      Thanks to everyone who responded. A half a fresh night's sleep, 200 cathartic pages more of Harry Potter, and of course the replies I received here got me hitting things from different angles, and I've gotten it to work. I'm posting what I did here because hopefully this will help the next person who searches for stored procedures and find lots of mostly helpful tidbits =)

      This is in reference (as the title suggests) to calling stored procedures via DBI&DBD::ODBC on Win32 systems to an m$ SQL server. The test table contained two fields: InField is a varchar field, and OutField is an int, autonumbering.
      CREATE PROCEDURE TestProc @InField varchar(50) AS INSERT INTO TestTable (InField) VALUES (@InField) SELECT @@identity GO
      For those as unfamiliar with stored procs as I am, @@identity on MS SQL returns the last selected identity-type field (ie: autonumbering) in the previous action. Here is the perl code which drops off data and picks up the auto-number field.
      ..connect to db.. my $sth = $dbh->prepare('{call TestProc(?)}'); my $a = "TEST"; $sth->bind_param(1, $a) or die $sth->errstr; $sth->execute(); my $results = $sth->fetch(); $sth->finish(); $dbh->disconnect; print "results are $$results[0]\n";
      Not the prettiest piece of code, but hopefully it's basic enough to be clear to somebody looking to accomplish the same thing.

      Thanks again for the help and the fresh angles. The myriad docs and posts on various sites all had different ways to go about it, and whether or not they work seems quite dependant on so many things.

      -=rev=-
Re: Yet Another Stored Procedure Question
by mpeppler (Vicar) on Jun 24, 2003 at 08:03 UTC
    MS-SQL (and Sybase) stored procedures can return three types of data:
    • SELECT statements
    • OUTPUT parameters
    • Procedure return status
    Output from SELECT statements are returned the normal way, and you normally just need to $sth->fetch() them.

    OUTPUT parameters are handled in DBD::ODBC via bind_param_inout(). However, this will only work if the parameter has been declared OUTPUT in the stored procedure source - otherwise the server has no idea that it needs to return data for that parameter.

    I'm not sure how to retrieve the return status with DBD::ODBC - it might be via the "call ..." syntax.

    In your case the error message appears to indicate that your stored procedure doesn't define any OUTPUT parameters, so bind_param_inout) fails.

    Michael

Re: Yet Another Stored Procedure Question
by one4k4 (Hermit) on Jun 24, 2003 at 14:27 UTC
    The replies above are great, but I wanted to show you some psuedo code for what I do when executing Stored Procs on Sybase. Mind you, this is untested and straight from the fingertips..
    my $sth->prepare("exec DBname..stored_proc_name ?,?"); $sth->execute($var1,'var2'); while ($sth->fetchrow_hashref){..... ... }
    And in the SP, according to what it sounds like you're doing.
    ..spcode.. declare @var int select @var = 42 select @var as var_name_for_hash end ..endofsp..
    The last select statement will be returned to the calling Perl code. And simply running select @var=42 might not do it.. so I used the @var as .. moniker. I don't now, maybe I'm misreading your post and if I am, let me know! :)

    I hope this helps.

    P.S. Why would binding an output value be used? Isn't "output value" really "whatever the SP returns.. list of employees, paychecks, candy bar names"?

    One4k4 - perlmonks@poorheart.com (www.poorheart.com)
Re: Yet Another Stored Procedure Question
by LanceDeeply (Chaplain) on Jun 24, 2003 at 14:37 UTC
    given the table:
    CREATE TABLE dbo.TestIds ( TestID int not null, TestName varchar(50) not null )

    and stored proc w/ out parameter:
    create proc dbo.GetTest( @pName varchar(50) = '', @pID int = 0 out ) as set nocount on begin select @pID = TestID from TestIds where TestName = @pName if ( @@rowcount = 0 ) begin select @pID = isnull(max(TestID),0) + 1 from TestIDs insert TestIDs select TestID = @pID, TestName = @pName end end

    you can do this:
    my $sql = ' declare @id int exec GetTest ?, @id out select GotID = @id '; my $sth = $dbh->prepare($sql); if ( $sth->execute('foo') ) { while (my $hash = $sth->fetchrow_hashref ) { foreach my $key (keys %$hash) { print $key . " => " . $$hash{$ +key} . "\n" if $$hash{$key}; }; } }

    to get this output:
    GotID => 2