I wonder if there is a way to pass some values into the parameters option on the sqlsrv_query function. I tried a few things but could not get it to work.
This query is what I want to be executed:
SELECT id, name, etc
FROM sqlTable
WHERE id IN ('1', '2', '100', '314')
I want to pass the WHERE IN values using the params option, like this:
$q = "SELECT id FROM sqlTable WHERE id IN ?";
$p = array(array('1', '2', '100', '314'));
sqlsrv_query($connection, $q, $p);
Right now I'm passing the values directly into the query string, but for obvious security reasons I want to pass them as parameters into the function.
Anyone any idea on how to achieve this?
Consider PDO binded parameters which you can pass a defined array in execute(). However, you would need to prepare the statement, knowing number of IN() clause items in advance.
try {
$dbh = new PDO("sqlsrv:server=$server;database=$database",$username,$password);
$sql = "SELECT * FROM sqlTable WHERE id IN (:first, :second, :third, :fourth)";
$STH = $dbh->prepare($sql);
$nums = array('1', '2', '100', '314');
$STH->execute($nums);
}
catch(PDOException $e) {
echo $e->getMessage()."\n";
}
So I have figured out this issue on the sql side. Now I pass a comma separated string with the ids to the query using the params in the sqlsrv_query() function. The query sets the string in a temporarily variable. Using a splitting function every id is stored in a temporarily table. As last I JOIN the temporarily table with the table from witch I want to get the results.
Splitting function in SQL:
CREATE FUNCTION dbo.splitstring ( #stringToSplit VARCHAR(MAX) )
RETURNS
#returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN
DECLARE #name NVARCHAR(255)
DECLARE #pos INT
WHILE CHARINDEX(',', #stringToSplit) > 0
BEGIN
SELECT #pos = CHARINDEX(',', #stringToSplit)
SELECT #name = SUBSTRING(#stringToSplit, 1, #pos-1)
INSERT INTO #returnList
SELECT #name
SELECT #stringToSplit = SUBSTRING(#stringToSplit, #pos+1, LEN(#stringToSplit)-#pos)
END
INSERT INTO #returnList
SELECT #stringToSplit
RETURN
END
PHP code and SQL query:
$q = "
DECLARE #inStr varchar(max)
SET #inStr = ?
DECLARE #tmpTable table (tmpID varchar(200))
INSERT #tmptable (tmpID)
SELECT * FROM dbo.splitstring(#inStr)
SELECT id, name, etc
FROM sqlTable
JOIN #tmpTable ON id = tmpID";
$p = array('1,2,100,314');
sqlsrv_query($connection, $q, $p);
Related
I am trying to create a query inside a PDO script that checks if a record exists if it does the query should update the record and if it doesn't exist it should create a new one.
The column that should only exist once in the table is not an INDEX key (cannot make it unique right now) so it is not set as unique and I cannot use the ON DUPLICATE KEY UPDATE
I would like to use this queries logic below to make it work:
$stmt = $conn->prepare('IF EXISTS (SELECT * FROM `Table1` WHERE `code`= :code )
UPDATE `Table1`
SET `code_stat` = 2
WHERE code = :code
ELSE
INSERT INTO `Table1` (`code`,`code_stat`)
VALUES (:code, 2 ) ' );
$stmt->execute([
'code' => $_POST['code']
]);
The problem is when executing the query I get the following error saying there is a syntax problem:
SQL syntax; check the manual that corresponds
to your MySQL server version for the right syntax to use near
'IF EXISTS (SELECT * FROM Table1 WHERE code= ? ) UPDATE Table1' at line 1
If you can't add a unique key to the table, you can attempt an update first, and if that doesn't update any rows, do an insert. Something like this:
$stmt = $conn->prepare('UPDATE `Table1` SET `code_stat` = 2 WHERE code = :code');
$stmt->execute(array(':code' => $_POST['code']));
if (!$stmt->rowCount()) {
// no rows updated, so insert
$stmt = $conn->prepare('INSERT INTO `Table1` (`code_stat`, `code`) VALUES (2, :code)');
$stmt->execute(array(':code' => $_POST['code']));
}
Note that you may need to set the PDO::MYSQL_ATTR_FOUND_ROWS attribute to ensure that the UPDATE query returns 1 if it finds the row but the value doesn't change. You must set that attribute when you make the connection e.g.
$conn = new PDO($dsn, $user, $pass, array(PDO::MYSQL_ATTR_FOUND_ROWS => true));
Why not write a stored procedure to handle this, similar to the below:
DROP PROCEDURE IF EXISTS db.SP_NEW_CODE;
CREATE PROCEDURE db.`SP_NEW_CODE`(IN `in_code` INT)
BEGIN
DECLARE numFound INT DEFAULT 0;
SET numFound=(SELECT * FROM `Table1` WHERE `code`= in_code);
IF (numFound=0) THEN
INSERT INTO `Table1` (`code`,`code_stat`) VALUES (in_code, 2 );
ELSE
UPDATE `Table1` SET `code_stat` = 2 WHERE code = in_code
END IF;
END;
From your code, simple execute CALL SP_NEWCODE(3); (for example, where 3 is the appropriate code value).
My application performs INSERT queries like this:
INSERT INTO table (`col1`, `col2`, `col3`) VALUES ('oneVal', 'twoVal', 'threeVal')
Now I want to rebuild my application so it will ALWAYS SELECT, INSERT, DELETE and UPDATE with a specific id.
Let's say the unique id is called: companyId
I don't want to rewrite all my queries manually, so I am trying to write a function that rewrites the existing SQL queries with PHP so it will include the companyId inside the query.
Desired outcome if companyId would be '1' (companyId IS NOT ALWAYS '1'!):
INSERT INTO table (`col1`, `col2`, `col3`, `companyId`) VALUES ('oneVal', 'twoVal', 'threeVal', '1')
My question(s) is/are:
Is there a way in PHP so I can dynamically rewrite the query so
it would include the companyId column and the matching id value?
Is there a better way to do this? Like some trick setting MySQL
server to ALWAYS use an extra value (in this case companyId='1'
?
I've tried option (1) by searching for the string
) VALUES
Once I found that string, I add companyId before the ).
Now get to the end of the query, get the most right ) and add the value before that.
But is this for a generic case? I think there might be a better way to solve this.
Thanks in advance community!
EDIT 1 with more clarification
Currently I've already built a function that modifies my SELECT statements.
Function code:
//If current query = SELECT query
if (containsString($sql, 'select')) {
//Check if contains WHERE
if (containsString($sql, 'where')) {
//Yes
//Add companyId after WHERE
$sql = substr_replace($sql, '(companyId=?) AND ', strpos($sql, 'WHERE') + 6, 0);
//Explanation:
//SELECT * FROM table WHERE deleted='No'; becomes -->
//SELECT * FROM table WHERE (companyId=?) AND deleted='No';
}else{
//No
//Get table , and after that INSERT WHERE companyId=?
$tableName = explode(' from ', strtolower($sql))[1]; //Get part AFTER 'from'
//First word after $tableName = tablename
$tableName = explode(' ', $tableName)[0]; //First word after 'from' = tablename
$sql = substr_replace($sql, 'WHERE (companyId=?) ', strpos($sql, $tableName) + strlen($tableName) + 1, 0);
//Explanation:
//SELECT * FROM table ORDER BY id; becomes -->
//SELECT * FROM table WHERE (companyId=?) ORDER BY id;
}
}
So this code dynamically adds an extra condition to the query statement.
This is also easily possible with DELETE and UPDATE statements (same as SELECT)
But Iam trying to come up with something like this for INSERT INTO queries.
How can I modify the original query using the new companyId?
I guess If you have an associative array with the column names and values then you easily can make it more dynamic for future also. Let's say you've an array of column names with value of it e.g
$data = ['col1'=>'val1','col2'=>'val2','col3'=>'val3','companyId'=>1];
$query = "INSERT INTO `yourtable` ( ".implode(' , ', array_keys($data)).") VALUES ('".implode("' , '", array_values($data))."')";
echo $query;
DEMO: https://3v4l.org/udt1i
Then you can do with regex replace way globally to add column and value to all of your 100 query.
<?php
$re = '/\) VALUES (.+?(?=\)))/m';
$str = 'INSERT INTO table (`col1`, `col2`, `col3`) VALUES (\'oneVal\', \'twoVal\', \'threeVal\')';
$subst = ',`col4`) VALUES $1 , \'1\'';
$result = preg_replace($re, $subst, $str);
echo $result;
?>
DEMO: https://3v4l.org/rOQDG
I need a way to perform the following query:
SELECT *
FROM myTable
WHERE column NOT IN ('val1', 'val2')
Now, val1 and val2 are inside an array that I'm imploding into a string like this:
$inClause = "'" . implode("','", $inClauseArr) . "'";
If I put the string in the query declaration like this it works:
$sql = "[...]WHERE column NOT IN ($inClause)";
But if I pass it as a sqlsrv_query parameter like this, the query is not working:
$stmt = sqlsrv_query($conn, $sql, array($inClause));
I'm not getting any error. $stmt is true but while cycle is not returning anything.
I absolutely need to pass it through sqlsrv_query, how can i do this?
UPDATE
Here's the $sql value:
SELECT *
FROM orders(NOLOCK)
WHERE order_id NOT IN (?)
Count of question marks in your original SQL query must match with the count of parameters you have passed into the query.
Supposing $inClauseArr is like:
$inClauseArr = array('val1', 'val2');
There are two elements. Actually, it is not important how much element it has. As long as $inClauseArr is an array, there will be no problem.
So, the query should be constructed as ([...] is the beginning of the query):
$sql = "[...]WHERE column NOT IN ("
. implode(',', array_fill(0, count($inClauseArr), '?'))
. ")";
// For $inClauseArr = array('val1', 'val2');
// Output should be [...]WHERE column NOT IN (?, ?)
And execution should be like:
$stmt = sqlsrv_query($conn, $sql, $inClauseArr);
References:
sqlsrv_query
implode
array_fill
Pass it in a a comma delimited string (varchar) then use a split function to create a table:
declare #vars varchar(14) = 'G' + ',' + 'H'
select * from syscode_detail
where code in (select value from dbo.Split(#vars, ','))
using this function:
CREATE function [dbo].[Split]
(
#List nvarchar(2000),
#SplitOn nvarchar(5)
)
returns #tblReturn table
(
id int identity(1,1),
value nvarchar(100)
)
as
Begin
While (Charindex(#SplitOn, #List) > 0)
Begin
Insert Into #tblReturn (value)
Select value = ltrim(rtrim(Substring(#List, 1, Charindex(#SplitOn,
#List) - 1)))
Set #List = Substring(#List, Charindex(#SplitOn, #List) +
len(#SplitOn), len(#List))
End
Insert Into #tblReturn (value)
Select Value = ltrim(rtrim(#List))
Return
End
I have a query that looks like this:
UPDATE table SET column = UNIX_TIMESTAMP()
WHERE id IN (:idString)
Where idString is a string of comma separated ids and is passed to execute() in an array. To my surprise, when this query is executed, only the row with the first id in idString is updated.
After banging my head against the wall for a while, I finally decided to try it like this:
UPDATE table SET column = UNIX_TIMESTAMP()
WHERE id IN (' . $idString . ')
The second query works as expected.
Why won't the query work when I bind the string of ids using PDO?
In SQL, the string
'1,2,3,5,12'
Is a single value, and casting it in a numeric context, it will just have the value of the leading digits, so just the value 1.
This is much different from the set of multiple values:
'1', '2', '3', '5', '12'
Any time you use bound parameters, whatever you pass as the parameter value becomes just one single value, even if you pass a string of comma-separated values.
If you want to pass a set of multiple values to parameters in your SQL query, you must have multiple parameter placeholders:
UPDATE table SET column = UNIX_TIMESTAMP()
WHERE id IN (:id1, :id2, :id3, :id4, :id5)
Then explode your string of values and pass them as an array:
$idlist = array('id1' => 1, 'id2' => 2, 'id3' => 3, 'id4' => 5, 'id5' => 12);
$pdoStmt->execute($idlist);
For cases like this, I would recommend using positional parameters instead of named parameters, because you can pass a simple array instead of an associative array:
$pdoStmt = $pdo->prepare("UPDATE table SET column = UNIX_TIMESTAMP()
WHERE id IN (?, ?, ?, ?, ?)");
$idlist = explode(",", "1,2,3,5,12");
$pdoStmt->execute($idlist);
#mario adds a comment that you can use FIND_IN_SET(). That query would look allow you to pass one string formatted as a comma-separated string of values:
$pdoStmt = $pdo->prepare("UPDATE table SET column = UNIX_TIMESTAMP()
WHERE FIND_IN_SET(id, :idString)");
$pdoStmt->execute(["idString" => "1,2,3,5,12"]);
However, I usually don't recommend that function because it spoils any chance of using an index to narrow down the search. It will literally have to examine every row in the table, and during an UPDATE that means it has to lock every row in the table.
Your working solution is not good as it's subject to SQL INJECTION.
The reason it's not working is because you are allocating an array, instead of plain comma separated values.
You have to use implode to separate the values of the array, and then assign the comma separated values to a variable wich can be used by pdo.
Otherwise you can use instead of : $idString, ? in the select statement, and executing the prepared statement from and array which holds the $idString.
$query=$db->prepare("Select a From table where b =? order by 1;");
$query->execute(array($idString));
You are trying to pass a string as a set to a prepared statement. MySQL is trying to execute the query
-- assuming idString is "1,2,3,4,5"
UPDATE table SET column = UNIX_TIMESTAMP() WHERE id IN ("1,2,3,4,5");
instead of
UPDATE table SET column = UNIX_TIMESTAMP() WHERE id IN (1,2,3,4,5);
you'll have to either use the statement
UPDATE table SET column = UNIX_TIMESTAMP() WHERE id == ?
and execute it for however many id's you have or prepare the statement by injecting id string into the query
When binding, PDO expects a single value. Something like this will work, given your $idString above (though if you have the source array, even better!):
$ids = explode(',', $idString);
$placeholders = implode(', id', array_keys($ids));
if($placeholders) {
$placeholders = 'id' . $placeholders;
$sql = "UPDATE table SET column = UNIX_TIMESTAMP()
WHERE id IN ({$placeholders})";
// prepare your statement, yielding "$st", a \PDOStatement
$st = $pdo->prepare($sql);
// bind every placeholder
foreach($ids as $key => $id) {
$st->bindValue("id{$key}", $id);
}
// execute
$st->execute();
}
Quoting MySQL INSERT manual - same goes for UPDATE:
Use the keyword DEFAULT to set a column explicitly to its default value. This makes it easier to write INSERT statements that assign values to all but a few columns, because it enables you to avoid writing an incomplete VALUES list that does not include a value for each column in the table. Otherwise, you would have to write out the list of column names corresponding to each value in the VALUES list.
So in short if I write
INSERT INTO table1 (column1,column2) values ('value1',DEFAULT);
A new row with column2 set as its default value - whatever it may be - is inserted.
However if I prepare and execute a statement in PHP:
$statement = $pdoObject->
prepare("INSERT INTO table1 (column1,column2) values (?,?)");
$statement->execute(array('value1','DEFAULT'));
The new row will contain 'DEFAULT' as its text value - if the column is able to store text values.
Now I have written an abstraction layer to PDO (I needed it) and to get around this issue am considering to introduce a
const DEFAULT_VALUE = "randomstring";
So I could execute statements like this:
$statement->execute(array('value1',mysql::DEFAULT_VALUE));
And then in method that does the binding I'd go through values that are sent to be bound and if some are equal to self::DEFAULT_VALUE, act accordingly.
I'm pretty sure there's a better way to do this. Has someone else encountered similar situations?
The only "workaround" I know for this is to use Coalesce() and Default(fieldname)
E.g.
$pdo = new PDO("mysql:host=localhost;dbname=test", 'localonly', 'localonly');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec("
CREATE TEMPORARY TABLE foo (
id int auto_increment,
x int NOT NULL DEFAULT 99,
y DATETIME NOT NULL DEFAULT '2010-03-17 01:00:00',
z varchar(64) NOT NULL DEFAULT 'abc',
primary key(id)
)
");
$stmt = $pdo->prepare('
INSERT INTO
foo
(x,y,z)
VALUES
(
Coalesce(:x, Default(x)),
Coalesce(:y, Default(y)),
Coalesce(:z, Default(z))
)
');
$stmt->bindParam(':x', $x);
$stmt->bindParam(':y', $y);
$stmt->bindParam(':z', $z);
$testdata = array(
array(null, null, null),
array(1, null, 'lalala'),
array(null, '2009-12-24 18:00:00', null)
);
foreach($testdata as $row) {
list($x,$y,$z) = $row;
$stmt->execute();
}
unset($stmt);
foreach( $pdo->query('SELECT id,x,y,z FROM foo', PDO::FETCH_NUM) as $row) {
echo join(', ', $row), "\n";
}
prints
1, 99, 2010-03-17 01:00:00, abc
2, 1, 2010-03-17 01:00:00, lalala
3, 99, 2009-12-24 18:00:00, abc
I tried replying to VolkerK answer, but couldnt find how. :( I'm kinda new to all this.
Anyway, I created a mysql function to use in conjuction with his COALESCE idea
CREATE FUNCTION NULLDEFAULT(colname VARCHAR(64), tablename VARCHAR(64), dbname VARCHAR(64)) RETURNS longtext DETERMINISTIC READS SQL DATA
BEGIN
DECLARE retval longtext;
SELECT
COLUMN_DEFAULT INTO retval
FROM
information_schema.COLUMNS
WHERE
TABLE_NAME = tablename
AND
COLUMN_NAME = colname
AND
TABLE_SCHEMA = dbname;
RETURN retval;
END
You would use it like this:
$stmt = $pdo->prepare("
INSERT INTO
foo
(x,y,z)
VALUES
(
Coalesce(:x, NULLDEFAULT('x', 'foo', 'database')),
Coalesce(:y, NULLDEFAULT('y', 'foo', 'database')),
Coalesce(:z, NULLDEFAULT('z', 'foo', 'database'))
)
");
That will return null if the column has no default value, and won't trigger the "Column has no default value" Error.
Of course you could modify it to not require the database parameter
Try changing this:
$statement = $pdoObject->
prepare("INSERT INTO table1 (column1,column2) values (?,?)");
$statement->execute(array('value1','DEFAULT'));
To this:
$statement = $pdoObject->
prepare("INSERT INTO table1 (column1,column2) values (?,DEFAULT)");
$statement->execute(array('value1'));
It seems to me that your original code will give you this:
INSERT INTO table1 (column1,column2) values ('value1','DEFAULT')
My code should give you this:
INSERT INTO table1 (column1,column2) values ('value1',DEFAULT)
i think that it is writing the String 'DEFAULT ' because it is escaped by pdo so there are parametres for bindvalue where you can specify the type of the value given so you can send a null with no quotes and it will be PDO::PARAM_NULL; and then default values will be put , but i'm not sure if there are similar parameters when binding with execute
if(is_int($param)){$pdoParam = PDO::PARAM_INT;}
elseif(is_bool($param)){$pdoParam = PDO::PARAM_BOOL;}
elseif(is_null($param)){ $pdoParam = PDO::PARAM_NULL;}
elseif(is_string($param)){$pdoParam = PDO::PARAM_STR;}
else{$pdoParam = FALSE;}
$this->_query->bindValue($k,$param,$pdoParam);