Assuming I do something like the following:
my $rows = $dbh->do("UPDATE MYTABLE SET amount=1200 WHERE id =123");
The $rows returns 1 even with the amount is already 1200. So it is considered an updated row.
My question is: Is there a way to check if an update actually changed the values in a row besides doing a query before the update?
Change the SQL query to:
UPDATE MYTABLE SET amount=1200 WHERE id = 123 AND amount <> 1200
The table will be identical, but it returns the number of rows that actually changed.
By default, DBD::mysql returns the number of rows matched in an UPDATE, not the number of rows physically changed. You can change this behavior by disabling mysql_client_found_rows in your call to connect:
my $dsn = "DBI:mysql:;mysql_client_found_rows=0";
my $dbh = DBI->connect($dsn, $user, $password);
Twinkles answer is correct, but you should create a statement handle using prepare and then execute most of your database queries.
In this case you would write
my $update_if_changed = $dbh->prepare('UPDATE mytable SET amount = ? WHERE id = ? AND amount != ?')
and subsequently
$update_if_changed->execute($amount, $id, $amount)
Related
I have selected all the appropriate columns for this statement. When I change WHERE user_id = $user, and the $user is a number, that works. However it doesn't seem to like the username letters. User is a valid column in my table and 'john' does exist. What am I doing wrong?
$user = 'john';
// Set the timestamp from the current system time
$time = time();
// Put our query together:
$query = "UPDATE table set
`time` = {$time}
WHERE user = {$user}";
In your query, here:
`user_id` = {$user},
`time` = {$time}
WHERE user = {$user}";
You're pointing twice to the same value $user, one for id, one for user, Are you sure you haven't defined that value twice in your JS scripts? Try giving different names to each value and that could help
do you miss a "," after "{$time}"?
Check the data in the user column, I have a feeling it's numeric. If you insist on using the username the look in MySQL documentation of a function to convert username to user-number and use it in your WHERE clause.
I have a select statement:
SELECT id, content, name
FROM records
WHERE type = '1'
AND name = 'test';
Here's the output:
id content name
99708 10.6.252.41 server01.example.org
What I'd like to do is be able to get the id that is returned from the previous statement and USE the id as input into another statement (an UPDATE statement) that will increment the value of a single column in the same table.
An example UPDATE statement that I am wanting is:
update records SET hits = hits + 1 WHERE id = ID_FROM_SELECT;
Thanks in advance.
You can use user defined session variables for this if the SELECT is returning just one result:
SELECT #id:=id AS id, content, name
FROM records
WHERE type = '1'
AND name = 'test';
Then, on the same database session (connection), do the following:
UPDATE records
SET hits = hits + 1
WHERE id = #id;
I'm assuming you're doing something with the selected records in your app, and you're trying to save on performance by avoiding having to search for the record again in the UPDATE. Though, in that case, why not set the 'id' value as a parameter in code?
Obviously, if the SELECT is returning multiple records, this would best be done in code as I mentioned above, otherwise you're left with running the SELECT query again as a subquery:
UPDATE records
SET hits = hits + 1
WHERE id IN
(SELECT id
FROM records
WHERE type = '1'
AND name = 'test');
So, then, it makes more sense just to apply the same filter to the UPDATE instead:
UPDATE records
SET hits = hits + 1
WHERE type = '1'
AND name = 'test'
Probably this is not what you want to do.
First of all...If the query only returns 1 line, the solution provided by Marcus Adams works fine. But, if the query only returns one line, you dont need to preset the id in order to update. Just update it:
update records
set hits = hits + 1
where type = '1'
and name = 'test'
Second...If the query will not return only one record and you want to update all records returned with same values or calculations, the same code above will do what you need.
Third, if the query does not return just one record and you need to update each record returned with different value then you need to have a different approach.
I think you are not designing your system very well. If the request for update come from outside, you should have the id to be updated as a parameter of your request. For example something like:
<html>
<body>
Test
</body>
</html>
And in your update.php you have something like:
<?php
$id = $_GET['id'];
$sql = "update records set hits = hits + 1 where type = '1' and name = 'test' and id = $id";
?>
Of course, the picture I have is to small. Probably you have a reason to do this way or this is just an example. If you fill us up with more info we might be more helpful.
I am trying to return a members firstname field from the table users from the last row of users.
my $dbh = DBI->connect($dsn, $db_user_name, $db_password) or die "$DBI::errstr";
my $LastID = $dbh->last_insert_id(`firstname`); ##but I want from table users
print qq~$LastID~;
This error is returned from above:
DBI last_insert_id: invalid number of arguments: got handle + 0, expected handle + between 4 and 5
Usage: $h->last_insert_id($catalog, $schema, $table_name, $field_name [, \%attr ])
So, what would be the "best" way (best being best for server, fastest, least memory, load, least amount of overhead.. whatever) to get the field firstname from the last row in the table users?
Realize my example above is not to be taken seriously as I have no idea how to do this without just doing something like my crude, but functional:
(p.s. UserID is NOT assigned by auto increment but, is in numeric order and a new user gets a higher UserID. Just the way this was when I tackled the pre existing problem.)
my $dbh = DBI->connect($dsn, $db_user_name, $db_password) or die "$DBI::errstr";
my $dsn = $dbh->prepare(qq{SELECT `firstname` FROM `users` ORDER BY ABS(UserID) DESC LIMIT ?,?});
$dsn->execute('1','1') or die "$DBI::errstr";
while(#nrow = $dsn->fetchrow_array()) {
$firstname = $nrow[0];
}
I assumed since I was using DBI that may provide the best solution but, I am inexperienced obviously and need some advice and guidance to learn the proper way to do this. Thanks for any assistance.
You mention that UserID is not auto incrementing, so I'm not sure if last_insert_id will work in this situation. It may, but I'm just not sure. The document states:
Typically this would be a value assigned by the database server to a
column with an auto_increment or serial type.
I would look to solve this by just using a SQL statement:
SELECT
u.firstname
FROM
users u
JOIN
(
SELECT
MAX(UserID) AS UserID
FROM
users
) t ON u.UserID = t.UserID
The code with DBI would then look like this:
my $stmt = 'SELECT u.firstname FROM users u JOIN(SELECT MAX(UserID) AS UserID FROM users) t ON u.UserID = t.UserID';
my $first_name = ($dbh->selectrow_array($stmt))[0];
last_insert_id method takes 4 args. Use like this:
my $id = $connection->last_insert_id(undef, 'myschemaname', 'mytablename', 'mycolumnname');
See the DBI pod.
is have this statement and i want to make it by the Active Records way in codeigniter
DELETE FROM TABLE
(col1 = value AND col2 = value2 ) OR (col1 = value2 AND col2 = value );
CodeIgniter Active Record is impractical for mixing AND's and OR's within a query.
You will likely need to construct some of the query manually, e.g. something along the lines of:
$this->db->where("col1 = $value AND col2 = $value2");
$this->db->or_where("col1 = $value2 AND col2 = $value");
Alternately, in your example where there are only two specific columns and two values, the following idea should also work:
$this->db->where_in('col1', array($value, $value2));
$this->db->where_in('col2', array($value, $value2));
$this->db->where('col1 != col2');
Try this:
$this->db->where('col1', $value1);
$this->db->where('col2', $value2);
$this->db->delete($table);
That's not the goal of active record. As name suggest active record object instance is tied to a single row in a table. If you want to do this in "active record way" you have to retrieve all the records you have to delete the database, mark them as deleted and commit changes witch is overkill. Best you can do is to get connection instance from codeignither and execute query manually.
my $sth = $dbh->prepare("SELECT id
FROM user
WHERE group == '1'
ORDER BY id DESC
LIMIT 1");
I was trying to get the id of the last row in a table without reading the whole table.
I am already accessing via:
my $sth = $dbh->prepare("SELECT name,
group
FROM user
WHERE group == '1'
LIMIT $from, $thismany");
$sth->execute();
while(my ($name,$group) = $sth->fetchrow_array()) {
...and setting up a little pagination query as you can see.
But, I am trying to figure out how to detect when I am on the last (<= 500) rows so I can turn off my "next 500" link. Everything else is working fine. I figured out how to turn off the "previous 500" link when on first 500 page all by myself!
I thought I would set up a "switch" in the while loop so if ($id = $last_id) I can set the "switches" var.
Like:
if ($id = $last_id) {
$lastpage = 1; #the switch
}
So I can turn off next 500 link if ($lastpage == 1).
I am really new to this and keep getting stuck on these types of things.
Thanks for any assistance.
Try to grab an extra row and see how many rows you really got. Something like this:
my #results = ( );
my $total = 0;
my $sth = $dbh->prepare(qq{
SELECT name, group
FROM user
WHERE group = ?
LIMIT ?, ?
});
$sth->execute(1, $from, $thismany + 1);
while(my ($name, $group) = $sth->fetchrow_array()) {
push(#results, [$name, $group]); # Or something more interesting.
++$total;
}
$sth->finish();
my $has_next = 0;
if($total == $thismany + 1) {
pop(#results);
$has_next = 1;
}
And BTW, please use placeholders in all of your SQL, interpolation is fraught with danger.
Always asking for one more row than you are going to show, as suggested by mu is too short, is a good way.
But if you want to take the other suggested approach of doing two separate queries, one to get the desired rows, and one to get the total count if there had not been a limit clause, MySQL provides an easy way to do that while combining as much of the work as possible:
SELECT SQL_CALC_FOUND_ROWS name, group FROM user WHERE group = '1' LIMIT ..., ...;
then:
SELECT FOUND_ROWS();
The SQL_CALC_FOUND_ROWS qualifier changes what a following FOUND_ROWS() returns without requiring you to do a whole separate SELECT COUNT(*) from user WHERE group = '1' query.
SELECT COUNT(*) from tablename will give you the number of rows, so if you keep a running count of how many rows you have read so far, you'll know when you're on the last page of results.
You could generate that query with (untested; away from a good workstation at the moment):
my $sth = $dbh->prepare("select COUNT(*) FROM user WHERE group == '1'");
my #data = $sth->fetchrow_array;
my $count = $data->[0];
(PS. you should be aware of SQL injection issues -- see here for why.)
As Ether mentioned in the comments, pagination usually requires two queries. One to return your paged set, the other to return the total number of records (using a COUNT query).
Knowing the total number of records, your current offset and the number of records in each page is enough data to work out how many pages there are in total and how many before and after the current page.
Although your initial suggestion of SELECT id FROM table WHERE ... ORDER BY id DESC LIMIT 1 should work for finding the highest matching ID, the standard way of doing this is SELECT max(id) FROM table WHERE ...