PDO inserts an id as 0 for some reason - php

So I wrote this method, for my chrome plugin (which does an ajax request to run this method), and when it runs, file_put_contents shows an id of what ever was inserted, but then when it gets to the insert ignore into songs, it puts in 0 for the artist_id. I have no idea why... Can someone help my find the part where I am going wrong?
<?php
public function saveLyrics($artist, $title, $lyric){
$this->db->query("insert ignore into artists (artist_name) value (:artist)", array("artist" => $artist));
$artist_id = (int)$this->db->insertID();
file_put_contents(__DIR__ . "/../process/page", "artist id: $artist_id");
//return;
if($artist_id == 0){
$artist_id = (int)$this->db->getOne("select artist_id from artists where artist_name = :artist", array("artist" => $artist));
}
if($artist_id == 0){
return false;
}
$this->db->query("insert ignore into songs (artist_id, song_name) values (:aid, :title)", array("aid" => $artist_id, "title" => $title));
$song_id = (int)$this->db->insertID();
if($song_id == 0){
$song_id = (int)$this->db->getOne("select song_id from songs where artist_id = aid and song_name = :title", array("aid" => $artist_id, "title" => $title));
}
}
PDO Wrapper:
<?php
/**
* #property PDO $pdo Description
* #property PDOStatement $sql Description
*/
class DB{
protected $sql = null;
protected $pdo = null;
public function connect(){
$this->pdo = new PDO("mysql:dbname=envne;host=xxx", "xxx", "xxx");
}
public function query($query, $params = array()){
if($this->pdo === null){
$this->connect();
}
$this->sql = $this->pdo->prepare($query);
foreach($params as $key => $value){
$this->sql->bindParam($key, $value);
}
$this->sql->execute();
if(!$this->sql)
return false;
return true;
}
public function insertID(){
return (int)$this->pdo->lastInsertId();
}
public function getAll($query, $params = array()){
$this->query($query, $params);
return $this->sql->fetchAll();
}
public function getOne($query, $params = array()){
$this->query($query, $params);
return $this->sql->fetchColumn();
}
}
Artists:
mysql> describe artists;
+-------------+------------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------------+------+-----+-------------------+----------------+
| artist_id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| artist_name | char(50) | YES | UNI | NULL | |
| add_date | timestamp | YES | | CURRENT_TIMESTAMP | |
+-------------+------------------+------+-----+-------------------+----------------+
3 rows in set (0.00 sec)
Songs:
mysql> describe songs;
+------------+------------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+-------------------+----------------+
| song_id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| artist_id | int(11) unsigned | YES | MUL | NULL | |
| album_id | int(11) | YES | MUL | NULL | |
| song_name | char(50) | YES | | NULL | |
| track_id | int(11) | YES | | NULL | |
| date_added | timestamp | NO | | CURRENT_TIMESTAMP | |
+------------+------------------+------+-----+-------------------+----------------+
6 rows in set (0.01 sec)

I just decided to put the Id directly into the query, and that works.
$artist_id = (int)$this->db->insertID();
$this->db->query("insert ignore into songs (artist_id, song_name) values ($artist_id, :title)", array("title" => $title));
Another way that works is using a question mark instead
$artist_id = (int)$this->db->insertID();
$this->db->query("insert ignore into songs (artist_id, song_name) values (?, ?)", array($artist_id, $title));

I just had the same problem : new inserted items get an id of 0 even though the ID field is set to AUTO_INCRIMENT.
The solution I found is very similar to yours. Using your code this is what we get :
$this->db->query("insert ignore into songs (artist_id, song_name) values (LAST_INSERT_ID(), :title)", array("title" => $title));
As you can see, I replaced $artist_id = (int)$this->db->insertID(); and $artist_id with SQL function LAST_INSERT_ID().
I hope this can help someone someday :)

