in the database I'm working with, there's a table named "packets", in this table there are columns like "id", "userid" etc. and a column named "usage"(I know its a poor choice of name cause usage is a reserved word, but sadly I may not change anything in the table), the type of this column is enum with 3 values("education", "study" and "advanced training") with "education" as default-value and the value can be NULL.
I wrote a class Packet with getters/setters like set_usage($usage)/get_usage() and a class Packet_dao for accessing database using pdo:
class Packet_dao {
private m_insert_query;
...
public function persist($data)
{
$query = $this->m_insert_query;
try
{
$stmt = $this->bind_param($data, $query);
$stmt->execute();
return true;
}
catch (PDOException $ex)
{
$this->m_error_message = $ex->getMessage();
return false;
}
}
private function bind_param($packet, $query)
{
$stmt = $this->m_pdo->prepare($query);
$stmt->bindParam(':userId', $packet->get_user_id());
...
$stmt->bindParam(':usage', $packet->get_usage());
return $stmt;
}
insert query would looks like
INSERT INTO packets (userId, number, ..., `usage`)
VALUES (:userId, Coalesce(:number, default(number)), Coalesce(:usage, default(`usage`)))
so if I want to add a packet into db, the code would looks like:
$packet = new Packet();
$packet_dao = new Packet_dao();
$packet->set_stuff("stuff");
$packet_dao->persist($packet);
so far, so good, everything is working fine.
now, another usage-case "fullversion" should be added, and instead of adding a new value to the enum, they decided to use NULL as value of usage for packets which are full version. Now I get a problem: when I try to set value for usage using $packet->set_usage(NULL), it will be interpreted as the same as if the value isn't set and the default value("education") will be set in this case. What should I do?
apparently, the 2nd parameter of Coalesce() force it to take the default value when nothing is set (or NULL), after I changed Coalesce(:usage, default(usage)) into :usage. It worked
Related
Here is the database and PHP information:
Database vendor and version : 10.2.32-MariaDB
PHP Version : PHP 7.3
I am running into an issue when trying to retrieve the last inserted id to use in another insert statement using PHP PDO and MariaDB...
Sorry for the vague pseudo-code below but trying to mask proprietary data:
try {
include_once $pdo connection stuff here;
$pdo->beginTransaction();
$sql = 'AN INSERT STATEMENT HERE';
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':some_value', $some_value);
$stmt->bindValue(':another_one', $another_one);
$stmt->bindValue(':additional_value', $additional_value);
$stmt->execute();
// have tried to call $pdo->commit(): here to no avail.
//should get the last inserted id here on the AUTO_INCREMENT column in the target table from above prepared statement
// the AI column is not included in the insert statement above nor any value specified in the VALUES clause so should
// set to the next available value (and does so according to peeking at row over in phpMyAdmin).
$last_insert_id = $pdo->lastInsertId();
// don't really want to commit the above insert here just yet in case something goes wrong below and can rollback
// a file could be uploaded but it's not mandatory
if (!empty($_FILES['some_file'])) { // file has been attached.
// some file operations here
// some file operations here
// some file operations here
// some file operations here
$extensions = array("extension I am expecting");
if (in_array($file_ext, $extensions) === false) {
//Uh-oh not the correct extension so rolling back
$pdo->rollback();
die('message here...');
} else {
// file type is ok so proceeding
// if the file already exists, get rid of it so we don't have 2 copies on the server
if (file_exists($file_dir.$file_name)) {
unlink($file_dir.$file_name);
}
// storing the attached file in designated directory
move_uploaded_file($file_tmp, $file_dir.$file_name);
// going to parse the file...
$xml = simplexml_load_file('xml file to parse');
// have tried to call $pdo->commit(): here to no avail.
foreach ($xml->children() as $row) {
foreach ($row as $obj) {
if (some checking things with the obj here yada yada yada) {
$insert_sql = "INSERT INTO another table(columns.....) //there is no AUTO_INCREMENT column attribute on any column in this table just FYI
VALUES(column values...)";
$stmt = $pdo->prepare($insert_sql);
// want the AI value here from the very first insert above but it's always zero (0)
$stmt->bindValue(':last_insert_id', intval($last_insert_id), PDO::PARAM_INT);
$stmt->bindValue(':some_column', strval($some_column));
$stmt->bindValue(':another_one', strval($another_one));
$stmt->execute();
}
}
}
// all is good so committing the first insert
$pdo->commit();
}
} else {
// the file was not uploaded and it is not mandatory so committing the first insert here and the second insert never happens
$pdo->commit();
}
} catch (Exception $e) {
if ($pdo->inTransaction()) {
$pdo->rollback();
}
throw $e;
echo 'An error occurred.';
echo 'Database Error '. $e->getMessage(). ' in '. $e->getFile().
': '. $e->getLine();
}
}
My goal is that the first insert always gets inserted (should nothing fail in it). The second insert is optional depending if a file is attached.
If the file is attached and all the file operations are good, then I'll insert some values in another table and use the auto_increment value from the first insert in this second table ( the idea is as a foreign key).
But for whatever reason, the value inserted is always zero (0).
When the code executes successfully both table inserts complete (granted a file is present and the second insert even fires)...
The row in the first table is created and 1 or more rows in the second insert's table are created but they have a value of 0 in the designated column, where I would expect them to contain the AI value from the first insert...
I've tried to call $pdo->commit() in several other places that "make sense" to me thinking that the first insert must be committed for an AI value to even exist on that table but no luck with any of them...
I even tried this I saw in another Stackoverflow post as a test to make sure PDO isn't doing anything wonky, but PDO is fine...
$conn = new PDO(connection info here);
$conn->exec('CREATE TABLE testIncrement ' .
'(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50))');
$sth = $conn->prepare('INSERT INTO testIncrement (name) VALUES (:name)');
$sth->execute([':name' => 'foo']);
var_dump($conn->lastInsertId());
And the above does return: string(1) "1"
So I think PDO is ok (granted the above was not wrapped in a transaction and I haven't tried that yet)
Hope I have provided enough clear details...
Does anyone know why I am getting 0 and not the last insert id?
Any help is greatly appreciated and thank you!
You need to check the result of $stmt->execute. Read the docs on PDOStatement::execute and you'll see that it returns a boolean value:
Returns TRUE on success or FALSE on failure.
Then read the docs on PDOStatement::errorInfo. Check this if execute returns FALSE.
$stmt->execute();
echo "\nPDOStatement::errorInfo():\n";
$arr = $stmt->errorInfo();
print_r($arr);
EDIT: it's not generally a good idea to output errors to the screen, I did so in this case for convenience. A better approach would be to write a log file:
$arr = $stmt->errorInfo();
file_put_contents("/path/to/file.log", print_r($arr, TRUE));
I was wondering if we can still use values from a freshly DELETE row as a SELECT or do we really need to SELECT it before ?
Example :
Transform this
$foo = $db->prepare("SELECT * FROM table WHERE id= :id");
$foo->execute(array(
"id" => $table_id
));
$foo = $foo->fetch(PDO::FETCH_ASSOC);
$delete_foo = $bdd->prepare("DELETE FROM table WHERE id = :id");
$delete_foo->execute(array(
"id" => $table_id
));
echo $foo['name'] . " has been deleted !";
Into this :
$delete_foo = $bdd->prepare("DELETE FROM table WHERE id = :id");
$delete_foo->execute(array(
"id" => $table_id
));
$delete_foo = $delete_foo->fetch(PDO::FETCH_ASSOC);
echo $delete_foo['name'] . " has been deleted !";
It would be easier. I was just wondering, I use the 1st method but it just went in mind and I don't find answers.
In postgresql, there is a proprietary extension to the delete statement called RETURNING. Sql Server provides something similar, they call it OUTPUT
For example, OUTPUT DELETED.* in the following DELETE statement
returns all columns deleted from the ShoppingCartItem table:
DELETE Sales.ShoppingCartItem
OUTPUT DELETED.*;
Unfortunately, mysql does not have anything like the above. If you delete a row it's gone (unless you roll back the transaction instead of commiting). If you want to Select the data, you need to execute the SELECT before the DELETE
DELETE queries wont return any results (besides rows affected), so a PDO::query wont have any usable data to fetch.
For the example provided, an extra select query just makes no sense. As you have your $value already.
I would rather say that you need to simplify your PDO code at whole. Compare the below code snippet with yours
$foo = $db->run("SELECT foo FROM table WHERE value = ?", [$value])->fetchColumn();
$db->run("DELETE FROM table WHERE value = ?", [$value]);
echo "$foo has been deleted!";
the run() function can be achieved by a very small PDO modification:
class MyPDO extends PDO
{
public function run($sql, $args = NULL)
{
$stmt = $this->prepare($sql);
$stmt->execute($args);
return $stmt;
}
}
the code is taken from my article, Simple yet efficient PDO wrapper
I'm creating an application using FuelPHP framework and MySQL and I'm trying to AJAX-update/insert a new log for an item already in DB.
This is my MySQL code:
UPDATE `work_orders` SET `status_id`='{$status}' WHERE `id` = '{$wo_id}';
INSERT INTO `work_order_logs`(`id`, `work_order_id`, `log_text`, `status_id`) VALUES ('{$id}', '{$wo_id}', '{$text}', '{$status}')
ON DUPLICATE KEY UPDATE `log_text`='{$text}',`status_id`='{$status}';
SELECT LAST_INSERT_ID() as id;
When this code is executed from phpmyadmin it runs successfully and returns the id, however when executed from FuelPHP it only returns 1, which I assume means a successful operation.
The FuelPHP code:
public static function updateLogById($id, $wo_id, $text, $status)
{
try {
$log_query = \DB::query("
UPDATE `work_orders` SET `status_id`='{$status}' WHERE `id` = '{$wo_id}';
INSERT INTO `work_order_logs`(`id`, `work_order_id`, `log_text`, `status_id`) VALUES ('{$id}', '{$wo_id}', '{$text}', '{$status}')
ON DUPLICATE KEY UPDATE `log_text`='{$text}',`status_id`='{$status}';
SELECT LAST_INSERT_ID() as id;
")->execute();
} catch(\Database_Exception $e) {
return array(false, \DBE::handle_error());
}
return array(true, $log_query);
}
Can anybody see, what's wrong?
Thanks for any answer.
separate the insert from the update that will fix it
In order to get the right result you'll have to supply the query type in this case because it's not designed to do super smart result type detection based on the query. I think your query should work when you supply the following second parameter:
$result = \DB::query($query, \DB::SELECT);
This should give you a Result object from which you can get the id.
I'm trying to write a php function which is going to put values to two diferent tables, but it dsn't work. How should I do?
public function AddKursplanering($kursBudgetId, $momentId, $momTypID, $pId, $rollId, $tid, $utfall)
{
if($stmt = $this->m_database->GetPrepareStatement(" INSERT INTO Kursmoment(KBID, MomentID, MomTypID)
VALUES(:kursBudgetId, :momentId, :momTypID)
INSERT INTO Uppgift (KMID, PID, RollID, Tid, Utfall)
VALUES (##IDENTITY, :pId, :rollId, :tid, :utfall)"))
{
$stmt->bindValue(':kursBudgetId', $kursBudgetId,PDO::PARAM_INT);
$stmt->bindValue(':momentId', $momentId,PDO::PARAM_INT);
$stmt->bindValue(':momTypID', $momTypID,PDO::PARAM_INT);
$stmt->bindValue(':pId', $pId,PDO::PARAM_INT);
$stmt->bindValue(':rollId', $rollId,PDO::PARAM_INT);
$stmt->bindValue(':tid', $tid,PDO::PARAM_INT);
$stmt->bindValue(':utfall', $utfall,PDO::PARAM_INT);
if($stmt->execute())
{
$stmt->CloseCursor();
return true;
}
return false;
}
}
If you are getting an error, post the error text
If it is not an error, but it is not working for some reason, state clearly what part doesn't work
Now, that should actually work, but use SCOPE_IDENTITY() instead of ##IDENTITY, which may return you an unrelated number if there are triggers involved.
Also, consider encapsulating such logic in a stored procedure with the parameters, so you can call a single proc that does both inserts (essentially the same code)
Without knowing the error message from the query above it look like you need to have a semi-colon between the statements. Without it, it will be interpreted as one query instead of two.
if($stmt = $this->m_database->GetPrepareStatement("
INSERT INTO Kursmoment(KBID, MomentID, MomTypID)
VALUES(:kursBudgetId, :momentId, :momTypID);
INSERT INTO Uppgift (KMID, PID, RollID, Tid, Utfall)
VALUES (##IDENTITY, :pId, :rollId, :tid, :utfall)"))
I am filtering null values, in php on MYSQL. When a null value is read, I need the MySQL to read the next record.
How do I go about doing that?
Why not filtering these nulls out at the source, i.e. in the SQL query.
By adding something like the following in the WHERE clause.
WHERE ... -- existing conditions
AND TheFieldOfInterest IS NOT NULL
Exactly as mjv already mentioned, you want to tell MySQL to skip over rows that have a NULL value in a particular column. As it stands in your question 'When a null value is read, I need the MySQL to read the next record.' : This is exactly what MySQL will do when you tell it not to include NULLs in the result set by specifying the WHERE condition.
Have fun hacking :)
In php you can use the is_null() function to detect whether a variable is null or not
$result = mysql_query("SELECT foo FROM bar;");
while($values = mysql_fetch_assoc($result))
{
if (is_null($values["foo"]))
continue; //Skip processing of this row
echo "Data: ".$values["foo"];
}
I agree that you shouldn't query all data and then filter the result set on the mysql-client (your php script). But: done that, but I "just" want to know another way :DThere's nothing wrong with being curious. And: More power to PDO and SPL, esp. FilterIterator in this case.
class ElementIssetFilter extends FilterIterator {
protected $index;
public function __construct(Iterator $iter, $index) {
parent::__construct($iter);
$this->index = $index;
}
public function accept() {
$c = $this->current();
return isset($c[$this->index]);
}
}
$pdo = new PDO('mysql:host=localhost;dbname=test', 'localonly', 'localonly');
$pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
// testtable and -data
$pdo->exec("CREATE TEMPORARY TABLE foo (id int auto_increment, v varchar(16), primary key(id))");
$pdo->exec("INSERT INTO foo (v) VALUES ('1'), (null), ('3'), ('4'), (null), ('6')");
$result = $pdo->query('SELECT id,v FROM foo');
$iter = new IteratorIterator($result);
$filterIter = new ElementIssetFilter($iter, 'v');
foreach( $filterIter as $e) {
echo $e['id'], " ", $e['v'], "\n";
}
$filterIter will act like $result, except that rows with NULL values in ['v'] will be filtered out. You don't have to change the "consuming" code, i.e. the same foreach-loop (or function/method call or whatever) would work with $result instead of $filterIter.