How to handle a Perl array the correct way? - perl

I'm quite new in writing Perl code, so I'm hoping for some help here.
I'm querying a database, which works as long as I'm just selecting one row and write the queried variables into some variables in another database.
As soon as I want to create an array and do with multiple variables, what I am able to do with a single one, my code doesn't work.
Do you spot the mistake?
use strict;
use warnings;
use DBI;
my $host = "host";
my $database = "DBI:mysql:database";
my $tablename = "table";
my $user = "user";
my $pw = "password";
my $sql = "SELECT COUNT(`attribute`) FROM `table` WHERE `attribute` =".GETVALUE("attribute");
my $connect = DBI->connect($database, $user, $pw) or die "Couldn't connect to database: " . DBI->errstr;
my $sth = $connect->prepare($sql);
$sth->execute();
my $row;
my $sql2 = "SELECT `attribute2`,`name`, `adress` FROM `table` WHERE `attribute` =".GETVALUE("attribute");
my $sth2 = $connect->prepare($sql2);
$sth2->execute();
my(#attribute2, #name, #adress);
while (($row) = $sth->fetchrow()){
for (my $i = 0; $i < $row; $i++)
{
SETVALUE("Variable".$i , my $name->[$i].", ". my $adress->[$i]);
}
SETVALUE("CountAttribute", $row);
}
`
I already tried to exchange "fetchrow" with "fetchrow_array" and "fetchrow_arrayref", but this didnt work either.

Accepting the comment that fetchrow() is an undocumented alias for fetchrow_array():
The fetchrow_array() method returns the contents of the row, not an index. Assuming what you want to do is call SETVALUE() with each name and address, and then one last time with the number of rows fetched, your while() loop should be something along the lines of:
my $i = 0;
while ( my ( $attr2, $name, $addr ) = $sth->fetchrow_array() ) {
SETVALUE( "Value$i", "$name, $addr" );
$i++;
}
SETVALUE( "CountAttribute", $i );

Related

How to create Hash with single key and multiple values?

I have the database which I am accessing, my goal is to store every row in hash where id would be key and other things present in the row i.e. other columns as values.
Actually I am parsing the XLSX sheet and storing its values, after that have to apply logic to fetch database values and later on compare values of XLSX sheet and DB, if they are equal then test is pass else fail. I almost spent 3 days but not getting any results.
Output
#carrierValuesAll[0] = 1 1 https://au-sbc.trustidinc.com/tid sbcLabStub sbcLab SKY 0 2019-11-07 20:10:43 2021-07-02 04:39:43 TrustID Lab Oracle Y Y Y ivr.localdomain Y trustid
#carrierValuesAll1 = 2 1 https://au-sbc.trustidinc.com/tid sbcLab sbcLab SKY 2 2019-11-07 20:10:43 2020-12-14 06:24:17 TrustID Lab Oracle Y Y Y ivr.localdomain Y admin
Desired Output
Hash --> Key = 1Values = 1,https://au-sbc.trustidinc.com/tid,sbcLabStub,sbcLab,SKY,0,2019-11-07,20:10:43,2021-07-02,04:39:43, TrustID, Lab Oracle, Y, Y, Y, ivr.localdomain, Y, trustidSimilarly for other iterations or id's in loop where id 2 will be key 2 and its values are key 2 values.Table looks likeThe code I am using for this is below:
#!/usr/bin/perl
use DBI;
use strict;
use warnings;
use Switch;
my #tstInfo;
my %DbHash;
my #carrierValuesAll=();
my #carrierValuesAllGet="";
if($dbType eq "mysql"){
#$database = "trustid";
$dsn = "DBI:$dbType:database=$database;host=$host:port=3306,DBSOCK = '/var/run/mysqld/mysqld.sock'";
$userid = "XXXX";
$password = 'XXXX';
}else{
$dbType="postgres";
$userid = "postgres";
$password = "postgres";
$dsn = "DBI:$dbType:database=$database;host=$host:port=5432";
}
my $dbcon = DBI->connect($dsn, $userid, $password) or die "Can't connect to database: $DBI::errstr\n";
print "connected to the database\n";
my $notables = $dbcon->tables();
print "No of tables : $notables"."\n" ;
my #tables = $dbcon->tables(undef, undef, undef, 'TABLE');
my $len = #tables;
for (my $i = 0; $i < $len; $i = $i + 1)
{
print ("\#tables[$i] = $tables[$i]\n");
print("\n");
}
my $sth = $dbcon->prepare('select * from active_table_set');
$sth->execute();
while (my #row = $sth->fetchrow_array()) {
$database=join(',', #row);
print($database."\n");
if($dbType eq "mysql"){
#$database = "trustid";
$dsn = "DBI:$dbType:database=$database;host=$host:port=3306,DBSOCK = '/var/run/mysqld/mysqld.sock'";
$userid = "XXXX";
$password = 'XXXX';
}else{
$dbType="postgres";
$userid = "postgres";
$password = "postgres";
$dsn = "DBI:$dbType:database=$database;host=$host:port=5432";
}
$dbcon = DBI->connect($dsn, $userid, $password) or die "Can't connect to database: $DBI::errstr\n";
print "connected to the database\n";
#tables = $dbcon->tables(undef, undef, undef, 'TABLE');
$len = #tables;
for (my $i = 0; $i < $len; $i = $i + 1)
{
print ("\#tables[$i] = $tables[$i]\n");
print("\n");
if($tables[$i] eq '`trustid_b`.`carriers`'){
$sth = $dbcon->prepare('select * from carriers');
$sth->execute();
while (my #row = $sth->fetchrow_array()) {
push(#carrierValuesAll, [ #row ]);
}
}
}
}
$len = #carrierValuesAll;
for (my $i = 0; $i < $len; $i = $i + 1) {
print ("\#carrierValuesAll[$i] = #{$carrierValuesAll[$i]}\n");
print("\n");
push(#tstInfo,\#carrierValuesAll[$i]);
}
for(my $i = 0; $i <= $#tstInfo; $i++){
# push the test data on the array
$DbHash{$i+1} = $tstInfo[$i];
}
print(%DbHash).("\n");
Can I create the Hash Here
while (my #row = $sth->fetchrow_array()) {
push(#carrierValuesAll, [ #row ]);
}
Yes, Then How?
What I was doingStoring each row in scalar which was giving me output as below: Where, I was trying to push my each row in #carrierValuesAll and then pushing #carrierValuesAll in #tstInfo and trying to populate hash after that. I don't know how to access this, Whether this is correct or not. Please help me.
I have the database which I am accessing, my goal is to store every row in hash where id would be key and other things present in the row i.e. other columns as values
That's probably not what you want. What you actually want is a two-level hash, where the first level has the id as a key and a hash reference as the value. The second level hash would have column names as the keys mapping to the associated values.
One easy way to do that is to use fetchrow_hashref() instead of fetchrow_array().
my %carriers;
while (my $row = $sth->fetchrow_hashref) {
$carriers{$row->{id}} = $row;
}

Perl won't execute SQL statement in a while loop

I wrote this piece of code: it is supposed to keep checking names until it finds one that does not exist, and then it should go on.
while ($repeating == 1) {
$new_name = $i . "_" . $file;
my $sql= "SELECT file_name FROM PDFdocument WHERE user_id = '$id' AND file_name = '$new_name' ";
my $sth = $dbh->prepare($sql);
$sth->execute();
while (my #row = $sth->fetchrow_array) {
//never enters here
if ($new_name ne $row[0]) {
$repeating = 0;
}
}
$i++;
}
It never enters the second while loop, so it gets stuck in this repeating loop. I don't know why it does not work; I do some other sql statements before and they all work. This is the only one that does not work.
Any help?
The problem is that you won't get any rows back if the name does not exist. The solution is to just check if you do get any rows - otherwise the file name mus be unused. BTW, let DBI escape stuff you sent to the database. This should work:
while ($repeating == 1) {
$new_name = $i . "_" . $file;
# the question marks are placeholders
my $sql= "SELECT file_name FROM PDFdocument WHERE user_id = ? AND file_name = ? ";
my $sth = $dbh->prepare($sql);
# filling the placeholders while executing
$sth->execute($id, $new_name);
if(!$sth->fetch) {
# no rows found? this name must be fresh
$repeating = 0;
}
$i++;
}
Edit: As #ikegami mentioned in a comment, the behavior of $sth->rows depends on the driver, so it might return different values for different database engines when dealing with SELECT statements (see also the DBI docs. Asking the driver to fetch a row should work the same on all drivers.
Keep in mind that this is susceptible to race conditions, i.e. if two scripts run at the same time, they might both chose the same "unused" filename. Make sure you're using some kind of locking mechanism to avoid that.
When you finally find the $i you should use, $sth->fetchrow_array returns an empty list, so = returns 0, so the loop isn't entered.
Solution 1:
my $new_name;
for (my $i=1; ; ++$i) {
$new_name = $i . "_" . $file;
$dbh->selectrow_arrayref(
"SELECT 1 FROM `PDFdocument` WHERE `user_id` = ? AND `file_name` = ?",
undef,
$id, $new_name,
)
and last;
}
Solution 2:
my $i = $dbh->selectrow_array(
"
SELECT CAST(LEFT(`file_name`, LOCATE("_", `file_name`)-1) AS INT) AS `i`
FROM `PDFdocument`
WHERE `user_id` = ?
AND `file_name` LIKE ?
ORDER BY DESC `i`
LIMIT 1
",
undef,
$id, "%\\_\Q$file\E"
);
++$i;
my $new_name = $i . "_" . $file;
Note the use of placeholders. Your buggy way of building the SQL statement leaves you vulnerable to malfunctions if not attacks.

display value in browser in perl cgi

I am able to fetch a data from db not able to display in browser.
below is the code-
my $q = CGI->new;
print $q->header,$q->start_html('testing');
my $title = $q->param('title');
my $perl = "";
#these is displayed properly
print "<font color=blue><b>TITLE:\"$title\"</b><br>";
print "<font color=blue><b>SCRIPT:\"$title\"</b>\n";
my $dbh = DBI->connect("DBI:ODBC:test","username","password") || die "Connection error: $DBI::errstr\n";
my $sql = "select * from tablename where title = '$title'";
my $sth = $dbh->prepare($sql);
$sth->execute;
my #row = $sth->fetchrow_array;
for(my $i=1;$i<=#row;$i++)
{
if($i == 5)
{
$perl = "$row[$i]";
}
}
#below is not displayed in browser
print $q->strong($title);
print $q->strong($perl);
$sth->finish();
$dbh->disconnect;
print $q->end_html;
I just want to print the value of $title and $perl in browser.
this program is running properly but cant able to display value of $title and $perl
The reason for the failure is not obvious to me, but you should use placeholders when performing queries:
my $sql = "select * from tablename where title = ?"; # placeholder
my $sth = $dbh->prepare($sql);
$sth->execute($sql); # $sql is used here
The placeholder is a question mark ?. This will ensure that your values are quoted properly, and prevent injection attacks. Using the data from the CGI object without sanitizing it is very dangerous.
Also, it seems that you are only taking one value from the array, so there is little need to use a loop in the first place. You could just do:
my $row = $row[5];
To see if the value was in the database, you can use if (defined $row), or if (#row >= 6). (Note that arrays start at 0, so the element with index 5 is actually the 6th element. Just pointing this out since you started your loop at 1.)
Try running it straight from the command line, without the browser.
See here and here.
You can also use the Perl debugger, if you start it with:
perl -d yourprogram

How can I check if a database query will return results?

Our website uses Perl to provide a simple mechanism for our HR people to post vacancies to our website. It was developed by a third party, but they have been long since kicked into touch, and sadly we do not have any Perl skills in-house. This is what happens when Marketing people circumvent their in-house IT team!
I need to make a simple change to this application. Currently, the vacancies page says 'We currently have the following vacancies:', regardless of whether there are any vacancies! So we want to change it so that this line is only displayed at the appropriate times.
I could, obviously, start to learn a bit of Perl, but we are already planning a replacement site, and it certainly won't be using Perl. So since the solution will be trivial for those with these skills, I thought I'd ask for some focused help.
Below is the start of the procedure that lists the vacancies.
sub list {
require HTTP::Date;
import HTTP::Date;
my $date = [split /\s+/, HTTP::Date::time2iso(time())]->[0];
my $dbh = DBI->connect($dsn, $user, $password)
|| die "cannot connect to $database: $!\n";
my $sql = <<EOSQL;
SELECT * FROM $table where expiry >= '$date' order by expiry
EOSQL
my $sth = $dbh->prepare($sql);
$sth->execute();
while (my $ref = $sth->fetchrow_hashref()) {
my $temp = $template;
$temp =~ s#__TITLE__#$ref->{'title'}#;
my $job_spec = $ref->{'job_spec'};
...etc...
The key line is while (my $ref = $sth->fetchrow_hashref()) {. I'm figuring that this is saying 'while I can pull off another vacancy from the returned recordset...'. If I place my print statement before this line, it will always be shown; after this line and it was be repeated for every vacancy.
How do I determine that there are some vacancies to be displayed, without prematurely moving through the returned recordset?
I could always copy the code within the while loop, and place it within an if() statement (preceding the while loop) which will also include my print statement. But I'd prefer to just have the simpler approach of If any records then print "We currently have.." line. Unfortunately, I haven't a clue to code even this simple line.
See, I told you it was a trivial problem, even considering my fumbled explanation!
TIA
Chris
A really simple way would be:
$sth->execute();
my $first = 1;
while (my $ref = $sth->fetchrow_hashref()) {
if( $first ) {
print "We currently have the following vacancies:\n";
$first = 0;
}
my $temp = $template;
...
}
if( $first ) {
print "No vacancies found\n";
}
If you are using Mysql, the "rows" method works just fine:
$sth->execute();
if($sth->rows) {
print "We have data!\n";
}
while(my $ref = $sth->fetchrow_hashref()) {
...
}
The method, and some caveats, are documented in extensive detail in "perldoc DBI". Always start with "perldoc".
This isn't so much a Perl question as it's a database question, and there is no good way to know how many results you have until you have them. You've got two choices here:
Do a query that does a "select count(*)" to see how many rows there are, and then another query to get the actual rows or
Do the query and store the results into a hash, then count how many entries you have in the hash, and then go through the hash and print out the results.
For example, off the top of my head:
my #results = ();
while (my $ref = $sth->fetchrow_hashref()) {
push #results, $ref;
}
if ($#results == 0) {
... no results
} else {
foreach $ref (#results) {
my $temp = $template;
....
}
Since everyone wants to optimize away the repeated tests for whether the header has been printed in Graeme's solution, I present this minor variation on it:
$sth->execute();
my $ref = $sth->fetchrow_hashref();
if ($ref) {
print "We currently have the following vacancies:\n";
while ($ref) {
my $temp = $template;
...
$ref = $sth->fetchrow_hashref();
}
} else {
print "No vacancies found\n";
}
Since your query is a SELECT, you cannot take advantage of rows or of the value returned by the execute itself.
However, you can pre-count how many rows (i.e. vacancies) your query will select by adding another query... something like this:
# Retrieve how many vacancies are currently offered:
my $query = "SELECT COUNT(*) AS rows FROM $table WHERE expiry >= ?";
$sth = $dbh->prepare($query);
$sth->execute($date);
$numVacancies = $numinfo->fetchrow_arrayref()->[0];
# Debug:
print "Number of vacancies: " . $numVacancies . "\n";
if ( $numVacancies == 0 ) { # no vacancy found...
print "No vacancies found!\n";
}
else { # at least a vacancy has been found...
print "We currently have the following vacancies:\n";
# Retrieve the vacancies:
my $sql = "SELECT * FROM $table where expiry >= '$date' ORDER BY expiry";
my $sth = $dbh->prepare($sql);
$sth->execute();
...
}
Or, similarly, instead of "prepare" and "execute" the query and then use "fetchrow_array", you can do everything in a single call using selectrow_array:
# Retrieve how many vacancies are currently offered:
my $query = "SELECT COUNT(*) AS rows FROM $table WHERE expiry >= ?";
my $numVacancies = $dbh->selectrow_array($query, undef, $date);
# Debug:
print "Number of vacancies: " . $numVacancies . "\n";
And the same is also true for selectall_arrayref:
# Retrieve how many vacancies are currently offered:
my $query = "SELECT COUNT(*) AS rows FROM $table WHERE expiry >= ?";
my $numVacancies = $dbh->selectall_arrayref($query, {Slice => {}}, $date);
# Debug:
print "Number of vacancies: " . #$numVacancies[0]->{rows} . "\n";
However, if you use selectrow_array or selectall_arrayref, you can also retrieve the number of vacancies directly from the result of the original query:
# Retrieve the vacancies:
my $sql = "SELECT * FROM $table where expiry >= ? ORDER BY expiry";
my $vacancies = $dbh->selectall_arrayref($sql, {Slice => {}}, $date);
# Debug:
print "Number of vacancies: " . scalar #{$vacancies} . "\n";
A bit more efficient way (avoiding a conditional inside the loop), if you don't mind it changing the way the page is output a bit (all at once rather than a row at a time) you could make a variable to hold the output just before the loop:
my $output = '';
and then inside the loop, change any print statement to look like this:
$output .= "whatever we would have printed";
then after the loop:
if ($output eq '')
{
print 'We have no vacancies.';
}
else
{
print "We currently have the following vacancies:\n" . $output;
}
Just add another query.. something like this:
# count the vacancies
$numinfo = $dbh->prepare("SELECT COUNT(*) FROM $table WHERE EXPIRY >= ?");
$numinfo->execute($date);
$count = $numinfo->fetchrow_arrayref()->[0];
# print a message
my $msg = '';
if ($count == 0) $msg = 'We do not have any vacancies right now';
else $msg = 'We have the following vacancies';
print($msg);
use Lingua::EN::Inflect 'PL';
$sth->execute();
my $results = $sth->fetchall_arrayref( {}, $max_rows );
if (#$results) {
print "We currently have the following ", PL("vacancy",scalar #$results), ":\n";
for my $ref (#$results) {
...
}
}
Says perldoc DBI:
For a non-"SELECT" statement, "execute" returns the number of rows
affected, if known. If no rows were affected, then "execute"
returns "0E0", which Perl will treat as 0 but will regard as true.
So the answer is to check the return value of $sth->execute():
my $returnval = $sth->execute;
if (defined $returnval && $returnval == 0) {
carp "Query executed successfully but returned nothing";
return;
}

How can I translate date values from Oracle to Excel using Perl's DBI?

I am having trouble with a very simple Perl process. I am basically querying an Oracle database and I want to load it into Excel. I have been able to use DBIx::Dump and it works. However, I need to be able to use a variety of Excel formatting tools. And I think Spreadsheet::WriteExcel is the best module that outputs to Excel that allows me do more formatting.
Below is the code and the error I am getting. I basically query Oracle, fetch the data, load into an array and try to write to Excel. For some reason it is doing some kind of comparison and it does not like the data types. For example, the date is '25-OCT-08'. The SVP is 'S01'. It seems to be saying that they are not numeric.
Error:
Argument "01-NOV-08" isn't numeric in numeric ge <>=> at C:/Perl/site/lib/Spreadsheet/WriteExcel/Worksheet.pm line 3414.
Argument "01-NOV-08" isn't numeric in pack ge <>=> ge <>=> at C:/Perl/site/lib/Spreadsheet/WriteExcel/Worksheet.pm line 2157.
Code:
#!/usr/bin/perl -w
#Set the Perl Modules
use strict;
use DBI;
use Spreadsheet::WriteExcel;
# Connect to the oracle database
my $dbh = DBI->connect( 'dbi:Oracle:xxxx',
'xxxx',
'xxxx',
) || die "Database connection not made: $DBI::errstr";
#Set up Query
my $stmt = "select
week_end_date, SVP, RD,
DM, store, wtd_smrr_gain,QTD_SMRR_GAIN,
wtd_bor_gain,QTD_BOR_GAIN,
wtd_cust_gain,QTD_CUST_GAIN,
wtd_CARD_CLOSED_OCT25,QTD_AVG_CARD_CL
from
bonus_4Q_store
order by
store";
#Prepare Query
my $sth = $dbh->prepare($stmt);
#Execute Query
$sth->execute() or die $dbh->errstr;
my( $week_end_date,$SVP,$RD,$DM,$store,
$wtd_smrr_gain,$QTD_SMRR_GAIN,
$wtd_bor_gain,$QTD_BOR_GAIN,
$wtd_cust_gain,$QTD_CUST_GAIN,
$wtd_CARD_CLOSED_OCT25,$QTD_AVG_CARD_CL);
#binds each column to a scalar reference
$sth->bind_columns(undef,\$week_end_date,\$SVP,\$RD,\$DM,\$store,
\$wtd_smrr_gain,\$QTD_SMRR_GAIN,
\$wtd_bor_gain,\$QTD_BOR_GAIN,
\$wtd_cust_gain,\$QTD_CUST_GAIN,
\$wtd_CARD_CLOSED_OCT25,\$QTD_AVG_CARD_CL,);
#create a new instance
my $Excelfile = "/Test_Report.xls";
my $excel = Spreadsheet::WriteExcel->new("$Excelfile");
my $worksheet = $excel->addworksheet("WOW_SHEET");
#Create array shell
my #data;
#Call data and Write to Excel
while ( #data = $sth->fetchrow_array()){
my $week_end_date = $data[0];
my $SVP = $data[1];
my $RD = $data[2];
my $DM = $data[3];
my $store = $data[1];
my $wtd_smrr_gain = $data[2];
my $QTD_SMRR_GAIN = $data[3];
my $wtd_bor_gain = $data[4];
my $QTD_BOR_GAIN = $data[5];
my $wtd_cust_gain = $data[6];
my $QTD_CUST_GAIN = $data[7];
my $wtd_CARD_CLOSED_OCT25 = $data[8];
my $QTD_AVG_CARD_CL = $data[9];
my $row = 0;
my $col = 0;
foreach my $stmt (#data)
{
$worksheet->write($row++, #data);
last;
}
}
print "DONE \n";
$sth->finish();
$dbh->disconnect();
The problem is here:
foreach my $stmt (#data)
{
$worksheet->write($row++, #data); # !!
last;
}
The correct syntax for write() is:
write($row, $column, $token, $format)
You are missing the $column argument, which in this case is probably 0.
If $stmt is an array ref then you can write it in one go as follows:
$worksheet->write($row++, 0, $stmt);
I would guess that it is coming out as a string, and when you try to insert it into the date column, there is no implicit conversion for it.
Try selecting the date like this, and it will turn it into a char that you can use to do compares.
to_char(date, 'YYYY/MM/DD HH24:MI:SS')
then
to_date(date, 'YYYY/MM/DD HH24:MI:SS')
to convert it back to a date on insert. That is generally what you need to do in SQL.
As I recall, perl has a trace facility for DBI that might giver a better picture as to what is going on.