Perl - Hash of Arrays across Module - arrays

I am a bit new to Perl and I need some help regarding moving my Hash of Arrays across Modules.
Currently I have a db module that stores an array like so:
sub getSourceCriteria {
my($self) = shift();
my($sourceId) = shift();
chomp $sourceId;
my(%criteria) =();
$logger->debug("Getting records for Source ID: " . $sourceId);
$dbh=DBI->connect('dbi:ODBC:StkSkrnDB', 'RTETET', 'XXuser01',{ RaiseError => 1, AutoCommit => 0 }) || \
$logger->err_die("Database connection not made: $DBI::errstr\n");
my($sth) = "select a.criteria_id, a.criteria_type, a.criteria_props,a.generalcriteria_id,b.field_id ";
$sth = $sth . "from t_criteria a, t_sourceMapping b where a.generalcriteria_id = (select generalcriteria_id from t_sourcecriteria where source_id =?) ";
$sth = $sth . "and a.criteria_id=b.criteria_id";
my($qry) = $dbh->prepare($sth);
$qry->execute($sourceId) || $logger->error("Could not query for Source Criteria: $DBI::errstr\n");
my(#row)=();
my($tempCount) = 0;
while ( #row = $qry->fetchrow_array ) {
$tempCount = scalar #row;
$logger->debug("Size of retrieved SQL Array : $tempCount");
$criteria{$row[0]} = \#row;
###{$criteria{$row[0]} } = \#row;
}
return %criteria;
}
And I have a seperate perl script that reads the SQL output from the code above:
foreach my $criteria (keys %criterias) {
#temp = exists( $criterias{$criteria} ) ? #{ $criterias{$criteria} } : ();
##my $tempStr = exists( $criterias{$criteria} ) ? "Yes" : "No";
$arraySize = scalar #temp;
$logger->debug("GENERALCRITERIA_ID is $GENERALCRITERIA_ID and size of array is $arraySize and $temp[0]");
$genCrit_ID = $temp[$GENERALCRITERIA_ID];
$logger->debug("Criteria ID $criteria has Gen Criteria ID $genCrit_ID");
if (0!=$generalCriteria_ID || $generalCriteria_ID != $genCrit_ID ) { ## test for uniqueness
$generalCriteria_ID = -1;
}
else {
$generalCriteria_ID = $genCrit_ID;
}
}# do something with $key and $value
$generalCriteria = $generalCriteria_ID;
}
The problem is I keep getting 0 as the retrieved array size( 2nd snippet) even though when I store the array ( in the 1st snippet ) I check and get the actual array size.
Please any help/clarification would be greatly appreciated.
EDIT
Added more code in the DB interface code.

