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

When I run this code I get back 3 lines of data regarding different volumes. It's reading some data from a sqlite database.

If I uncomment the $dbh->prepare line I get back only 1 line of data and the loop ends. I cant understand why? It's like the prepare statement is causing the while loop to exit?
# Get a list of volumes that are being monitored and attempt # to get data about them # $sth = $dbh->prepare("SELECT volumeletter,peernodes from VolData order + by volumeid"); $sth->execute(); # Iterate over configured volumes and update status, rrd data and grap +hs while ( my ($vol,$peernodes) = $sth->fetchrow_array() ) { print "\n\n\nVOLUME $vol Peer nodes ($peernodes)\n\n\n"; # Populate a hash with current volume data %voldata = &getVolumeData($vol,$peernodes); # Now, let's update the database with the volumes current stat +e switch ($voldata{'MirrorState'}) { case "0" {$vol_current_state = "Not Mirrored"} case "1" {$vol_current_state = "Mirroring"} case "2" {$vol_current_state = "Resync"} case "3" {$vol_current_state = "Broken"} case "4" {$vol_current_state = "Paused"} case "5" {$vol_current_state = "Resync Pending"} else {$vol_current_state = "Unknown"} } print "COLLECTOR: Setting volume $vol to state $vol_current_st +ate\n"; #$sth = $dbh->prepare("UPDATE VolData SET volstatus = '$vol_cu +rrent_state' WHERE volumeletter = '$vol'"); #$sth->execute(); }

Replies are listed 'Best First'.
Re: Why does DBI prepare break my loop?
by hippo (Archbishop) on Jul 07, 2014 at 10:12 UTC

    By uncommenting the last two lines of the loop you are overwriting the $sth object which will necessarily affect the condition in the while test. If you don't want to overwrite $sth choose a different variable name to hold the inner statement handle.

Re: Why does DBI prepare break my loop?
by Corion (Patriarch) on Jul 07, 2014 at 10:19 UTC

    In addition to the remarks of the others, you may want to read up on placeholders and bind values to reduce the amount of preparing of statements.

Re: Why does DBI prepare break my loop?
by Anonymous Monk on Jul 07, 2014 at 10:13 UTC

    Because you're storing a new handle in $sth thus destroying $sth thus ending the loop

Re: Why does DBI prepare break my loop?
by locked_user sundialsvc4 (Abbot) on Jul 07, 2014 at 13:48 UTC

    Here, in a nutshell, is what I think you should be doing.   Fortunately, it is an easy change:

    1. You will need to use two statement-handles:   one for the SELECT statement, the second for the UPDATE.   As it is, you are destroying(!) the SELECT statement while you’re fetching from it, and that’s why the loop ends.
    2. The UPDATE statement should use placeholders, not literal text, and so it should be PREPAREd once, outside of the loop, then executed repeatedly with a different set of substitutions for those placeholders each time.

    For example, in relevant parts ... (extemporaneous coding, might have syntax errors ...)

    # LET'S GIVE MEANINGFUL VARIABLE-NAMES TO THE TWO STATEMENT HANDLES .. +. $sth_select = $dbh->prepare("SELECT volumeletter,peernodes from VolDat +a order + by volumeid"); $sth_select->execute(); $sth_update = $dbh->prepare("UPDATE VolData SET volstatus = ? WHERE vo +lumeletter = ?"); # NOTICE THAT THE '?'S ARE NOT ENCLOSED IN QUOTES!
    ... and ...
    $sth_update->execute( [$vol_current_state, $vol] );

    The question-marks in the prepared statement (which are not enclosed in quotes) act as “placeholders” for values which are provided each time the prepared statement is executed.

    NOTE:   In the case of an SQLite database, you should also enclose the loop in a transaction.   When an UPDATE statement is executed outside of a transaction, SQLite will physically verify (re-read ...) the data after it has written, more than doubling the I/O activity and bringing things to a crawl.   When a transaction is active, SQLite performs the expected “lazy writes.”   This is not-so important for other database engines, but it is crucial for SQLite.

      sundialsvc4:

      You'll need to change that last line of code to:

      $sth_update->execute( $vol_current_state, $vol );

      ...roboticus

      When your only tool is a hammer, all problems look like your thumb.

      Despite the advice to use transactions not being bad, like last time I call BS on the "physically verify" part. (Got any references?)

      Also like last time, you're misrepresenting what "outside a transaction" means, since there is no such thing. There's only implicit and explicit transactions, and unlike what you say, SQLite performs the same actions at the end of either. Explicit transactions just help becase they delay those actions until the user choses to commit the transaction.

Re: Why does DBI prepare break my loop?
by perlfan (Parson) on Jul 07, 2014 at 11:41 UTC
    Don't use prepare, use the select*family of methods (e.g., selectall_hashref, selectrow_array, etc.) These allow you to avoid the explicit prepare and use bind variables.

      Hi perlfan,

      sorry, but I have to disagree with your general advice not to use prepare and execute. IMHO this use case is exactly the one to use prepared statement handles. It was just abused or misunderstood by aditya1977.

      I would rewrite the snippet to the following ona rush:

      my $data_source = 'something'; my $username = 'user'; my $password = 'password'; $dbh = DBI->connect($data_source, $username, $password, { RaiseError => 1, AutoCommit => 1, }); my $sql_select = " SELECT volumeletter, peernodes FROM VolData ORDER BY volumeid "; my $sql_update = " UPDATE VolData SET volstatus = ? WHERE volumeletter = ? "; my %NUM_2_VOLSTATUS = ( 0 => 'Not Mirrored', 1 => 'Mirroring', 2 => 'Resync', 3 => 'Broken', 4 => 'Paused', 5 => 'Resync Pending', ); my $sth = $dbh->prepare($sql_select); my $sth_update = $dbh->prepare($sql_update); $sth->execute(); # Iterate over configured volumes and update status, rrd data and grap +hs while ( my ($vol, $peernodes) = $sth->fetchrow_array() ) { # Populate a hash with current volume data my %voldata = getVolumeData($vol, $peernodes); my $vol_current_state = $NUM_2_VOLSTATUS{$voldata{'MirrorState'}} +|| 'Unknown'; print "COLLECTOR: Setting volume $vol to state $vol_current_state\ +n"; $sth_update->execute($vol_current_state, $vol); } $sth_update->finish; $sth->finish; $dbh->disconnect;

      The two SQL statements are prepared and checked early. The statement for the update which is often used in the loop is prepared exactly once and used many times.

      This solution has another advantage: On a database which supports server side result set cursors you really iterate over the result set row by row. You don't pull the whole result set to the DB client.

      I also replaced the switch clause by a hash lookup which should be faster. More readable is it anyway, IMHO.

      Regards
      McA

        You may, TIMTOWTDI =)