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

I'm relatively new to Perl (as well as programming in general). I'm writing a Perl script to automate our monthly stats. The script below will connect to the Oracle database (Express edition 11g) and get the values from a table.

There's another script that runs everyday and adds stats to this table. So, this table has data for every day. I want to get the overall data for the last month.

When I run this script I don't get any error messages. I just get the '0 results' output. However, when I run the exact same SQL statement in the database directly i get a proper output!

It seems that there's a problem in the way I'm passing the date to the SQL statement. I've tried multiple variations of this but nothing seems to work! :(

Any help on where I'm going wrong will be highly appreciated.

use strict; use warnings; use DBI; my $dbh = DBI->connect('DBI:Oracle:SID', 'username', 'password') or di +e "Couldn't connect to database: " . DBI->errstr; # # this date format is just for demo. # the actual variable will have date passed like this: # my $date = "$month/%/$year"; # where $month & $year will be calculated using DateTime # my $date = '08/%/2015'; # I've also tried other date formats like '"08/%/2015"' but nothing + seems to work! :( my $sth = $dbh->prepare('SELECT sum(v1),sum(v2),sum(v3),sum(v4),sum(v5 +) FROM some_table WHERE DATE LIKE ?'); $sth->execute($date) or die "Couldn't get values from table: " . DBI-> +errstr; my ($v1,$v2,$v3,$v4,$v5); if ($sth->rows == 0) { print "0 results\n"; exit; }else{ while (my @data = $sth_1->fetchrow_array()) { $v1 = $data[0]; $v2 = $data[1]; $v3 = $data[2]; $v4 = $data[3]; $v5 = $data[4]; } print "$v1,$v2,$v3,$v4,$v5\n"; }
--------

UPDATE

I've managed to fix this based on suggestions by runrig and SimonPratt. Thanks guys :)

Here's what I did:

1. The dates in the table are stored in Oracle's default date format. So instead of using the 'LIKE' and the % wildcard I used actual dates for searching. The new statement looks like this:

