Fetching one single row from database using Perl - perl

I am trying to run a simple database connection with Perl using postgreSQL
use DBI;
$database = "postgres";
$user = "postgres";
$password = "admin";
my $dbh = DBI->connect( "dbi:Pg:dbname=$database"
, $user
, $password
)
or die "Can't Connect to database: $DBI::errstr\n";
# get data from the URL string
my $firstname = "haroon";
my $lastname ="ash";
my $age = 24;
# insert the data into the database
my $query = "INSERT INTO people (firstname, lastname, age)
VALUES ('$firstname','$lastname', '$age')";
$dbh->do($query);
# get the ID of the inserted person
$query = "SELECT MAX(id) FROM people";
my $sth = $dbh->prepare($query);
my $rv =$sth->execute;
if($rv < 0){
print $DBI::errstr;
}
else {
my $row = $sth->fetchrow_hashref;
my $person_id = $row->{'max'};
print $firstname, $lastname
. "was successfully inserted at position "
. $person_id;
}
I am trying to print the person id which i had entered latest. But my $person_id = $row->{'max'} seems to give me the correct answer instead of my $person_id = $row->{'id'};. I am not understanding why is that.

You might want to set column alias for query,
$query = "SELECT MAX(id) AS id FROM people";
as postgres is putting his own alias for you, and that is max.
If all you want is last inserted id, you can
my $query = "INSERT INTO people (firstname, lastname, age)
VALUES (?,?,?)
returning id
";
and fetch query as you would do with select. (check pg docs)

You can use the RETURNING keyword to return the id associated with the row you just inserted:
my $query = '
INSERT INTO people (firstname, lastname, age)
VALUES ($1, $2, $3)
RETURNING id';
my $sth = $dbh->prepare($query);
$sth->execute($firstname, $lastname, $age);
my $rv = $sth->fetchrow_hashref();
printf "%s, %s was successfully inserted at position %d\n",
$firstname, $lastname, $rv->{id};

You did not enter a PERSON_ID in your insert statement.
Instead it seems the ID field was populated by the DB, an auto increment value during insert, it seems.
Without knowing the table definition (and potential insert trigger on the table) it is hard/impossible to give you a better answer.

Related

PDO postgresql last inserted id return 'false' or -1

I have code
$dbh = new PDO("pgsql:host=localhost; port=5432; dbname=gpd; user=postgres; password=postgres");
$sth = $dbh->prepare("INSERT INTO res (res_name, res_order) VALUES ('London', 1)");
$sth->execute();
$id = $dbh->lastInsertId();
echo $id;
Record insert, but $id is empty.
I try execure query
INSERT INTO res (res_name, res_order) VALUES ('London', 1) RETURNING id
and received a value "-1"
Going home, I myself understood what must to do
$dbh = new PDO("pgsql:host=localhost; port=5432; dbname=gpd; user=postgres; password=postgres");
$sth = $dbh->prepare("INSERT INTO res (res_name, res_order) VALUES ('London', 1)");
$sth->execute();
$result = $sth->fetch(PDO::FETCH_ASSOC);
echo "ID - ". $result["id"];
It's simple

Populating missing rows in postgresql

I have written a CGI script in perl. I am receiving POST request to that script from two different locations simultaneously.
From POST request i am populating five different tables. I get 500 rows in each POST request and then i populate each table with 100 rows.
My issue is that after some time one or two of the table start showing less number of rows. Both of these tables store ip in one of their column. Every time only these two tables are the one which is missing data which is weird for me. I am not able to understand why this is happening.
Although i receive something in postgresql logs and number of such logs are same as the number of rows missing from table.
UTC STATEMENT: insert into post (ip, count, location, source_server) values ($1, $2, $3, $4)
UTC STATEMENT: insert into post (ip, count, location, source_server) values ($1, $2, $3, $4)
UTC STATEMENT: insert into post (ip, count, location, source_server) values ($1, $2, $3, $4)
This is my CGI code.
use strict;
use warnings;
use CGI;
use DBI;
use JSON;
#Taking parameters passed as post request
my $conn = CGI->new;
my $data = $conn->param('POSTDATA');
#Details regarding database
my $host = 'host';
my $user = 'user';
my $password = 'password';
my $database = 'db';
my ( $location, $table, $entry );
#Creating connection to DB
my $dsn = "dbi:Pg:dbname=$database;host=$host";
my $dbh
= DBI->connect( $dsn, $user, $password,
{ RaiseError => 0, PrintError => 0 } )
or die("Connection to DB failed $!\n");
#Decoding json received in post request to perl hash
$data = decode_json $data;
my ($source_server) = keys %$data;
my $location = 'XX';
my $main_data = $$data{$source_server};
my #db = qw /a b c d e/;
my %mapping = (
'a' => 'A',
'b' => 'B',
'c' => 'C',
'd' => 'D',
'e' => 'E'
);
foreach (#$main_data) {
my $hash = $_;
#Deciding which table data will be pushed into
$table = shift #db;
$entry = $mapping{$table};
my $sth = $dbh->prepare(
"insert into $table ($entry, count, location, source_server) values (?, ?, ?, ?)");
while ( ( my $key, my $value ) = each %$hash ) {
$sth->execute( $key, $value, $location, $source_server );
}
$sth->finish;
}
#Sending 200 status code back to client
print "Status: 200\n\n";
Data is passed to my CGI script in JSON as {KEY : [{HASH},{HASH},{HASH},{HASH},{HASH}]}. Second and fifth hash contains data for tables which are missing it after some time.
Every hash will have 100 key:value pairs thus making total 500 entries.
Well after simbabque comment above i enabled RaiseError and PrintError. After this i am able to get error in server error logs. Error logs indicate that because of simultaneous insertion of rows coming from multiple places i am getting primary key violation which was causing this strange behaviour.
I tweaked my primary key and all is well :)

