mysql select and insert safely and quickly with php - php

I have a simple table, the logic is that before inserting a new row, I should fetch one column from that table. Let me explain:
table
id key groupId Note
1 00001 1 abd
2 00002 1 aasdas
3 00003 1 aasdas
4 00001 2 q2eqwd
5 00002 2 qwdvvd
6 00003 2 qwrqw
7 00004 2 qwdqdqw
You see, key increases like Auto Increment for each groupId.
When group with id 2, wants to add a new note, he should know last key. After finding it, php addes +1 to last key and inserts a new row. I do it like below:
$groupId = 2; //for example
$note = $_POST['note'];
$select = $db -> prepare("SELECT key FROM table where groupId = :groupId ORDER BY id DESC limit 1");
$select -> bindValue(":groupId", $groupId, PDO::PARAM_INT);
$select -> execute();
$fetch = $select -> fetch(PDO::FETCH_ASSOC);
$lastKey = $fetch['key']+1;
$insert = "INSERT INTO table (key, groupId, note) VALUES(:key, :groupId, :note)";
$ins = $db -> prepare($insert);
$insert -> bindValue(":key", $lastKey, PDO::PARAM_INT);
$insert -> bindValue(":groupId", $groupId, PDO::PARAM_INT);
$insert -> bindValue(":note", $note, PDO::PARAM_STR);
This method works good for me, but I am afraid of is here will be any conflict while fetching last key from table? Because, at the same time, 10 user with same groupId can add a new row. May php fetch same key to 3 users with group ID 2 at the same time?
Is there any quickly and safely way?

You can do this with AUTO_INCREMENT using MyISAM.
From MySQL Docs:
For MyISAM and BDB tables you can specify AUTO_INCREMENT on a
secondary column in a multiple-column index. ... This is useful when you want to put data into ordered groups.
Otherwise, you should set the value in your insert query with a subquery like SELECT MAX(key) + 1 FROM table WHERE groupID = 1 and read back the value.

Related

How to check how many rows were updated and inserted in a ON DUPLICATE statement?

I have a statement that looks like this:
$count=0;
while($row = pg_fetch_assoc($result)){
$sql=("INSERT INTO joblist (job_no, billed, completed, paid, paid_amount, inv_no, invoice, type, o_submitted, approval_date, gals, jobtype, name, state, region, territory)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE
job_no=VALUES(job_no), billed=VALUES(billed),completed=VALUES(completed), paid=VALUES(paid), paid_amount=VALUES(paid_amount), inv_no=VALUES(inv_no), invoice=VALUES(invoice), type=VALUES(type), o_submitted=VALUES(o_submitted), approval_date=VALUES(approval_date), gals=VALUES(gals), jobtype=VALUES(jobtype), name=VALUES(name), state=VALUES(state), region=VALUES(region), territory=VALUES(territory)");
$stmt = $conn->prepare($sql);
$stmt->bind_param("ssssssssssssssss",$job_no, $billed, $completed, $paid, $paid_amount, $inv_no, $invoice, $type, $o_submitted, $approval_date, $gals, $jobtype, $name, $state, $region, $territory);
$stmt->execute();
$count++;
}
The problem is, I cannot decipher between updated rows and inserted rows. Is there a way I can do this?
I know i can use the effected rows function, but it reads the same if updated/inserted. Any ideas? thanks!
You're looking for an UPSERT with a RETURNING clause.
Taking into account the following table a records ..
CREATE TEMPORARY TABLE t
(id INT PRIMARY KEY, name TEXT);
INSERT INTO t VALUES (1,'elgar'),(2,'bach'),(3,'brahms');
.. you can use an UPSERT to catch a primary key conflict and ask the query to return the affected records. You can put this insert inside a CTE and count it with a new query. The following query will insert two already existing primary keys (1 and 2) and a new one (4):
WITH j (affected_rows) AS (
INSERT INTO t VALUES (1,'edward'),(2,'johann sebastian'),(4,'schubert')
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name
RETURNING *
) SELECT count(affected_rows) FROM j;
count
-------
3
See the results yourself :-)
SELECT * FROM t ORDER BY id;
id | name
----+------------------
1 | edward
2 | johann sebastian
3 | brahms
4 | telemann

Update data in mysql column field without removing previous value