You're placeholders are incorrectly defined: (You're missing the colons)
I would do something like this:
public function saveLyrics($artist, $title, $lyric){
$this->db->query("insert ignore into artists (artist_name) value (:artist)", array(":artist" => $artist));
$artist_id = (int)$this->db->insertID();
file_put_contents(__DIR__ . "/../process/page", "artist id: $artist_id");
//return;
if($artist_id == 0){
$artist_id = (int)$this->db->getOne("select artist_id from artists where artist_name = :artist", array(":artist" => $artist));
return false;
}
$this->db->query("insert ignore into songs (artist_id, song_name) values (:aid, :title)", array(":aid"=>$artist_id, ":title"=>$title));
$song_id = (int)$this->db->insertID();
if($song_id == 0){
$song_id = (int)$this->db->getOne("select song_id from songs where artist_id = :aid and song_name = :title", array(":aid"=>$artist_id, ":title"=>$title));
}
}
Taking a look at your PDO-wrapper you have this code:
if(!$this->sql)
return false;
Because of this you would never notice an actual error. I guess the error is about the placeholder in this case.
(If $this->db->query("insert ignore into songs (... fails $song_id would just be false if there is an error when executing the query).
Use exceptions instead and catch the errors, that would be better.
I also noticed that:
$song_id = (int)$this->db->insertID();
would cast the value twice, first in above code and then in the actual function insertID() in the PDO-Wrapper. Maybe this is an issue also to consider.

Related

How to update data as null in mysql using php loop?

I have a table named users with a column called user_subs. It looks like this.
In user_subs I have stored the specific users session username. Lets say this specific users name is James.
Now how would I loop through a specific user_subs looking for "James" and remove him from that specific user_subs without removing all the other names.
This is what I have so far and the only problem is, its deleting all the usernames in user_subs instead of just "James".
if(isset($_GET['p_id'])) {
$the_post_id = $_GET['p_id'];
$the_post_author = $_GET['author'];
}
if(isset($_POST['delete_sub'])) {
$username = $_SESSION['username'];
$query = "SELECT user_subs FROM users WHERE username = '{$username}' ";
$select_users_by_id = mysqli_query($connection, $query);
while ($row = mysqli_fetch_array($select_users_by_id)) {
$user_subs = explode(',', $row['user_subs']);
foreach($user_subs as $out) {
$query = "UPDATE users SET user_subs = null WHERE username = '{$the_post_author}' ";
$unsubscribe_user = mysqli_query($connection, $query);
echo "Unsubscribed";
}
}
}
THIS IS JUST IN TEST, PREPARED STATEMENTS WILL BE USED BEFORE GOING LIVE
Thank you for your time.
I second the other user's comment about moving this column to a different table. In the meanwhile, if you want to achieve what you are asking for, you can try removing the user name from the column value and update it with the remaining text.
if(isset($_POST['delete_sub'])) {
$username = $_SESSION['username'];
$query = "SELECT user_subs FROM users WHERE username = '{$username}' ";
$select_users_by_id = mysqli_query($connection, $query);
while ($row = mysqli_fetch_array($select_users_by_id)) {
$user_subs = str_replace($username . ',', '', $row['user_subs']);
$query = "UPDATE users SET user_subs = '{$user_subs}' WHERE username = '{$the_post_author}' ";
$unsubscribe_user = mysqli_query($connection, $query);
echo "Unsubscribed";
}
}
OPTION-2
$user_subs = explode(',', $row['user_subs']);
$user_subs_new = [];
foreach($user_subs as $out) {
if ($out !== $username) {
$user_subs_new[] = $out;
}
}
$user_subs = implode(',',user_subs_new);
$query = "UPDATE users SET user_subs = '{$user_subs}' WHERE username = '{$username}' ";
$unsubscribe_user = mysqli_query($connection, $query);
echo "Unsubscribed";
}
Let's start over. Let's start here, in fact...
DROP TABLE IF EXISTS users;
CREATE TABLE users
(user_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,email VARCHAR(20) NOT NULL UNIQUE
);
DROP TABLE IF EXISTS user_subs;
CREATE TABLE user_subs
(user_id INT NOT NULL
, sub_id INT NOT NULL
, active TINYINT NOT NULL DEFAULT 1
, PRIMARY KEY(user_id,sub_id)
);
INSERT INTO users VALUES
(1,'b.smyth634#gmail.com'),
(2,'james#gmail.com'),
(3,'f#gmail.com'),
(4,'sally#gmail.com'),
(5,'thomas#gmail.com');
INSERT INTO user_subs (user_id,sub_id) VALUES
(1,5),
(1,2),
(1,1),
(1,4),
(2,1),
(2,2),
(2,4);
SELECT * FROM users;
+---------+----------------------+
| user_id | email |
+---------+----------------------+
| 1 | b.smyth634#gmail.com |
| 2 | james#gmail.com |
| 3 | f#gmail.com |
| 4 | sally#gmail.com |
| 5 | thomas#gmail.com |
+---------+----------------------+
SELECT * FROM user_subs;
+---------+--------+--------+
| user_id | sub_id | active |
+---------+--------+--------+
| 1 | 5 | 1 |
| 1 | 2 | 1 |
| 1 | 1 | 1 |
| 1 | 4 | 1 |
| 2 | 1 | 1 |
| 2 | 2 | 1 |
| 2 | 4 | 1 |
+---------+--------+--------+
SELECT u.*
, GROUP_CONCAT(us.sub_id) subs
FROM users u
JOIN user_subs us
ON us.user_id = u.user_id
GROUP
BY u.user_id;
+---------+----------------------+---------+
| user_id | email | subs |
+---------+----------------------+---------+
| 1 | b.smyth634#gmail.com | 1,2,4,5 |
| 2 | james#gmail.com | 1,2,4 |
+---------+----------------------+---------+
From here we have a choice. We can either DELETE subs we no longer wish to consider, or simply UPDATE them as 'inactive'.
Either way, we just need a DELETE or an UPDATE. So no SELECT needed. In fact a SELECT would, as I mentioned, be counterproductive - because a user may modify the data set in between the execution of the SELECT and the execution of the UPDATE/DELETE. This is known as a 'race condition'.

using one key to multiple values in php

I'm developing a webaplicattion in php where there will be an area called 'My Products'. This area you can see the products that you listed. Lets say you placed a car to sell. You will have something like:
Model: R8
Color: Yellow
Brand: Audi
Type: Diesel
Price: 90000
CarID: 1
My problem: How can I select the carid so that I have the cardid as the key and the other values (eg. model, color, etc...) as values?
I'm using mysql to store the listings. SQL TABLE:
+---------+------------------------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+------------------------------------------+------+-----+---------+----------------+
| carid | int(11) | NO | MUL | NULL | auto_increment |
| brand | enum('Alfa Romeo','Aston Martin','Audi') | NO | | NULL | |
| color | varchar(20) | NO | | NULL | |
| type | enum('gasoline','diesel','eletric') | YES | | NULL | |
| price | mediumint(8) unsigned | YES | | NULL | |
| mileage | mediumint(8) unsigned | YES | | NULL | |
| model | text | YES | | NULL | |
| year | year(4) | YES | | NULL | |
| user | varchar(30) | YES | | NULL | |
+---------+------------------------------------------+------+-----+---------+----------------+
My pseudo code is something like this:
Fetch ID of the cars that was listed by certain user;
Use that ID as a key;
Get every other options to be the values (diesel, yellow, etc...)
I have tried some codes since array_combine to foreach inside foreach.
This is the code I have ended up so far:
$qry_id = "select carid from cars where user='$login'";
if ($car_id = mysqli_query($link, $qry_id)){
while ($row = mysqli_fetch_assoc($car_id)){
//It will fetch the iD of the user in the DB
$user_id = $row["carid"];
$qry_model = "select model from cars where carid='$user_id'";
if($model_obj = mysqli_query($link, $qry_model)){
while ($row_2 = mysqli_fetch_assoc($model_obj)){
$model = $row_2['model'];
$final_array = array($user_id => $model);
}
}
}
}
I don't know if this is the right approach since it's my first webapp I'm building.
I'm just using the model value in this code for the simplicity sakes.
$q = "SELECT * FROM cars where user=?";
if ($stmt = $pdo->preapre($q)){
$result=[];
$stmt->execute([$login]);
while ($row = $stmt->fetchObject()){
$carId = $row->carid;
unset($row->carid);
$result[$carId]=$row;
}
}
now note that $pdo is object of pdo connecttion to the database not mysqli, you can do the same thing with mysqli but i am not uses to it
also i don't recommend that you use the user name on every column you should instead store the userId as foreign key to the primary key id in the table user
that will save alot more storage make the query faster ( it's easier to look for numbers than string ) and user can change it's name without having to change his name in all other tables ( userid won't be changing of course )
also carid should be unsigned int(10) not int(11) if wondering why 10 see that post MySQL datatype INT(11) whereas UNSIGNED INT(10)?
To avoid SQL injection, use prepared statements. You can use one query to fetch all attributes for the cars:
$qry_id = "select carid, model, price, color from cars where user=?";
$stmt = mysqli_prepare($link , $qry_d) or die("SQL statement error");
// Bind the login parameter to the statement
mysqli_stmt_bind_param($stmt, "s", $login);
mysqli_stmt_execute($stmt);
// bind every column in the SELECT
mysqli_stmt_bind_result($stmt, $user_id, $carid, $model, $price, $color);
while (mysqli_stmt_fetch($stmt)){
$final_array[] = array(
"model" => $model,
"price" => $price,
"color" => $color
);
}
You may do the following
Note this query is not safe and pron to SQL Injection, I would recommend to use prepared statements or PDO
The $carArray variable will finally have the array with carid as key in it
$query = "select * from cars where user='$login'";
$result = mysqli_query($query);
$carArray = array();
while ($row = mysqli_fetch_assoc($result)){
$carArray[$row['carid']] = $row;
}

Query returns one post late

So I have a query that gets all the posts in the database. Say I post a post, it won't show, till I post another post. So essentially there's a post delay, until you post another post. That's the best I can explain it. Here's my query
SELECT * FROM comments WHERE comment_post_id = :id
Then I create an array
//Get post comments
$cmt = $con->prepare("SELECT * FROM comments WHERE comment_post_id = :id");
$cmt->bindValue(':id', $id, PDO::PARAM_INT);
$cmt->execute();
$cmtc = $cmt->fetch(PDO::FETCH_ASSOC);
$comments = array();
while($row = $cmt->fetch(PDO::FETCH_ASSOC)){
$comments[] = array(
'comment_user' => $row['comment_user'],
'comment_ip' => $row['comment_ip'],
'comment_date' => $row['comment_date'],
'comment_content' => $row['comment_content'],
'comment_post_id' => $row['comment_post_id']
);
}
And finally display it
<?php foreach($comments as $comment){?>
<h1 class="message"><?php echo $comment['comment_content']?></h1>
<?php } ?>
I'm not getting any errors, but when I run the query in the command line I get one result. But when I visit the page I don't see any results.
Here's the structure
+-----------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+------------------+------+-----+---------+----------------+
| id | int(11) unsigned | NO | PRI | NULL | auto_increment |
| comment_user | varchar(255) | YES | | NULL | |
| comment_ip | varchar(255) | YES | | NULL | |
| comment_date | varchar(255) | YES | | NULL | |
| comment_content | varchar(85) | YES | | NULL | |
| comment_post_id | varchar(255) | YES | | NULL | |
+-----------------+------------------+------+-----+---------+----------------+
Your first call to fetch automatically increments the internal pointer of the result set by one after fetching the row:
$cmtc = $cmt->fetch(PDO::FETCH_ASSOC);
The above returns the zeroth row and moves the pointer to the first row. So, when you get to your while loop, the pointer is offest by one, and nothing is found:
while($row = $cmt->fetch(PDO::FETCH_ASSOC)){
The reason is that line:
$cmtc = $cmt->fetch(PDO::FETCH_ASSOC);
You should simple remove it.
Otherwise you fetch first record and don't assign it to $comments variable and then in loop you try to get records from 2 to n
To fetch all results of your query try this:
$cmt = $con->prepare("SELECT * FROM comments WHERE id = :id");
$cmt->bindValue(':id', 1);
$cmt->execute();
$comments = $cmt->fetchAll(PDO::FETCH_ASSOC);
PS. your schema use id as primary key, but your query use comment_post_id.

Error in mysqli->prepare with insert statement

I'm trying to figure out why my first prepare statement works just fine but my second one doesn't. The actual INSERT INTO syntax looks correct and works when I substitute numbers and place it in a mysql console, but the prepare statement returns false.
Just to clear away the simple questions; $db_table_prefix == "uc_" and all variables are initialized. Also, the first statement sets $results == 0 (EDIT: this was my mistake, it's really 0, not 1).
global $mysqli,$db_table_prefix;
$stmt = $mysqli->prepare("SELECT COUNT(id) FROM ".$db_table_prefix."attempts WHERE ((exp_m = ?) AND (exp_n = ?) AND (max_base <= ?))");
$stmt->bind_param("iii", $m, $n, $this->max_base);
$stmt->execute();
$stmt->bind_result($results);
$stmt->fetch();
if ($results < 1)
{
$stmt = $mysqli->prepare("INSERT INTO ".$db_table_prefix."attempts (exp_m, exp_n, base_x, max_base) VALUES (?,?,?,?)");
$stmt->bind_param("iiii", $m, $n, $x, $this->max_base);
.....
}
I've included the table structure, just in case that's the issue.
mysql> describe uc_attempts;
+----------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+---------------------+------+-----+---------+----------------+
| exp_m | bigint(20) unsigned | NO | | NULL | |
| exp_n | bigint(20) unsigned | NO | | NULL | |
| base_x | bigint(20) unsigned | YES | | NULL | |
| max_base | bigint(20) unsigned | NO | | NULL | |
| id | int(11) | NO | PRI | NULL | auto_increment |
+----------+---------------------+------+-----+---------+----------------+
I'm sure I'm just missing something simple, but after a few days of staring at the code, I needed to ask. Thanks in advance for the help. Please let me know if there is any other information I should include.
You forgot to close the first resource. I believe it can't open a second prepared statement if the first one isn't closed. I think $mysqli->prepare( ... ); will then return false and obviously false->bind_param( ... ); does not exist ;-) causing your Fatal error: Call to a member function bind_param() on a non-object-error.
global $mysqli,$db_table_prefix;
$stmt = $mysqli->prepare("SELECT COUNT(id) FROM ".$db_table_prefix."attempts WHERE ((exp_m = ?) AND (exp_n = ?) AND (max_base <= ?))");
$stmt->bind_param("iii", $m, $n, $this->max_base);
$stmt->execute();
$stmt->bind_result($results);
$stmt->fetch();
$stmt->close(); //<-- this is the problem
if ($results < 1)
{
$stmt = $mysqli->prepare("INSERT INTO ".$db_table_prefix."attempts (exp_m, exp_n, base_x, max_base) VALUES (?,?,?,?)");
$stmt->bind_param("iiii", $m, $n, $x, $this->max_base);
.....
}

need advice on mysql normalization and php class structure

i am trying to build a system that logs, media (photo, video, audio) upload statistic. so i come up with 3 tables, 1 for audio, 1 for video, and 1 for photo. and here's the structure
+-----------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| member_id | int(10) | NO | | NULL | |
| counter | int(11) | NO | | NULL | |
| daydate | text | NO | | NULL | |
| epochtime | text | NO | | NULL | |
+-----------+---------+------+-----+---------+----------------+
all three tables has the same fields, since i think (so far) i need to differentiate the medias on each and particular table, is this redundant ??
anyway since each of the media is treated the same, so i think i should only build once class and kinda use the methods depending of what media i am accessing at the time. here's the class :
require_once(INC_PATH.DS.'database.php');
class Log extends DatabaseObject {
protected static $db_fields = array('id', 'member_id', 'counter', 'daydate', 'epochtime');
public $id;
public $member_id;
public $counter;
public $daydate;
public $epochtime;
public function find_counter($table_name){
global $database;
$time = date('d-m-Y');
$timestamp = time();
$sql = "SELECT * FROM ". $table_name;
$sql .= " WHERE daydate = '".$this->daydate."'";
$sql .= " AND member_id = '".$this->member_id."'";
return self::find_by_sql($sql);
}
public function add_log($table_name){
global $database;
$tes = $this->find_counter();
if(!empty($tes)){
$sql = "UPDATE ".$table_name;
$sql .= " SET counter = counter+1";
$sql .= " WHERE daydate = '".$this->daydate."'";
$sql .= " AND member_id = '".$this->member_id."'";
$database->query($sql);
return ($database->affected_rows() == 1) ? true : false;
}else{
$sql = "INSERT INTO ".$table_name;
$sql .= " (id, member_id, user_privelege, counter, daydate, epochtime)";
$sql .= " VALUES ('', '".$this->member_id."'";
$sql .= " , '".$this->user_privelege."', '1', '".$this->daydate."', '".$this->epochtime;
$sql .= "')";
$database->query($sql);
return ($database->affected_rows() == 1) ? true : false;
}
}
}
so far this is pretty much working but, i still have doubts on the way i break the table in to 3 tables, and the way i use the $table_name as parameter. any suggestions for better approach ?? i really want to improve my codes, thank you very much
Regards
UPDATE
alright, based on everybody's answer, i would like to clear somethings up:
i only need to log the uploaded media
i already have another separate table to record other information (length, filename, titles, etc) for each media.
lets just say i add a field called "media_id" and merge the table into one, and since the way that i add and update my rows is based on the day-date, it will INSERT if the given daydate and the member_id is not found, and otherwise it will UPDATE, i think i should also change the way my add_log() method works, into something like:
public function add_log($table_name, $media_id){
global $database;
$tes = $this->find_counter();
if(!empty($tes)){
$sql = "UPDATE ".$table_name;
$sql .= " SET counter = counter+1";
$sql .= " WHERE daydate = '".$this->daydate."'";
$sql .= " AND member_id = '".$this->member_id."'";
$sql .= " AND media_id = '".$media_id."'";
$database->query($sql);
return ($database->affected_rows() == 1) ? true : false;
}else{
$sql = "INSERT INTO ".$table_name;
$sql .= " (id, member_id, media_id, counter, daydate, epochtime)";
$sql .= " VALUES ('', '".$this->member_id."'";
$sql .= " , '".$media_id."', '1', '".$this->daydate."', '".$this->epochtime;
$sql .= "')";
$database->query($sql);
return ($database->affected_rows() == 1) ? true : false;
}
}
what do you guys think ??
thanks again
Put it all in one table and have a media_type column.
+-----------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| member_id | int(10) | NO | | NULL | |
| counter | int(11) | NO | | NULL | |
| daydate | text | NO | | NULL | |
| epochtime | text | NO | | NULL | |
| media_type| int | NO | | NULL | |
+-----------+---------+------+-----+---------+----------------+
Create a foreign key relationship to another table that defines media_type.
+-----------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------+------+-----+---------+----------------+
| media_type_id| int | NO | PRI | NULL | |
| description | text | NO | | NULL | |
+-----------+---------+------+-----+---------+----------------+
This would hold media_type_id (e.g. 1) and media_type_description (e.g. audio).
e.g.
1, audio
2, video
3, photo
The main table would then just include on each row the id of 1, 2, 3 to denote which media type it is. You can then use this from your application to constrain via the WHERE clause, which media you're referring to. e.g. SELECT * FROM maintable WHERE media_type_id = 3 to just get the photos.
Make it one table with an extra column "media_type". Your design would require you to make an extra table for every new media type and that is possibly poor design.

Categories