why autoincrement primary key column not autoincrementing in sqlite3

I have an sqlite3 db for an address book, and I created the address table with
my $dbcreate = qq(CREATE TABLE IF NOT EXISTS address
(ID INTEGER PRIMARY KEY AUTOINCREMENT,
LASTNAME TEXT NOT NULL,
FIRSTNAME TEXT NOT NULL,
COMPANY TEXT,
STREET TEXT,
CITY TEXT,
STATE TEXT,
COUNTRY TEXT,
ZIPCODE TEXT,
MOBILE TEXT,
OTHERPHONE TEXT,
FAX TEXT,
EMAIL TEXT,
EMAIL2 TEXT,
WEBSITE TEXT,
WEBSITE2 TEXT,
DOB DATE,
NOTES TEXT,
TAGS TEXT););
So the first column SHOULD, as I understand it, be an autoincrementing primary ID, only, it's not autoincrementing. My understanding from reading sqlite tutorials is that I should only have to enter the other 18 values, and that column should autoincrement, but if I insert 18 values, I'm told I'm missing one.
I've only been able to insert rows if I manually set the ROWID.
What gives?
Here's how I'm adding rows (unsuccessfully)
#!/usr/bin/perl -w
# script to enter a new addressbook entry
use DBI;
use strict;
use warnings;
print "Please enter a DB username? \n";
my $userid=<STDIN>;
chomp($userid);
print "Please enter a DB password? \n";
my $password=<STDIN>;
chomp($password);
my $driver = "SQLite";
my $database = "myaddress.db";
my $dsn = "DBI:$driver:dbname=$database";
my $dbh = DBI->connect($dsn, $userid, $password, { RaiseError => 1 })
or die $DBI::errstr;
print "Successfull DB connection\n";
print "Creating new address book entry\n";
my $arval = 0;
my #dbits = ('LASTNAME','FIRSTNAME','COMPANY','STREET','CITY','STATE','COUNTRY','ZIPCODE','MOBILE','OTHERPHONE','FAX','EMAIL','EMAIL2','WEBSITE','WEBSITE2','DOB','NOTES','TAGS');
# print "#dbits";
# for $b (0 .. 17) {
# print "\'$dbits[$b]\', ";
# }
print "-----\n";
my #nubits = ();
foreach my $databit(#dbits) {
print "Enter $databit: \n";
my $nubit = <STDIN>;
chomp($nubit);
print "$databit = $nubit\n\n";
push(#nubits,$nubit);
print "$databit = $nubits[$arval]\n";
my $arval = (++$arval);
print "$arval\n";
}
# print "new entry will be:\n";
# for $b (0 .. 17) {
# print "$nubits[$b] | ";
# }
my $entry = qq(INSERT INTO address VALUES('$nubits[0]','$nubits[1]','$nubits[2]','$nubits[3]','$nubits[4]','$nubits[5]','$nubits[6]','$nubits[7]','$nubits[8]','$nubits[9]','$nubits[10]','$nubits[11]','$nubits[12]','$nubits[13]','$nubits[14]','$nubits[15]','$nubits[16]','$nubits[17]'););
my $retval = $dbh->do($entry);
if($retval < 0){
print $DBI::errstr;
} else {
print "entry created successfully\n";
}
my $query = qq(select * from address);
print $query;
$dbh->disconnect();
I can manually use sqlite3 from the cli to enter rows, but, as mentioned, I have to manually set the ROWID/Primary ID.
In the past, I've only used sqlite with tcl/tk, never perl, but even though I'm a complete perl n00b, I don't think perl is my problem. sqlite3 is not behaving as expected (unless I've completely misread about a dozen tutorials that state that a primary key id set to autoincrement should, well, autoincrement).
It's not auto incrementing because you're already giving it an id, $nubits[0].
With no column list, INSERT INTO address VALUES (...) inserts all columns in the table. You could use NULL as #ReenactorRob suggests, but that just hides another problem with that query.
INSERT INTO address VALUES (...) requires knowledge of the order in which address was created to know that element 5 is street. If anything changes the ordering in the table, your INSERT breaks. If a column is added, your query breaks. If you put a value in the wrong slot (as you did) its difficult to tell that. You're much better off using an explicit column list.
INSERT INTO address
(lastname, firstname, company, street, ...)
VALUES
(...)
Now your ID will increment and you're protected from future table changes. If that seems like a lot of work, make it a function that takes a hash of values to build a query. It'll be much more readable than remembering what $nubits[12] is. But before you do that, look at DBIx::Class which has already done this for you.
I would be remiss if I didn't mention bind parameters. These are faster, allowing you to use prepared statements, and they protect you against SQL injection attacks.
Try using null in the insert like this:
my $entry = qq(INSERT INTO address VALUES(null,'$nubits[0]','$nubits[1]',...
As has been explained, the problem is that the items in the VALUES section of the INSERT are assigned one-for-one to the table's columns in the order they were first created. If you need to provide values for only a subset of the columns then you also need to provide a list of the column names that should receive the values.
Without the column names the values will be assigned to columns in the order that they were created, so it is the columns at the end that will not receive a value. In this case the database will allocate the value for the LASTNAME column to ID, and it is the TAGS element at the end that doesn't have a value.
It is also much better to use placeholders for the values in your SQL statement, and use prepare and execute instead of just do. That way the DBI module will quote and escape the actual values appropriately according to their data type.
This program shows how to do that
#!/usr/bin/perl
use strict;
use warnings;
use DBI;
use DBD::SQLite;
STDOUT->autoflush;
my ($driver, $dbname) = qw/ SQLite myaddress.db /;
my $dsn = "DBI:$driver:dbname=$dbname";
print 'Please enter a DB username: ';
my $username = <STDIN>;
chomp($username);
print 'Please enter a DB password: ';
my $password = <STDIN>;
chomp($password);
my $dbh = DBI->connect($dsn, $username, $password, { RaiseError => 1 });
print "Successful DB connection\n";
print "Creating new address book entry...\n";
print "-----\n";
my #dbits = qw/
LASTNAME FIRSTNAME COMPANY STREET
CITY STATE COUNTRY ZIPCODE
MOBILE OTHERPHONE FAX EMAIL
EMAIL2 WEBSITE WEBSITE2 DOB
NOTES TAGS
/;
my #nubits;
for my $databit (#dbits) {
print "Enter $databit: ";
my $nubit = <STDIN>;
chomp($nubit);
push #nubits, $nubit;
print "$databit = $nubits[-1]\n";
print scalar #nubits, "\n";
}
my $columns = join ', ', #dbits;
my $placeholders = join ', ', ('?') x #dbits;
my $insert = <<"__END__SQL__";
INSERT INTO address (
$columns
)
VALUES (
$placeholders
)
__END__SQL__
$insert = $dbh->prepare($insert);
$insert->execute(#nubits);
print "Entry created successfully\n";
$dbh->disconnect;
This creates an SQL statement in $insert that looks like this
INSERT INTO address (
LASTNAME, FIRSTNAME, COMPANY, STREET, CITY, STATE, COUNTRY, ZIPCODE, MOBILE, OTHERPHONE, FAX, EMAIL, EMAIL2, WEBSITE, WEBSITE2, DOB, NOTES, TAGS
)
VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)

problems in using DBI

I'm new to all this this stuff. I'm trying to execute the following code
use DBI;
my $dsn = 'DBI:mysql:db:localhost';
my $db_user_name = 'root';
my $db_password = '*******';
my $dbh = DBI->connect($dsn, $db_user_name, $db_password);
my $sth = $dbh->prepare("select id from table where field = 'value'");
$sth->execute();
($id) = $sth->fetchrow_array();
print "id is $id";
$sth->finish();
print outputs nothing. Can you tell me what am I doing wrong?
Thank you in advance!
You said in one of the comments that you had an # in the value. If you're having a quoting issue, you should use a placeholder. Let the database driver handle the quoting issues for you:
my $sth = $dbh->prepare("select id from table where field = ?");
$sth->execute($some_value);

Perl DBI insert and select

I want to copy a single record from a table, modify some of the fields in the record and insert into the same table. The table has 90 columns.
Thought of using insert..select in one statement but there are 90 columns and i need to tell the column name in the select query. How can i do it in a better way in perldbi. Pls provide me an example.
Fetch and cache the column names for your table using the NAME statement attribute
my $sth = $dbh->prepare('SELECT * FROM my_table where 1=0');
$sth->execute;
my $cols = $sth->{NAME};
then use $cols to construct your insert...select using some replacement function to inject your modifications in the select.
my %mods_for_column = ( 'foo' => 'foo+10', 'bar' => 'trim(bar)' );
my $inscols = join(',', #$cols);
my $selcols = join(',',
map { exists($mods_for_column($_)) ? $mods_for_column($_) : $_ } #$cols
);
my $sql = "insert into my_table ($inscols) select $selcols from my_table where mumble...";