I am trying to update "new" column value with new value but problem is my query remove previous data while inserting new value
What is want: here is example table structure,
Table name = agg_lvl primary key set = uid
uid | new
--------|--------
1 | 100
2 | 300
You can see "new" has 100 points, for example I send 100 new points to user 1, so new column value should be 100 + 100 = 200, right now with this code
$query4 = mysql_query("INSERT INTO agg_lvl (uid, new) VALUES ('$uid','$new')
ON DUPLICATE KEY UPDATE uid='$uid',new='$new'");
Not sure what
new = '$new'
I have tried both ways but no success = >
new = 'new + $new' or new = new + '$new'
You should make changes in your query
Make num = nun+$num to add new value to old one
Remove quotes arount $new because it is a number but not a string
Remove uid from set list because insert already point to that record
And your query should look so:
$query4 = mysql_query("INSERT INTO agg_lvl (uid, new) VALUES ('$uid','$new')
ON DUPLICATE KEY UPDATE new=new+$new");
Okay first i will answer with the proper way to do the same, In this case i am assuming that UID is unique, so you make a new table scorecard with UID as foreign key. Now rather than update, you just insert stuff to table like if UID 1 gains 10 and 20 points, there are two entries. onw with 10 and one with 20. Now to get his current points, you add all points where UID=1 .
Now in your implementation the correct query would be
UPDATE userData SET points = points + x WHERE UID = $uid
where x is the new points gained and points is the name of column
$query4 = mysql_query("INSERT INTO agg_lvl (uid, new) VALUES ('$uid','$new')
ON DUPLICATE KEY UPDATE uid='$uid',new=new+$new");
worked for me with help of #splash58

putting values in between the ascending database column

Following is my database in mysql:
Id Username Password
1 admin admin
2 jay jay1
3 suman xyza
4 chintan abcde
This is my code in php:
$fetchid = mysql_query(" SELECT MAX(Id) As max From user;");
$row = mysql_fetch_array($fetchid);
$largest = $row['max'];
$largest++;
$user= $_POST['username'];
$pass= $_POST['password'];
$result = mysql_query(" INSERT INTO `proshell`.`user` (
`Id` ,
`Username` ,
`Password`
)"."
VALUES (
'".$largest."', '".$user."', '".$pass."'
);");
Problem:
Now if I delete row with Id=1 and then re-enter the data then it should use ID=1 then Again I reinsert the data it use ID=5
It works like this:
if I delete row with Id=1 and then re-enter the data the Id it gets is 5 but then 1 is free so,
What should I write to perform that task.
First, if you set your Id column to AUTO_INCREMENT you don't need the following part in your code at all:
$fetchid = mysql_query(" SELECT MAX(Id) As max From user;");
$row = mysql_fetch_array($fetchid);
$largest = $row['max'];
$largest++;
Because AUTO_INCREMENT will automatic add new value to your ID colume.
But if you don't set it to AUTO_INCREMENT, the above code will grab the MAXIMUM ID value (in this case, 4).
When you re-enter your data again after you delete the row 1, the MAXIMUM ID still 4, so your new ID value will be 5 (from $largest++;).
.....
If you really need to use consecutive ids as you PK, you need to re-write you code but I suggest you to use UUID for you ID column instead.
You can easily generate UUID by using uuid().
How about the UUID performance? Refer to Dancrumb's answer about this:
A UUID is a Universally Unique ID. It's the universally part that you should be considering here.
Do you really need the IDs to be universally unique? If so, then UUIDs
may be your only choice.
I would strongly suggest that if you do use UUIDs, you store them as a
number and not as a string. If you have 50M+ records, then the saving
in storage space will improve your performance (although I couldn't
say by how much).
If your IDs do not need to be universally unique, then I don't think
that you can do much better then just using auto_increment, which
guarantees that IDs will be unique within a table (since the value
will increment each time)
see. UUID performance in MySQL?
EDIT: I don't suggest you run query on the whole table just to find the MAX ID value before inserting new value everytime, because it will give you a performance penalty (Imagine that if you have million rows and must query on them everytime just to insert a new row, how much workload causes to your server).
It is better to do the INSERT just as INSERT, no more than that.
EDIT2:
If you really want to use consecutive ids, then how about this solution?
Create new TABLE just for store the ids for insert (new ids and the ids that you deleted).
For example:
CREATE TABLE cons_ids (
ids INT PRIMARY KEY,
is_marker TINYINT DEFAULT 0
);
then initial ids with values from 1-100 and set marker to be '1' on some position, e.g. 80th of whole table. This 'marker' uses to fill your ids when it's nearly to empty.
When you need to INSERT new Id to your first table, use:
$result = mysql_query("SELECT ids, marker FROM cons_ids ORDER BY ids ASC LIMIT 1;");
$row = mysql_fetch_row($result);
and use $row[0] for the following code:
INSERT INTO yourtable (Id, Username, Password)
VALUES ($row[0], $username, $password);
DELETE FROM cons_ids
WHERE ids = $row[0];
This code will automatically insert the lowest number in cons_ids as your Id and remove it from the cons_ids table. (so next time you do insert, it will be the next lowest number)
Then following with this code:
if ($row[1] == 1) {
//add new 100 ids start from the highest ids number in cons_ids table
//and set new marker to 80th position again
}
Now each time you delete a row from your first table, you just add the Id from the row that you deleted to cons_ids, and when you do INSERT again, it will use the Id number that you just deleted.
For example: your current ids in cons_ids is 46-150 and you delete row with Id = 14 from first table, this 14 will add to your cons_ids and the value will become 14, and 46-150. So next time you do INSERT to your first table, your Id will be 14!!.
Hope my little trick will help you solve your problem :)
P.S. This is just an example, you can modify it to improve its performance.
First of all, as I understand, you are selecting highest column ID which should be always the last one (since you set auto-increment on ID column).
But what are you trying to do is actually filling up holes after delete query, right?
If you are really looking for such approach, try to bypass delete operation by making new boolean column where you flag record if it is active or not (true/false).
SQL table change:
Id Username Password Active
1 admin admin false
2 jay jay1 true
3 suman xyza false
4 chintan abcde true
PHP request:
$fetchid = mysql_query(" SELECT MIN(Id) As min FROM user WHERE active = false;");
$result = mysql_query(" INSERT INTO `proshell`.`user` (
`Id` ,
`Username` ,
`Password`
`Active`
)"."
VALUES (
'".$largest."', '".$user."', '".$pass."', 'true'
);");

MySQL fixing autoincrement gaps in two tables

I have two tables like so;
id_image foo bar
1 3 5
2 8 1
3 17 88
7 14 23
8 12 9
id_image bar foo
1 2 3
1 5 6
2 18 11
2 10 12
3 8 21
3 17 81
7 29 50
7 1 14
8 10 26
8 27 34
There is a gap in the autoincremented id_image in the first table. In the second table, the id_image refers to the id_image in the first table, and there's two of each ID in there.
Notice: This table is theoretical. I have no idea where the gap is exactly, or whether or not there are even multiple gaps. All I know is that the first value is 1 and the last value is higher than the total row count.
Now, I'd like to fix this gap.
Before you say that the gaps don't matter and if they do, it's bad database design, let me tell you; I agree with you.
However, what I'm dealing with is a (hopelessly rear end backwards) third-party open source system to which I need to import a huge amount of existing data that doesn't have cross-referenceable IDs into multiple tables. The only way I can make sure that the same data gets a matching ID in every table throughout the system is to input it sequentially, and that means I can't have gaps.
So what I do now need to do is;
Fix the gap in the id_image column in the first table, so that the last value matches with the row count.
Edit the id_image column in the second table so that its value corresponds to the same row is corresponded to before the gap fix.
How would I begin to do this? I understand that this might be outside the capabilities of the MySQL query language, so PHP answers are also acceptable. Thanks! :)
ALTER TABLE table2
ADD FOREIGN KEY FK_IMAGE (id_image)
REFERENCES table1 (id_image)
ON DELETE CASCADE
ON UPDATE CASCADE;
SET #currentRow = 0;
UPDATE table1 INNER JOIN (
SELECT #currentRow := #currentRow + 1 AS id_image_new, id_image AS id_image_old
FROM table1
ORDER BY id_image ASC) t on t.id_image_old = table1.id_image
SET table1.id_image = t.id_image_new;
ALTER TABLE table1 AUTO_INCREMENT = 1;
The FK will automatically update ids of your 2nd table accordingly.
I'm not sure at all but in some older versions of mysql, update a table that you are referencing within a subquery of the update could crash. If so, just create a 2nd table and fill it up (inserts), then delete the old one and rename the new one.
The basic idea here is to find all of the gaps first to determine how much you need to decrement each id. Then, you have to iterate through both tables and apply the decrement. (You'll need to add: host, db, user, pass, and the actual table names)
try {
$pdo = new PDO('mysql:host=HOST;dbname=DB', 'user', 'pass');
$pdo->beginTransaction();
// Iterate through all id's in the first table
$stmt = $pdo->exec('SELECT image_id FROM TableOne ORDER BY image_id ASC');
$stmt->bindColumn('image_id', $id);
if(!$stmt->fetch(PDO::FETCH_BOUND)) {
throw Exception('No rows in table');
}
$lastId = $id;
$gaps = array();
// Find all the gaps
while($stmt->fetch(PDO::FETCH_BOUND)) {
if($id != ($lastId + 1)) {
$gaps[] = $id;
}
$lastId = $id;
}
if(!isset($gaps[0])) {
throw new Exception('No gaps found');
}
// For each gap, update the range from the last gap to that gap by subtracting
// the number of gaps there has been from the id
$lastGap = $gaps[0];
for($i = 1; $i < count($gaps); $i++) {
$stmt = $pdo->prepare('UPDATE TableOne SET image_id = image_id - :i WHERE image_id BETWEEN :lastGap AND :gap');
$stmt->execute(array(
':i' => $i,
':lastGap' => $lastGap,
':gap' => $gaps[$i]
));
$stmt = $pdo->prepare('UPDATE TableTwo SET image_id = image_id - :i WHERE image_id BETWEEN :lastGap AND :gap');
$stmt->execute(array(
':i' => $i,
':lastGap' => $lastGap,
':gap' => $gaps[$i]
));
$lastGap = $gaps[$i];
}
// Finally, fix the gap between the last found gap and the end of the table
$stmt = $pdo->prepare('UPDATE TableOne SET image_id = image_id - :i WHERE image_id BETWEEN :lastGap AND :gap');
$stmt->execute(array(
':i' => $i,
':lastGap' => $lastGap,
':gap' => $gaps[$i]
));
$stmt = $pdo->prepare('UPDATE TableTwo SET image_id = image_id - :i WHERE image_id BETWEEN :lastGap AND :lastId');
$stmt->execute(array(
':i' => $i,
':lastGap' => $lastGap,
':lastId' => $lastId
));
// Verify everything is correct
$stmt = $pdo->exec('SELECT image_id FROM TableOne ORDER BY image_id ASC');
$stmt->bindColumn('image_id', $id);
if(!$stmt->fetch(PDO::FETCH_BOUND)) {
throw new Exception('No rows'); // Should never be thrown
}
$lastId = $id;
while($stmt->fetch(PDO::FETCH_BOUND)) {
if($id != ($lastId + 1)) {
throw new Exception('There was an error between ids ' . $lastId . ' and '. $id);
}
$lastId = $id;
}
$stmt = $pdo->exec('SELECT image_id FROM TableTwo ORDER BY image_id ASC');
$stmt->bindColumn('image_id', $id);
if(!$stmt->fetch(PDO::FETCH_BOUND)) {
throw new Exception('No rows in table two'); // Shouldn't hit this
}
$lastId = $id;
$ids = array($id);
while($stmt->fetch(PDO::FETCH_BOUND)) {
$ids[] = $id;
if(count($ids) == 2) {
if($ids[0] !== $ids[1]) {
throw new Exception('Table two error on ids ');
}
if($ids[0] !== $lastId) {
throw new Exception('Table two error on id gapfix');
}
$lastId = $ids[0];
$ids = array();
}
}
$pdo->commit();
} catch(Exception $e) {
$pdo->rollBack();
var_dump($e);
}
Important: You might want to throw this in a file and run via the CLI: php -f gapfix.php and include a query before $pdo->commit() that returns a list of all the ids so you can verify the operation worked as expected. If it didn't, you can roll it back as if nothing happened. The code now checks for itself if the first table is in the right order. It doesn't however check the second table yet. All checking has been implemented!
Painful this.
Create a table like the first, no identity on Id_Image and an extra int column called rownumber
Use the pseudo row_number trick to populate it, some like
Insert into NewTable
Select id_image,foo,bar,#RowNumber := #RowNumber + 1 order by id_image.
If you have a foreign key to the second table drop it, then it's a simple update with a join. Drop the old table1 , rename the new one, add the identity and reseed, and put your foreign key back if you had one.
You do realise you are going to have to keep doing this crap?
There's probably a fun way of doing all this in one go, if you have cascading updates on, but pay careful attention to the execution plan. The RowNumber trick only works if things are done in Id_Image order. If Mysql decides there's a more efficient way of doing the query....

pdo - prepared statement loses data?

I have a query (PHP, Mysql) a table named 'table' that looks like this:
id | name
-------------
6 | abc
10| xxx
52| def
And a query:
$ids = '5,62'
$name = $pdo -> prepare('SELECT id, name FROM table WHERE id IN ( :ids )');
$name -> bindValue(':ids', $ids, PDO::PARAM_STR);
$name -> execute();
$name = $name->fetchAll(PDO::FETCH_ASSOC);
print_r($nazwa);
I would expect to get a result like
id | name
-------------
6 | abc
52| def
Unofrtunately i get only:
id | name
-------------
6 | abc
As if second value would be ignored. If I change the query to:
$name = $pdo -> prepare('SELECT id, name FROM table WHERE id IN (' $ids ')');
It all goes right. Can you tell me why prepared statement doesnt take under consideration imploded table with commas?
Because the comment field does not allow to write that well, here an answer that is merely a comment:
Let's say you have two IDs:
$name = $pdo->prepare('SELECT id, name FROM table WHERE id IN ( :id1, :id2 )');
Let's say you have three IDs:
$name = $pdo->prepare('SELECT id, name FROM table WHERE id IN ( :id1, :id2, :id3 )');
You see the pattern? You have as many values as you have IDs. Formulate the prepare statement as well as perform the bind statements accordingly to the number of IDs you have.
So the answer is: You need to change your code so that it actually reflects the number of IDs you want to handle. The MySQL server will not magically interpret a comma-separated list inside a string as multiple IDs. Instead you need to tell the server about each single ID.

Categories