SELECT sum(EMAIL_IRT),sum(DROP_CALL_COUNT),sum(ANS_CALL_COUNT),sum(EMA +ILS_IN),sum(EMAILS_OUT) FROM CS_STATS WHERE DATE_ADDED >= TO_DATE(?,\ +'yyyy-mm-dd\') AND DATE_ADDED <= TO_DATE(?,\'yyyy-mm-dd\')

2. Set the dates between which I want to search using DateTime. The script will run 1st of every month (to get the stats for the previous month) so I'm using this piece of code to get the 1st and last dates.

my $yesterday = DateTime-> now(time_zone=>'Asia/Calcutta')->subtract( +days => 1 ); my $y_month = $yesterday->month; my $y_year = $yesterday->year; if ($y_month < 10){ $y_month = "0".$y_month; } my $finaldt1 = $y_year."-".$y_month."-01"; my $finaldt2 = $yesterday->ymd;

Replies are listed 'Best First'.
Re: Problem passing date to SQL
by runrig (Abbot) on Aug 18, 2015 at 15:36 UTC
    You should either use a TO_DATE() function in your sql, or set the nls date/timestamp format(s), e.g.:
    ... WHERE date_column = TO_DATE(?, 'YYYY-MM-DD') ... my $date_str = '2015-08-08'; $sth->execute($date_str); # OR my $date_fmt = my $datetime_fmt = my $datetime_tz_fmt = 'YYYY-MM-DD HH +24:MI:SS'; $dbh->do("alter session set nls_date_format=$date_fmt"); $dbh->do("alter session set nls_timestamp_format=$datetime_fmt"); $dbh->do("alter session set nls_timestamp_tz_format=$datetime_tz_fmt") +; ... WHERE date_column = ? ... my $date_str = '2015-08-08'; $sth->execute($date_str);
    Update: Oh, and I agree w/below about doing a LIKE against a date (although it CAN work in Oracle, it is incredibly bad practice). It should be, e.g.:
    $start_date = '2015-08-01'; $end_date = '2015-09-01'; ... WHERE date_column >= ? and date_column < ? ... $sth->execute($start_date, $end_date);
    Update: Fixed date fmt. Twice.
Re: Problem passing date to SQL
by GotToBTru (Prior) on Aug 18, 2015 at 15:10 UTC

    If the previous suggestion does not work, in order to make sure the module is interpreting your data as you intend, you might want to do something like:

    use DBI qw(:sql_types); ... $sth->bind_param( 1, $date, SQL_VARCHAR ); $sth->execute(); ...
    Dum Spiro Spero
      I was just reading up on bind_param and the different placeholders DBD::Oracle supports. I'll try this out and see if it works. :)
Re: Problem passing date to SQL
by kennethk (Abbot) on Aug 18, 2015 at 15:14 UTC
    I'm a little surprised that this SQL works since DATE is a reserved word, and you are treating dates like strings. But that aside, from DBD::Oracle:
    rows

    $rv = $sth->rows;

    Returns the number of rows affected for updates, deletes and inserts and -1 for selects.
    i.e. that's not how rows works. What happens when you run
    $sth->execute($date) or die "Couldn't get values from table: " . DBI-> +errstr; my @data; while (my @row = $sth->fetchrow_array) { @data = @row; } if (@data) { local $" = ','; print "@data\n"; } else { "0 results\n"; exit; }
    I also note in your posted code, you are calling fetchrow_array on $sth_1 and not $sth, which is the SQL you prepare. Please make sure code you post actually runs; perhaps are testing the wrong statement handle in your original code as well? How do I post a question effectively?

    #11929 First ask yourself `How would I do this without a computer?' Then have the computer do it the same way.

      I used "date" to keep things simple here. :) The actual statement looks like this:
      my $sth_1 = $dbh->prepare("SELECT sum(EMAIL_IRT),sum(DROP_CALL_COUNT), +sum(ANS_CALL_COUNT),sum(EMAILS_IN),sum(EMAILS_OUT) FROM CS_STATS WHER +E DATE_ADDED LIKE ?");
      Also, I removed the row->0 if statement completely but still have the same problem - it looks like nothing is returned. This is what I have now:
      my $sth_1 = $dbh->prepare("SELECT sum(EMAIL_IRT),sum(DROP_CALL_COUNT), +sum(ANS_CALL_COUNT),sum(EMAILS_IN),sum(EMAILS_OUT) FROM CS_STATS WHER +E DATE_ADDED LIKE ?"); $sth_1->execute($date_1) or die "Couldn't get values from DB: " . DBI- +>errstr; $sth_1->execute(); my ($email_irt,$drop_call_count,$ans_call_count,$emails_in,$emails_out +); while (my @data = $sth_1->fetchrow_array()) { $email_irt = $data[0]; $drop_call_count = $data[1]; $ans_call_count = $data[2]; $emails_in = $data[3]; $emails_out = $data[4]; } print "$email_irt,$drop_call_count,$ans_call_count,$emails_in,

      And this is the output:

      Use of uninitialized value $email_irt in concatenation (.) or string a +t monthly_stat_mailer.pl line 88. Use of uninitialized value $drop_call_count in concatenation (.) or st +ring at monthly_stat_mailer.pl line 88. Use of uninitialized value $ans_call_count in concatenation (.) or str +ing at monthly_stat_mailer.pl line 88. Use of uninitialized value $emails_in in concatenation (.) or string a +t monthly_stat_mailer.pl line 88. Use of uninitialized value $emails_out in concatenation (.) or string +at monthly_stat_mailer.pl line 88. ,,,,
        $sth_1->execute($date_1) or die "Couldn't get values from DB: " . DBI- +>errstr; $sth_1->execute();

        Do you really run execute twice?

        لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
        You did not run the code I posted, and you never actually test if any assignment happens before outputting. That's why I had an array scoped outside the while loop, and then tested it before getting into output. When I asked you to run specific code, it was to check what's coming back from the DB call.
        $sth_1->execute($date_1) or die "Couldn't get values from DB: " . DBI- +>errstr; $sth_1->execute();
        Is this your actual code? The second execute is clearing the cursor.

        Try updating your connector to

        my $dbh = DBI->connect( 'DBI:Oracle:SID', 'username', 'password', { PrintError => 0, RaiseError => 1, AutoCommit => 0, }, );
        This way, if this is a database error, the statement handles will all autodie on errors and print out exactly what went wrong. It'll also tell you if you've reset a cursor with a second execute.

        #11929 First ask yourself `How would I do this without a computer?' Then have the computer do it the same way.

        You're only posting part of your code to keep this simple, but it's getting in the way of us helping. See http://sscce.org.

        Dum Spiro Spero
Re: Problem passing date to SQL
by poj (Abbot) on Aug 18, 2015 at 17:57 UTC
    my $SQL = 'SELECT ID, DDATE FROM MYTEMP4 WHERE EXTRACT(MONTH FROM DDATE) = ? AND EXTRACT(YEAR FROM DDATE) = ?'; my $sth = $dbh->prepare($SQL); $sth->execute(8,2015); while (my @f = $sth->fetchrow_array){ print join " ",@f,"\n"; }

    Update : If you really want to use LIKE then

    my $SQL = "SELECT ID, DDATE FROM MYTEMP4 WHERE to_char(DDATE,'mm/dd/yyyy') LIKE ?"; my $sth = $dbh->prepare($SQL); $sth->execute('08/%/2015');
    poj
Re: Problem passing date to SQL
by SimonPratt (Friar) on Aug 18, 2015 at 15:51 UTC

    You're trying to perform a string comparison of the string '08/%/2015' with a date field, which is a number. This absolutely will not work in Microsoft SQL and I suspect (though can't test) that it won't work in Oracle either.

    A better solution is: SELECT ... WHERE DATE >= $startdate AND DATE <= $enddate

    UPDATE: Yeah, dunno who down-voted me, but there are some assumptions I made:

  • OP is using Oracle and checking monthly stats, so probably a professional environment
  • It then holds that OP probably has a DBA maintaining the DB
  • Following on from that, it is likely that the DB was properly designed / built
  • Given WHERE DATE LIKE ? won't work in Oracle, OPs statement about running the exact same SQL is blatantly untrue
      Although a "WHERE date_column LIKE '...'" clause CAN work in Oracle, it is incredibly poor practice. You can not depend on the date format in any particular environment, and any indexes on the date column will not be used.

        Aah, that does work in Oracle? I stand corrected :)

Re: Problem passing date to SQL
by Hermano23 (Beadle) on Aug 18, 2015 at 15:02 UTC
    You may need to escape your percentage sign in $date, like so:

    my $date = '08/\%/2015';
      Thanks. I just tried but still have the same problem. :( Also, since I'm using single quotes do I need to escape the % sign?

        I am working in mysql. If i want previous month data i write code like this. Dont know whether works in sql

        $month=`date --date='1 month ago' +%B`; . . . my $sth = $dbh->prepare("SELECT * from $table where monthname(Date)='$ +month'" ); . . .