PHP SQLSRV: passing a string of values to IN clause - php

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

Related

PHP dynamically add new column and value in existing Query

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

PHP, MYSQl copy one database tables to others and replace empty with null

I am copying one complete database to other database. Every thing is working except one problem. Say one of my table has following structure
id -> Integer PK AI
state_id -> Integer (Default NULL)
sell_id -> Integer (Default NULL)
Now this table can have NULL in state_id and sell_id. So I query SELECT to this table, an empty string is return against all NULL values. But when I try to insert these values to other database table, it generates error as empty string cannot be given in integer column. Like select query return data some thing like following array
array(
'id' => 1,
'state_id' => '',
'sell_id' => ''
)
So I will try to insert this array into new table, it will generate error. So I want to replace all '' with NULL. I have tried following by setting NULL to all empty values
$array = array_map(function($v){
return $v == '' ? NULL : $v;
},$array);
But this also prints '' to all NULL values. This is my complete Code
$result = mysqli_query($dbConnection1, "SELECT * FROM $table ORDER BY $sortedBy DESC limit $noOfRecords");
$columns = '';
$values = '';
if($result && mysqli_num_rows($result)) {
while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC) ) {
$array = array_values($row);
$array = array_map(function($v){
return $v == '' ? NULL : $v;
},$array);
$columns = " (".implode(", ",array_keys($row)).") ";
$values .= " ('".implode("', '",$array)."'),";
}
$values = rtrim($values, ",");
mysqli_query($dbConnection2, "INSERT INTO $table $columns VALUES $values");
}
But this code isn't working for above stated scenario. The Insert query is printed as
INSERT INTO table (id, state_id, sell_id) VALUES (1, '', '') , (2, '', '')
But I want it to be like
INSERT INTO table (id, state_id, sell_id) VALUES (1, NULL, NULL) , (2, NULL, NULL)
Can any body let me know how can I achieve that in my scenario.
This could easily be done with a single query:
INSERT INTO newtable (newfield1, newfield2, etc...)
SELECT oldfield1, IF(oldfield2="", NULL, oldfield2), etc...
FROM oldtable
The select can correct/adjust any of the field values on-the-fly.
And note that your version is vulnerable to sql injection attacks. Doesn't matter that the data you're dealing with came out of the database just milliseconds before - it's still unquoted/unescaped data and can/will blow up your insert query.
First of all if i need to move some data i do this over only SQL like #MarcB mentioned his answer but if you insist on your method i think you need to change the following line
return $v == '' ? NULL : $v;
to
return $v == '' ? 'NULL' : $v;
With this change your array's null elements becomes php's string 'NULL' so when they implode they wont convert to '' empty string they will still remains as a string NULL so they will appear as NULL in your insert SQL

Search parameters SQLSRV_QUERY for WHERE IN syntax

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);

Pad array values with parentheses to perform insert

I want to have each array value inside a paranthesis
$id = $_POST['id'];
$test2 = array($id);
$id_list = implode(',', $test2);
$sql .= "INSERT INTO tmp (id) VALUES ({$id_list});";
for example: I'm performing an insert so the output of the list should be (5),(10),(15) not '5','10','15'
Any suggestions on how I can insert using an array?
MySQL's extended insert syntax is
INSERT INTO sometable (...fields...) VALUES (value set #1), (value set #2), (value set #3), etc...
Note how each value set is in its own (). You're not doing that. you're just passing a bunch of values in a SINGLE () set, which means you're providing values for fields that are NOT present in the field list.
You need to do
$id_list = '(' . implode('),(', $_POST['id']) . ')';
producing
(5),(10),(15)
instead of
(5, 10, 15)
You also need to realize that you're vulnerable to sql injection attacks.

Using WHERE IN (...) with PDO doesn't work when the string is bound

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();
}

Categories