In your while loop, you are assigning to #row and then storing a reference to that array. However, each time the loop iterates, you are replacing the contents of #row without declaring a new array. So at the end, each of your references point towards the same thing.
In your code here:
my(#row)=();
my($tempCount) = 0;
while ( #row = $qry->fetchrow_array ) {
$tempCount = scalar #row;
$logger->debug("Size of retrieved SQL Array : $tempCount");
$criteria{$row[0]} = \#row;
###{$criteria{$row[0]} } = \#row;
}
Each time the while loop iterates, you assign new values to the #row array. But since the my(#row)=(); line occurs outside of the loop, the #row array is always the same. So each time you assign to the array, you are changing what is stored in all of the references you have already taken.
To fix the problem, you need to declare a new array for each iteration. The simplest way to do this is to move the declaration into the while condition:
my($tempCount) = 0;
while ( my #row = $qry->fetchrow_array ) {
$tempCount = scalar #row;
$logger->debug("Size of retrieved SQL Array : $tempCount");
$criteria{$row[0]} = \#row;
###{$criteria{$row[0]} } = \#row;
}
Now each time you take the reference \#row you will be getting a reference to a new array.
If your $qry->fetchrow_array method returned an array reference, you would not have had the issue:
my $row;
while ($row = $qry->fetchrow_array) {
$logger->debug("Size of retrieved SQL Array : ".#$row);
$criteria{$$row[0]} = $row; # already a reference
}
But I would still write that as while (my $row = ... in my own code, since keeping scopes small is a good thing.

Related

perl DBI execute doesn't recognize '?'

I have this code:
if($update[$entity_id])
{
my $sql = "UPDATE cache SET date = '?', value = '?' WHERE item_id = ? AND level = ? AND type = ?;";
}
else
{
my $sql = "INSERT INTO cache (date, value, item_id, level, type) VALUES ('?','?',?,?,?);";
}
my $db = $self->{dbh}->prepare(q{$sql}) or die ("unable to prepare");
$db->execute(time2str("%Y-%m-%d %X", time), $stored, $entity_id, 'entity', 'variance');
But when it want to run the update I get this error:
DBD::Pg::st execute failed : called with 5 bind variables when 0 are needed.
Why?
If you had turned on strict and/or warnings, you would see what your problem is.
You're writing
if (...) {
my $sql = ...;
} else {
my $sql = ...;
}
execute($sql);
Which means that the $sql variables that you declare in the if branches aren't in scope and you're trying to execute completely empty SQL.
You're preparing literal '$sql', but that is not your only problem, lexical $sql variables go out of scope outside {}.
Try,
use strict;
use warnings;
#...
my $sql;
if($update[$entity_id])
{
$sql = "UPDATE cache SET date = ?, value = ? WHERE item_id = ? AND level = ? AND type = ?";
}
else
{
$sql = "INSERT INTO cache (date, value, item_id, level, type) VALUES (?,?,?,?,?)";
}
my $st = $self->{dbh}->prepare($sql) or die ("unable to prepare");
$st->execute(time2str("%Y-%m-%d %X", time), $stored, $entity_id, 'entity', 'variance');

Save check constraint values to variables

I get a table check constraint definition this way:
select a.CHECK_CLAUSE
from INFORMATION_SCHEMA.CHECK_CONSTRAINTS a,INFORMATION_SCHEMA.TABLE_CONSTRAINTS b
where b.TABLE_NAME = 'table name'
For my example, suppose running this query returns this:
[([depname]='mathematics' OR [depname]='electronics' OR [depname]='computer science')]
How do I assign the values ​​specified in the check constraint into variables? i.e. computer science, mathematics and electronics?
It looks like you're getting a string returned. What you can do is split the string on instances of OR and store that in an array, and then run through the array and split each element on = to isolate the values. So, if you were to do this in PHP, the code might look something like this:
// For reasons of simplicity we will assume the result is stored in $result,
// and the leading [( and trailing )] have already been removed
$values = array();
$resultsplit = explode(' OR ', $result);
/* $resultsplit is now an array:
* $resultsplit[0] = "[depname]='mathematics'"
* $resultsplit[1] = "[depname]='electronics'"
* $resultsplit[2] = "[depname]='computer science'"
*/
if ($result != '') {
foreach ($resultsplit as $rs) {
$rsparts = explode('=', $rs);
/* $rsparts is now an array. On the first element:
* $rsparts[0] = "[depname]"
* $rsparts[1] = "'mathematics'"
* So all we need to do is stick $rsparts[1] into $values
*/
$values[] = $rsparts[1];
}
}
This will put all of the values into the array $values (including the single-quotes at the beginning and end) for you to do with as you please, regardless of how many there are. If PHP is not the language you have available to you, the same method should still work in your language of choice.

add key=>value pair to associative array in php While loop

I create a simple associative array in a WHILE loop. The key is the field name and the value is a Unix timestamp. I would like to add to this array a new key=>value pair 'Date"=>format the Unix timestamp, json_encode the array and return it to a JQ script in an Ajax call. The array looks like this:
Array
( [0] => Array
( [Post_timestamp] => 1370876787 ) [Date] => 2013 06 10 )
However, shouldn't it look like this:
Array
( [0] => Array ( [Post_timestamp] => 1370876787 [Date] => 2013 06 10))
I guess my question is "how do I create the array so that the formatted timestamp and the raw timestamp are a single record"? Right now, it appears as if they are two records.
PHP
$query = "SELECT Post_timestamp FROM Comments LIMIT 1";
$result = mysqli_query($dbc, $query);
while ($rows = mysqli_fetch_assoc($result)) {
$array[] = $rows;
$array['Date'] = date("Y m d", $rows['Post_timestamp']);
}
The problem is you have two different values into the array, what you need to do is push an array that contains both values. This should get you what you want.
$query = "SELECT Post_timestamp FROM Comments LIMIT 1";
$result=mysqli_query($dbc,$query);
while ($rows = mysqli_fetch_assoc($result)) {
$rows["Date"] = date("Y m d",$rows['Post_timestamp']);
$array[] = $rows;
}

How to store the current value of the array in a variable inside a Perl for loop

My requirement is, I want to map some checklist values to some groups. The following is my code:
#selectbox1 => contains the selected select groups
#selectbox2 => contains selected checklist
Code:
foreach $select1(#selectbox1) {
my $sql_select1 = "select id from group_management where group_name = '$select1'";
my $box1 = $dbslave -> prepare($sql_select1);
$box1 -> execute();
while($select_box1= $box1->fetchrow_array())
{
push (#box1,$select_box1);
}
my $box_1 = #box1; # currently I tried like this to store the current value .NEED CORRECTION HERE
foreach $select2(#selectbox2) {
my $sql_select2 = "select id from checklist where checklist_name = '$select2'";
my $box2 = $dbslave -> prepare($sql_select2);
$box2 -> execute();
while($select_box2 = $box2->fetchrow_array())
{
push (#box2,$select_box2);
}
my $box_2 = #box2; # currently I tried like this to store the current value .NEED CORRECTION HERE
my $sql_insert = "insert into checklist_group_mapping values ('',$box_2,$box_1)";
my $ins = $dbslave -> prepare($sql_insert);
$ins -> execute();
}
}
How can I assign the current value of the array to a variable so that I can insert it into the mapping table?
You need to read up on 'context', and in particular 'scalar context' and 'array context'.
When you write:
my $box_1 = #box1;
you are providing scalar context, and in scalar context, #box1 returns the number of elements in the array. If you wrote:
my($box_1) = #box1;
you would be providing array context, and in array context, the first element of #box1 would be assigned to the first element of the array context, $box_1 — and the remaining elements of #box1 would be dropped. (This may well be what you're after; it is likely that you are trying to select the single ID value for each of the various names in #selectbox1.)
Judging from how you're trying to use the $box_1 and $box_2 variables in your code, you are looking to obtain a single string containing all the values from #box1 and another single string containing all the values from #box2, and they probably need to be presented to the DBI driver enclosed in single quotes.
You can get space-separated values into a string using:
my $box_1 = "#box1";
If you need comma-separated values, you can use:
my $box_1;
{ local $" = ","; $box_1 = "#box_1"; }
The $" (aka $LIST_SEPARATOR under use English '-no_match_vars';) must be localized to prevent damage, but that means you have to separate the definition of $box_1 from the assignment (because if you don't, $box_1 is destroyed when you leave the {...} block).
Now, to protect that so that the SQL can work, you need to use the quote method:
$box1 = $dbslave->quote($box1);
or:
my $box1 = $dbslave->quote("#box1");
Assembling these changes, we get:
#!/usr/bin/env perl
use strict;
use warnings;
### Improved, but not operational
# use DBI;
my #selectbox1 = ( "group1", "group2", "group3" );
my #selectbox2 = ( "check1", "check2", "check3" );
my $dbslave;
# $dbslave = DBI->connect(...) or die "A horrible death";
foreach my $select1 (#selectbox1)
{
my $sql_select1 = "select id from group_management where group_name = '$select1'";
my $box1 = $dbslave->prepare($sql_select1);
$box1->execute();
my #box1;
while (my $select_box1 = $box1->fetchrow_array())
{
push #box1, $select_box1;
}
my $box_1 = $dbslave->quote("#box1");
foreach my $select2(#selectbox2)
{
my $sql_select2 = "select id from checklist where checklist_name = '$select2'";
my $box2 = $dbslave->prepare($sql_select2);
$box2->execute();
my #box2;
while (my $select_box2 = $box2->fetchrow_array())
{
push #box2, $select_box2;
}
my $box_2 = $dbslave->quote("#box2");
my $sql_insert = "insert into checklist_group_mapping values ('', $box_2, $box_1)";
my $ins = $dbslave->prepare($sql_insert);
$ins->execute();
}
}
Note that the two SELECT statements assume that the select box strings contain no funny characters (specifically, no single quotes). If you're in charge of the content of #selectbox1 and #selectbox2, that's OK. If they contain user input, you have to sanitize that input, or use $dbslave->quote() again, or use place-holders. I'm going to ignore the issue.
You are also using scalar context with $box1->fetchrow_array(), which is not going to yield the answer you want (although fetchrow_array() is context sensitive, the manual warns you to be careful). I would use something like:
my #box1;
while (my #row = $box1->fetchrow_array())
{
push #box1, $row[0];
}
my $box_1 = $dbslave->quote("#box1");
You also need to use functions. There's a glaring repeat in your code that can be encapsulated into a single function used twice:
#!/usr/bin/perl
use strict;
use warnings;
# use DBI;
my #selectbox1 = ( "group1", "group2", "group3" );
my #selectbox2 = ( "check1", "check2", "check3" );
my $dbslave;
# $dbslave = DBI->connect(...) or die "A horrible death";
sub fetch_all
{
my($dbh, $sql) = #_;
my $sth = $dbh->prepare($sql);
$sth->execute();
my #results;
while (my #row = $sth->fetchrow_array())
{
push #results, $row[0];
}
my $result = $dbslave->quote("#results");
return $result;
}
foreach my $select1 (#selectbox1)
{
my $sql_select1 = "select id from group_management where group_name = '$select1'";
my $box_1 = fetch_all($dbslave, $sql_select1);
foreach my $select2(#selectbox2)
{
my $sql_select2 = "select id from checklist where checklist_name = '$select2'";
my $box_2 = fetch_all($dbslave, $sql_select2);
my $sql_insert = "insert into checklist_group_mapping values ('', $box_2, $box_1)";
my $ins = $dbslave->prepare($sql_insert);
$ins->execute();
}
}
The INSERT statement should be converted to use placeholders so it can be prepared once and used many times:
my $sql_insert = "insert into checklist_group_mapping values ('', ?, ?)";
my $ins = $dbslave->prepare($sql_insert);
foreach my $select1 (#selectbox1)
{
my $sql_select1 = "select id from group_management where group_name = '$select1'";
my $box_1 = fetch_all($dbslave, $sql_select1);
foreach my $select2(#selectbox2)
{
my $sql_select2 = "select id from checklist where checklist_name = '$select2'";
my $box_2 = fetch_all($dbslave, $sql_select2);
$ins->execute($box_1, $box_2);
}
}
Indeed, the two SELECT statements should also be parameterized and prepared once and reused. I've not shown that change because (a) I'm lazy and (b) there's a bigger change that is still more effective.
When we look at what you're really doing, it should all be a single SQL statement:
#!/usr/bin/perl
use strict;
use warnings;
# use DBI;
my #selectbox1 = ( "group1", "group2", "group3" );
my #selectbox2 = ( "check1", "check2", "check3" );
my $dbslave;
# $dbslave = DBI->connect(...) or die "A horrible death";
sub placeholder_list
{
my($n) = #_;
die "$n should be larger than 0" if $n <= 0;
my $list = "(?" . ",?" x ($n - 1) . ")";
return $list;
}
my $sql_insert = qq%
INSERT INTO checklist_group_mapping(col1, col2, col3)
SELECT '', gm.id, cl.id
FROM group_management AS gm
CROSS JOIN checklisst AS cl
WHERE gm.group_name IN X1
AND cl.checklist_name IN X2
%;
my $X1 = placeholder_list(scalar(#selectbox1));
my $X2 = placeholder_list(scalar(#selectbox2));
$sql_insert =~ s/X1/$X1/;
$sql_insert =~ s/X2/$X2/;
my $ins = $dbslave->prepare($sql_insert);
$ins->execute(#selectbox1, #selectbox2);
The big advantage of this is that there are far fewer round trips for information flowing between the application and the database, which (almost) invariably improves performance, often dramatically.
The only residual issue is whether your DBMS supports explicit CROSS JOIN like that. If not, you'll need to replace the words CROSS JOIN with a single comma.
There are still things that should be fixed, such as checking that the prepared statements were successfully prepared, and so on. But this may have given you some insight into how to think about using the DBI with Perl.
The trick is to use $_ variable inside your foreach. Like this:
my $current_value;
foreach $select2(#selectbox2) {
$current_value = $_;
my $sql_select2 = "select id from checklist where checklist_name = '$select2'";
......
my $box_2 = $current_value;

How can I fetch a single count value from a database with DBI?

The following code seems to be just too much, for getting a single count value.
Is there a better, recommended way to fetch a single COUNT value using plain DBI?
sub get_count {
my $sth = $dbh->prepare("SELECT COUNT(*) FROM table WHERE...");
$sth->execute( #params );
my $($count) = $sth->fetchrow_array;
$sth->finish;
return $count;
}
This is shorter, but I still have two statements.
sub get_count_2 {
my $ar = $dbh->selectall_arrayref("SELECT ...", undef, #params)
return $ar->[0][0];
}
Easy enough to do in one line with no extra variables:
$count = $dbh->selectrow_array('SELECT count(*) FROM table WHERE...', undef, #params);
I don't know Perl, but if it's syntax is logical I would think this would work based on your 2nd example:
sub get_count {
return $dbh->selectall_arrayref("SELECT ...", undef, #params)->[0][0];
}
I probably wouldn't do this myself, but you could always make it a new top-level function of the DBH object you're using:
WARNING: untested code follows!
sub DBD::SQLite::db::count
{
my($dbh, $table, $where) = #_;
my($stmt) = "SELECT COUNT(*) FROM $table";
$stmt .= " WHERE $where" if $where;
my($count) = $dbh->selectrow_array($stmt);
return $count;
}
and then call it like this:
my($cnt) = $dbh->count('Employee', 'year_hired < 2000');
Besides polluting a namespace that's not yours, you'd also have to write this for every DB driver you use, though I'm sure your could work something up that allows you to construct and eval some code to auto-configure this for a given DBH object.

Resources