Inserting values into a table under conditions - php

Im trying to insert two values into a table if one condition is met and another is not met.
I've found a tutorial on the matter but i can't seem to get it to work.
The tutorial explains how to create a simple PHP like button, and has two tables, articles and articles_likes.
articles has two columns: id and title.
articles_likes has three columns: id, user and article.
The code in the tutorial looks like this:
$db->query("
INSERT INTO articles_likes (user, article)
SELECT {$_SESSION['user_id']}, {$id}
FROM articles
WHERE EXISTS (
SELECT id
FROM articles
WHERE id = {$id})
AND NOT EXISTS (
SELECT id
FROM articles_likes
WHERE user = {$_SESSION['user_id']}
AND article = {$id})
LIMIT 1
");
Now first of all, im using PDO with $query = $pdo->prepare(" .. "); and question marks plus bindValue() to avoid SQL injections, and all that is working fine with other SQL statements, but this one does not seem to work.
I've googled the INSERT INTO .. SELECT .. FROM syntax, and W3schools explains it as copying values from one table into another one. So how is this even working in the tutorial? articles has a completely different structure, and he is inserting $variables into the SELECT statement.
Can anyone explain why this works in the first place, and how it would work in PDO?
Edit:
Here is my own code (I've added $value because my code is for a binary rating instead of a like):
global $pdo;
$query = $pdo->prepare("
INSERT INTO quote_ratings (user_ip, quote_id, value)
SELECT ?, ?, ?
FROM posts
WHERE EXISTS (
SELECT id
FROM posts
WHERE id = ?)
AND NOT EXISTS (
SELECT id
FROM quote_ratings
WHERE user_ip = ?
AND quote_id = ?)
LIMIT 1
");
$query->bindValue(1, $user_ip);
$query->bindValue(2, $quote_id);
$query->bindValue(3, $rating);
$query->bindValue(4, $quote_id);
$query->bindValue(5, $user_ip);
$query->bindValue(6, $quote_id);
$query->execute();

Some kind of nested SQL queries did not get bind properly and something is left by the PDO parser during the processing of query. Also there is no way to see if the the final query generated by PDO.
I will recommend to use Mysqli library to use in such scenarios. I usually encounter such issues during complex joins or executions of custom triggers. If you want to go ahead with object oriented code you can use MySQLi Class or also you could use simple procedural approach.
Atul Jindal

I i completely agree that PDO is good choice to avoid sql injections but, you know at such situations you are unable to debug your queries. But if you use mysqli wisely and properly check sql injections when you input, then there will not be a problem.

I've found the problem: PDO, by default, uses unbuffered queries. This for some reason makes the request impossible to parse. So the mode has to be changed for this request:
$dbh->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
and everything works fine from then on!

Related

How can I use an SQL query's result for the WHERE clause of another query?

Okay, basically I have a table that contains statements like:
incident.client_category = 1
incident.client_category = 8
incident.severity = 1
etc.
I would like to use the contents from this table to generate other tables that fulfill the conditions expressed in this one. So I would need to make it something like
SELECT * FROM incident WHERE incident.client_category = 1
But the last part of the where has to come from the first table. Right now what I'm trying to do is something like
SELECT * FROM incident WHERE (SELECT condition FROM condition WHERE id = 1)
id = 1 stands for the condition's id. Right now I only want to work with ONE condition for testing purposes. Is there a way to achieve this? Because if there isn't, I might have to just parse the first query's results through PHP into my incident query.
Table schemas:
Engineering Suggestion - Normalize the DB
Storing a WHERE clause, like id = 10, in a field in a MySQL table, is not a good idea. I recommend taking a look at MySQL Normalization. You shouldn't store id = 10 as a varchar, but rather, you should store something like OtherTableid. This allows you to use indices, to optimize your DB, and to get a ton of other features that you are deprived of by using fields as WHERE clauses.
But sometimes we need a solution asap, and we can't re-engineer everything! So let's take a look at making one...
Solution
Here is a solution that will work even on very old, v. 5.0 versions of MySQL. Set the variable using SET, prepare a statement using PREPARE, and execute it using EXECUTE. Let's set our query into a variable...
SET #query = CONCAT(
"SELECT * FROM incident WHERE ",
(SELECT condition FROM condition WHERE id = 1)
);
I know for a fact that this should work, because the following definitely works for me on my system (which doesn't require building any new tables or schema changes)...
SET #query = CONCAT("SELECT id FROM myTable WHERE id = ", (SELECT MAX(id) FROM myTable));
If I SELECT #query;, I get: SELECT id FROM myTable WHERE id = 1737901. Now, all we need to do is run this query!
PREPARE stmt1 FROM #query;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
Here we use a prepare to build the query, execute to execute it, and deallocate to be ready for the next prepared statement. On my own example above, which can be tested by anyone without DB schema changes, I got good, positive results: EXECUTE stmt1; gives me...
| id | 1737901 | .
here is one way to achieve your goal by using what is called dynamic sql, be ware that this works only select from condition table returns only one record.
declare #SQLSTRING varchar(4000)
, #condition VARCHAR(500) -- change the size to whatever condition column size is
SELECT #condition = condition
FROM
condition
WHERE
id = 1
SET #SQLSTRING= 'SELECT * FROM incident WHERE ' + #condition
exec sp_executesql(#SQLSTRING)
Since you have also tagged the question with PHP, I would suggest using that. Simply select the string from the condition table and use the result to build up a SQL query (as a string in PHP) including it. Then run the second query. Psudo-code (skipping over what library/framework you re using to call the db):
$query = "select condition from condition where id = :id";
$condition = callDbAndReturnString($query, $id);
$query = "select * from incident where " . $condition;
$result = callDb($query);
However, be very careful. Where and how are you populating the possible values in the condition table? Even how is your user choosing which one to use? You run the risk of opening yourself up to a secondary SQL injection attack if you allow the user to generate values and store them there. Since you are using the value from the condition table as a string, you cannot parametrise the query using it as you (hopefully!) normally would. Depending on the queries you run and the possible values there as conditions, there might also be risk even if you just let them pick from a pre-built list. I would seriously ask myself if this (saving parts of SQL queries as strings in another table) is the best approach. But, if you decide it is, this should work.

How to create a pdo mysql prepared statement with a placeholder in a custom equation?

I am implementing a rating system that was similar to what you see in google play and many others. To do that, I created a table named tbl_ratings and columns PRIMARY, IID and sum_rating. Off course, the PRIMARY column is in auto-increment key, the IID is the item id and sum_rating is the cumulative sum of all user ratings.
So the tbl_ratings table can look like this:
PRIMARY IID sum_rating
21 2 100
Now this is what I intend to do, I want to avoid doing SELECT just to retrieve a value for use in PHP, since I can do a simple addition from within an update query, so each time there is a user that submits a rating, I will update the table following this:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);
$stmt = $pdo->prepare("UPDATE tbl_ratings SET sum_rating=sum_rating+".$_POST['rating']." WHERE IID='2'")
$stmt->execute();
But of course as you know, this is a bad implementation cause this is open to SQL Injection. But hey it work! So now I want to do it much safer by doing
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);
$stmt = $pdo->prepare("UPDATE tbl_ratings SET sum_rating=sum_rating+? WHERE IID='2'")
$stmt->execute(array($_POST['rating']));
If you notice its quite simple, I just replace the potential sql injection point by the ? placeholder as would a correct prepared statement should be constructed. The Bad Bad thing is, this does not work. I tried looking in the web but it seems to be not that fruitful.
What should be the proper way to achieve the calculation of a known column value plus a data in a 1 pdo prepared statement?
All values convert to string values in execute method. You must use bindParam method to set a value type, for example:
$sth->bindParam(1, $_POST['rating'], PDO::PARAM_INT);

SELECT and INSERT not selecting

When I execute
INSERT INTO `survey`
(`id`,`name`,`date_start`,`date_end`)
values
(:id,:name,NULL,DATE_ADD(NOW(), INTERVAL 1 MINUTE))
on duplicate key UPDATE `name`=:name;
SELECT coalesce(:id,LAST_INSERT_ID()) as 'id'
it inserts a new data fine, but doesn't select the id (which is needed later on in my php code)
I've tried this suggestion
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
but this SQL throws errors (due to duplicate parameters)
SELECT ASCII(substr(`perm` FROM floor(:i/8)+1))&(1<<(:i%8))>0 as perm FROM `user` WHERE `id`=:id
I'm in a lose-lose situation, re-writing all my SQL code to not have duplicate parameters would be very messy, doing a separate select straight after inserting may not return the id I want. Any suggestions would be great
You cannot run two queries at the same time, only one at the time.
If you want to do the whole thing at once then create a stored procedure.
Same goes for complex queries, when it gets complicated you want to have your logic in the database.
Here is an example:
DELIMITER //
CREATE PROCEDURE sp_insert_survey(IN `p_id`,
IN `p_name`,
IN `p_date_start`,
IN `p_date_end`)
BEGIN
INSERT INTO `survey`(`id`,`name`,`date_start`,`date_end`)
VALUES (p_id, p_name, p_date_start, p_date_end);
SELECT `id`,`name`,`date_start`,`date_end`
FROM survey WHERE `id` =LAST_INSERT_ID();
END //
DELIMITER ;
Call the sp from PDO:
$stmt = $db->prepare('CALL sp_insert_survey(?, ?, ?, ?)');
then fetch the data as a SELECT query.
Upon typing this up, one of the similar questions that came up on the right getting last inserted row id with PDO (not suitable result) gave a suitable answer in the question itself, although I'm a little dubious considering the method is being questioned itself.
$db->lastInsertId();
Seems to work for me, but in the question linked it isn't working as desired for the questioner, so I'm not entirely settled with this answer. It does seem to be a bit of a hack.

mySQL Query - Not inserting

I have a problem (obviously), the sql query is not successfully inserting into the table;
This is my code:
if (isset($_POST["newpost"])) {
$sqlSTR = "SELECT idPhoto FROM tblhomepagephotos order by idPhoto desc LIMIT 1 INTO #myID;
INSERT INTO tblhomepagetext(idText, hpText)
VALUES (#myID, '" . $_POST["newpost"] . "')";
echo $sqlSTR;
$result= mysql_query($sqlSTR);
}
The echo for the sqlSTR is:
SELECT idPhoto FROM tblhomepagephotos order by idPhoto desc LIMIT 1 INTO #myID;
INSERT INTO tblhomepagetext(idText, hpText) VALUES (#myID, 'Text here')
Now the problem is that it worked perfectly and inserts into tblhomepagetext perfectely when executed from mySQL Workbench, but doesn't work when executed from the website.
Any ideas of why?
I thought it might be due to some PHP conflict where theres the ';' in the sql query.
Your using the INSERT INTO SELECT syntax incorrectly, you have the order swapped around. It should be more like:
INSERT INTO Customers (CustomerName, Country)
SELECT SupplierName, Country FROM Suppliers
WHERE Country='Germany';
http://www.w3schools.com/sql/sql_insert_into_select.asp
However, I suspect you don't require INSERT INTO SELECT at all. If you simply need to INSERT into a table then the syntax is more like this:
INSERT INTO Customers (CustomerName, City, Country)
VALUES ('Cardinal', 'Stavanger', 'Norway');
http://www.w3schools.com/sql/sql_insert.asp
I suggest you read through those links before proceeding
Edit: Also that semi colon in your example should not be there, that is separating the command in two. This is the reason mySQL workbench can perform the query. It is literally performing two queries sequentially. First it selects some value from your tblhomepagephotos table, then it inserts into tblhomepagephotos. You need to combine the queries as I show above. Then, PHP will be able to perform the single query.
Edit2:
There are many issues with your code its hard to tell whether you need INSERT INTO SELECT or not because I can't figure out your logic.
What I suggest you do is read more examples of howto perform basic CRUD interaction with your database (http://en.wikipedia.org/wiki/Create,_read,_update_and_delete). I suggest you might require to first SELECT your photo details, THEN insert into the tblhomepagetext table. but firstly just see if the below code works. BTW, at very least you should use mysql_real_escape_string() as below when inserting a value via post into your database with mysqli . better yet is to use something like PDO, or learn an entire PHP framework such as Cake, Codeigniter or Zend Framework. These are all solutions to help with exposure to SQL Injection
From your latest comments, here is a solution i think your after:
// PERFORM Base64 PHOTO INSERT QUERY HERE
// (I assume your already doing this as you mention from your Base64 comment.)
// Directly following the insert image query, you need to use the magical command of `mysqli_insert_id()`.
// This will grab the latest inserted Database ID from the previous INSERT command.
$latest_photo_id = mysqli_insert_id();
// now that we have the latest photo ID we can insert into our homepage table
if (isset($_POST["newpost"])) {
$your_id = 1; // put a ID from your photo table here.
$sqlSTR = "INSERT INTO tblhomepagetext(idText, hpText)
VALUES (".$latest_photo_id.", '" . mysql_real_escape_string($_POST["newpost"]) . "')";
echo $sqlSTR;
$result= mysqli_query($sqlSTR);
}

What are the benefits of using Query Builders

Pardon my ignorance on the matter but what's the point of using Query Builders? Isn't it far more succinct to write one line of SQL instead of 3 lines of AR code:
$this->db->query(" SELECT title, contents FROM data WHERE id = 2 ");
Instead of:
$this->db->select('title, contents');
$this->db->from('data');
$this->db->where('id', 2);
It just seems more verbose to me but then again I know nothing about Query Builders so I could be missing something. Would really like to know what the benefits are.
If you need to programatically build your query CodeIgniter's Active Records are far more comfortable than plain text,
$id = 2;
//with CodeIgniter's Active Record
$this->db->select('title, contents');
$this->db->from('data');
if(isset($id))
$this->db->where('id', $id);
//without CodeIgniter's Active Record
if(isset($id))
$where = " WHERE id = {$id}";
$this->db->query(" SELECT title, contents FROM data".$where);
Ok, this isn't changing that much but what if you have 10 constraints on the where clause?
furthermore CodeIgniter's Active Record build the string in the right way (with placeholders ?) according to the data you pass i.e. you won't have to insert the ' manually on the query.
EDIT
#Col. Shrapnel said that there are no benefits with CodeIgniter's Active Record, since I'm not in agree with him I try to enforce my thesis with another example:
Let's do an example for an INSERT statement:
$array = array('A'=>'aaaa','B'=>'bbbb','C'=>'cccc');
//without CodeIgniter's Active Record
$query = "INSERT INTO my_table (";
$query.= implode(',',array_keys($array)) .')';
$query.= ......
//with CodeIgniter's Active Record
$this->db->insert('my_table',$array);
I see no benefits at all.
So did Dalen say, "this isn't chanching that much".
And with 10 constraint on the where clause AR just become even more werbose and messy, making you completely unable to grasp the meaning of the query. And there are no joins yet!
The only thing you really needed is support for placeholders.
With placeholders your queries become safe and easy to compose.
The style of programming you are complaining about should be impervious to SQL injection attacks. That's assuming that the DB interface you're talking to does sensible quoting and escaping, of course.

Categories