How to use a Select with OR in PHP - odbc_prepare Issue - php

I'm using Microsoft SQL Server for this
function novogetListagemClientes($conn, $User){
$stmt = odbc_prepare($conn, 'SELECT * FROM users WHERE id IN ( SELECT idCliente FROM ligacoes WHERE ligacoes.idGest = ? OR idSocioG = ? OR idFunc = ? OR idColab = ? OR idSub = ? )');
$success = odbc_execute($stmt, array($User));
while($myRow = odbc_fetch_array($stmt))
{
$rows = $myRow;
}
if(empty($rows))
{
return array();
}
return utf8_converter($rows);
}
That's my PHP Function and I want that all the '?' have the same number, how should I fill that array ?
Right now when I use this query in my page it returns nothing, but in Navicat using query builder, returns what I expect.
Error I get: Warning: odbc_execute(): SQL error: [Microsoft][ODBC SQL Server Driver]Syntax error or access violation, SQL state 37000 in SQLDescribeParameter
If switch the '?' by a number that exists, I get the true value.
Update:If I switch the ? by '.$User.' and take of the array part it works

This answer is a summary of interactions in comments on the original question.
Having checked that the ODBC connection was fine (data is returned when the relevant parameter was "hardcoded"), I surmised that the problem was that the ODBC connection wasn't coping with the "Prepare" statement. This will likely have been because the parameters to be passed were not in the outermost WHERE clause and so prior to executing the query the connection couldn't work out the overall shape of the resulting data.
To get around this, we refactored the SQL code to remove the subquery and so put the parameter in the outermost WHERE clause.
The SQL therefore changed from
SELECT *
FROM users
WHERE id IN ( SELECT idCliente
FROM ligacoes
WHERE ? in (ligacoes.idGest, idSocioG, idFunc, idColab, idSub))
to
SELECT DISTINCT u.*
FROM users u
JOIN ligacoes l ON
u.id = l.idCliente
WHERE ? in (l.idGest, l.idSocioG, l.idFunc, l.idColab, l.idSub)
And with that change the code ran without complaint.

Related

What is wrong with this concatenate statement?

I'm working with a MySQL server Server version: 5.6.42-cll-lve - MySQL Community Server (GPL) and I'm having some major issues with CONCAT()
public function get_urls() {
// Create query
$query = "SELECT a.Name, a.PrimaryVersion, e1.URL
FROM " . $this->table . " a
INNER JOIN
FunnelsFlows b ON a.Funnel_ID = b.Funnel_ID
INNER JOIN
BackendFlows c ON b.BackendFlow_ID = c.BackendFlow_ID
INNER JOIN
BackendLevels d ON CONCAT(c.Level, :lv) = d.BackendLevel_ID
LEFT JOIN
BackendPages e1 ON d.Upsell = e1.BackendPage_ID
LEFT JOIN
BackendPages e2 ON d.Downsell1 = e2.BackendPage_ID
LEFT JOIN
BackendPages e3 ON d.Downsell2 = e3.BackendPage_ID
WHERE
a.Funnel_ID = :fid
LIMIT 0,1";
// Prepare statement
$stmt = $this->conn->prepare($query);
// Bind ID
$stmt->bindParam(':fid', $this->fid, PDO::PARAM_INT);
$stmt->bindValue(':lv', $this->lv, PDO::PARAM_STR);
// Execute query
$stmt->execute();
Running this code throws the following error: PHP Fatal error: Uncaught PDOException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'c.Level' in 'on clause' in ...
The entire database structure you can view here:
https://app.sqldbm.com/MySQL/Share/wBAF2JMRFFSoPPjIPdYZc0GFrngIE8md_DYjF4jNYw0
Delete the quotes (both for column name and for the value):
CONCAT(a.Level, :level)
Also, you use a wrong parameter name:
$stmt->bindParam(':level', $this->lv, PDO::PARAM_INT);
// ^^^^^
Your query cannot work like that. You are trying to concat a field content CONCAT(c.Level, :lv) with a parameter :lv. This results in a string if there were a column Level. However, you rather want to build a dynamic column name like Level1 / Level2 aso.
Unfortunately this cannot be done by parameters. This is owing to the fact, that query plans are prepared. Changing column names at execution time would alter the query plan.
You either need to build dynamic SQL in such a case or write a complex ON clause.
The ON clause:
ON
(
(:lv = 1 AND c.Level1 = d.BackendLevel_ID)
OR
(:lv = 2 AND c.Level2 = d.BackendLevel_ID)
-- and so on
)
Dynamic SQL:
$sql = 'ON (c.Level' . inval($this->lv) . ' = d.BackendLevel_ID)'
Since PHP's intval function is considered to be safe, I would prefer the latter example in order to improve SQL performance.
What about this?
$query = "SELECT a.Name, b.URL
FROM " . $this->table . " a
INNER JOIN
FunnelsFlows b ON CONCAT(a.Level, ':lv') = b.Level_ID
WHERE
a.Funnel_ID = :fid
LIMIT 0,1";
// Prepare statement
$stmt = $this->conn->prepare($query);
// Bind ID
$stmt->bindParam(':fid', $this->fid, PDO::PARAM_INT);
$stmt->bindParam(':lv', $this->lv, PDO::PARAM_INT);
// Execute query
$stmt->execute();
That was a quick response, so I'll explain now.
What I'm seeing it that you want to concatenate a table column with a string, right?
First you had a wrong ident :level which you are trying to bind with the following statement $stmt->bindParam(':lv', $this->lv, PDO::PARAM_INT); so you have to change it with :lv or chenge the binding statement to $stmt->bindParam(':level', $this->lv, PDO::PARAM_INT);.
Second - while you are trying to concatenate a column you have to remove the quotes so it should be CONCAT('a.Level', and if the second operator is a string it should be in single quotes, so the full CONCAT operator should be written as follow CONCAT(a.Level, ':lv')

pg_query_params is not substituting parameters in the query

I want my application to dynamically change which database and schema name it uses depending on whether it's running on a local machine, staging environment, or production. I thought pg_query_params would be a great choice because you can dynamically substitute things into your queries by parameterizing them. The idea is the schema name will be dynamically inserted into the query. But, pg_query_params() doesn't seem to be substituting the values in the array, causing a query syntax error.
I have verified that the query itself is valid by using pg_query and hardcoding the schema name in versus parameterizing it. When I look at the logs, I see the following error:
PHP Warning: pg_query_params(): Query failed: ERROR: syntax error at or near "$1. Line 2 FROM $1.module__c
So, clearly, pg_query_params isn't actually substituting the parameters in (at least from what I can see)
As a result, pg_query_params() returns false, and breaks the whole page.
The code first gets an array from my databaseInfo() function (it returns an array for the schema name and connection resource. I've verified that this part of my code is working and returning the expected information.
$database = databaseInfo();
if (isset($projSFID)) {
$placeholder = $projSFID;
$query = "SELECT $1.module__c.name, $1.module__c.module_title__c, $1.module__c.number_of_units__c, $1.module__c.duration_minutes__c, $1.module__c.module_bg_hex__c, $1.module__c.module_description__c, $1.lms_asset__c.lms_asset_url__c
FROM $1.module__c
INNER JOIN $1.teproject__c ON $1.module__c.associated_project__c = $1.teproject__c.sfid
INNER JOIN $1.lms_asset__c ON $1.module__c.module_image_url__c = $1.lms_asset__c.sfid
WHERE $1.teproject__c.sfid = $2 AND $1.module__c.published__c=TRUE";
} elseif (isset($trackSFID)) {
$placeholder = $trackSFID;
$query = "SELECT $1.module__c.name, $1.module__c.module_title__c, $1.module__c.number_of_units__c, $1.module__c.duration_minutes__c, $1.module__c.module_bg_hex__c, $1.module__c.module_description__c, $1.lms_asset__c.lms_asset_url__c
FROM $1.module_track_association__c
INNER JOIN $1.module__c ON $1.module_track_association__c.module__c = $1.module__c.sfid
INNER JOIN $1.track__c ON $1.module_track_association__c.track__c = $1.track__c.sfid
INNER JOIN $1.lms_asset__c ON $1.module__c.module_image_url__c = $1.lms_asset__c.sfid
WHERE $1.track__c.sfid = $2
ORDER BY $1.module_track_association__c.navigation_sequence__c";
} else {
$placeholder = '';
$query = "SELECT $1.module__c.name, $1.module__c.module_title__c, $1.module__c.number_of_units__c, $1.module__c.duration_minutes__c, $1.module__c.module_bg_hex__c, $1.module__c.module_description__c, $1.lms_asset__c.lms_asset_url__c
FROM $1.module__c
INNER JOIN $1.lms_asset__c ON $1.module__c.module_image_url__c = $1.lms_asset__c.sfid
WHERE $1.module__c.published__c=TRUE AND $1.module__c.display_on_frontend_filtered_only__c=FALSE $2";
echo $query;
}
$result = pg_query_params($database['connection'], $query, array($database['schema'],$placeholder));
The expected result is that $1 will be the name of the schema and $2 will be the value of the placeholder as determined by the conditional logic.

PHP Prepared Statement variable binding error with subquery

I have a query with a few subqueries like so
SELECT ...
FROM (SELECT ...
FROM ...
GROUP BY ...) as speedLimitCalc INNER JOIN
(SELECT ...
FROM date a INNER JOIN HOURLY_TEST b ON a.[FULL_DAY_DT] = b.DATE
WHERE (b.DATE BETWEEN '".$date_s."' AND '".$date_e."')
AND HOUR BETWEEN ".$time_s." AND ".$time_e."
AND(LKNO BETWEEN '".$lkno_s."' and '".$lkno_e."')
AND RDNO= '".$rdno."'
AND pub_hol IN (".$pubholquery.")
AND school_hol IN (".$schholquery.")
AND day_no IN (".$dayquery.")
GROUP BY RDNO, LKNO, PRESCRIBED_DIRECTION, CWAY_CODE) as origtable ON ...
,(SELECT ...
FROM [Dim_date]
WHERE (FULL_DAY_DT BETWEEN '".$date_s."' AND '".$date_e."')
AND pub_hol IN (".$pubholquery.")
AND school_hol IN (".$schholquery.")
AND day_no IN (".$dayquery.") ) as c
ORDER BY ...
where I am inserting variables in the inner query where clause.
I am trying to parametrize this query using odbc_prepare and odbc_execute, however I am running into issues of binding the variables. At present, when I use the following
$result = odbc_prepare($connection, $query);
odbc_execute($result)or die(odbc_error($connection));
to run this query, everything works fine. However, when I try to bind a variable, such as
AND RDNO= ?
...
odbc_execute($result, array($rdno))or die(odbc_error($connection));
I get the following error message.
PHP Warning: odbc_execute() [/phpmanual/function.odbc-execute.html]: SQL error: [Microsoft][ODBC SQL Server Driver]Invalid parameter number, SQL state S1093 in SQLDescribeParameter
My guess is that it's because I'm binding a variable in a subquery, since this procedure works when the Where clause is in the top Select query.
I was wondering whether anyone else has encountered this issue before, and how they solved it? Thanks
Fixed the issue by removing the need for parameters in the subquery by separating the query into multiple queries using temporary tables.
$query = "SELECT ...
INTO ##avgspeedperlink
FROM Date a INNER JOIN HOURLY_TEST ON a.[FULL_DAY_DT] = b.DATE
WHERE (b.DATE BETWEEN ? AND ?)
AND HOUR BETWEEN ? AND ?
AND(LKNO BETWEEN ? and ?)
AND RDNO= ?
AND pub_hol IN (".$pubholquery.")
AND school_hol IN (".$schholquery.")
AND day_no IN (?,?,?,?,?,?,?)
GROUP BY RDNO, LKNO, PRESCRIBED_DIRECTION, CWAY_CODE";
$result = odbc_prepare($connection, $query);
odbc_execute($result, array($date_s,$date_e,$time_s,$time_e,$lkno_s,$lkno_e,$rdno,$daysanitised[0],$daysanitised[1],$daysanitised[2],$daysanitised[3],$daysanitised[4],$daysanitised[5],$daysanitised[6]))or die(odbc_error($connection));
$query = "SELECT ...
INTO ##daysinperiod
FROM [RISSxplr].[dbo].[Dim_date]
WHERE (FULL_DAY_DT BETWEEN ? AND ?)
AND pub_hol IN (".$pubholquery.")
AND school_hol IN (".$schholquery.")
AND day_no IN (?,?,?,?,?,?,?)";
$result = odbc_prepare($connection, $query);
odbc_execute($result, array($date_s,$date_e,$daysanitised[0],$daysanitised[1],$daysanitised[2],$daysanitised[3],$daysanitised[4],$daysanitised[5],$daysanitised[6]))or die(odbc_error($connection));
$query = "SELECT ...
FROM ##avgspeedperlink, ##daysinperiod
ORDER BY LKNO, OUTBOUND
drop table ##avgspeedperlink
drop table ##daysinperiod";
Note that I had to use double ## for making the temporary tables (single # means that table is local to the query, ## means that the temporary table becomes global for multiple queries).

PHP PDO Query returning an empty array when using bindParam

Kind of stumped by this one. I have been developing for about a week now, maybe two so it might be a noob mistake, but here is what I have:
<?php
$msDB = new PDO('odbc:Driver={SQL Server Native Client 11.0};Server=SOMESERVER;Trusted_Connection=yes;');
try{
//set values for query
$s="4";
$d1="'2014-10-01 00:00:00'";
$d2="'2014-10-31 23:59:59'";
//create query variable
$q1 = "SELECT ID FROM SURVEY_QUESTION_RESPONSE AS t1 WHERE EXISTS
(SELECT * FROM SURVEY_RESPONSE AS tN
WHERE (tN.ID = t1.SURVEY_RESPONSE_ID)
AND (t1.SELECTION = :s)
AND (tN.RESPONSE_DATE BETWEEN :d1 AND :d2))";
//run prepare and bindParam
$tbe = $msDB->prepare($q1);
$tbe->bindParam(':s',$s, PDO::PARAM_INT);
$tbe->bindParam(':d1',$d1, PDO::PARAM_STR);
$tbe->bindParam(':d2',$d2, PDO::PARAM_STR);
//execute query
$tbe->execute();
//fetch resulting data
$res = $tbe->fetchAll(PDO::FETCH_ASSOC);}
//error handling
catch (PDOException $e) {
throw new pdoDbException($e);
}
//print the resulting array
print_r($res);
//set initial count
$cnt=0;
//loop through and increment count
foreach($res as $key=>$value){
foreach($value as $v2 ){
$cnt++;
}
}
//return count value
echo "Total:<br/>".$cnt."<br/>";
?>
I am expecting this to return a result set of the number 3. And when I specify the values in the query manually, everything works as expected and it returns the number 3.
If I however use the bindParam method it returns nothing and throws no errors of any sort. It simply returns an empty array.
I can also break up the query set in $q1 and concatenate the values into it, and it also works flawlessly. I have not really used bindParam before, but as far as I can tell, I am using it correctly.
Works:
//create query variable
$q1 = "SELECT ID FROM SURVEY_QUESTION_RESPONSE AS t1 WHERE EXISTS
(SELECT * FROM SURVEY_RESPONSE AS tN
WHERE (tN.ID = t1.SURVEY_RESPONSE_ID)
AND (t1.SELECTION = ".$s.")
AND (tN.RESPONSE_DATE BETWEEN ".$d1." AND ".$d2."))";
When I run the query in MSSQL Server Management Studio, it also returns the result set I expect.
Can anyone tell me what I am doing wrong?
The default data type passed by PDO::bindParam and PDO::bindValue is ALWAYS text. Conversion from datatype text in MSSQL is only possible to CHAR, VARCHAR, NCHAR, and NVARCHAR. It is because of the datatype issue that the value has to be converted from text into CHAR or VARCHAR and then into DATETIME from there. This is however an implicit conversion, and depending on the value passed to the query, may result in rounding errors, truncation, or simply a failed conversion.
This, does NOT work:
//create query variable
$q1 = "SELECT ID FROM SURVEY_QUESTION_RESPONSE AS t1 WHERE EXISTS
(SELECT * FROM SURVEY_RESPONSE AS tN
WHERE (tN.ID = t1.SURVEY_RESPONSE_ID)
AND (tN.RESPONSE_DATE BETWEEN :d1 AND :d2))";
//run prepare and bindParam
$tbe = $msDB->prepare($q1);
$tbe->bindParam(':d1',$d1, PDO::PARAM_INT);
$tbe->bindParam(':d2',$d2, PDO::PARAM_INT);
This however, does work:
$q1 = 'SELECT ID FROM SURVEY_QUESTION_RESPONSE AS t1 WHERE EXISTS
(SELECT * FROM SURVEY_RESPONSE AS tN
WHERE (tN.ID = t1.SURVEY_RESPONSE_ID)
AND (tN.RESPONSE_DATE BETWEEN CONVERT(datetime,CONVERT(VARCHAR(MAX),?))
AND CONVERT(datetime,CONVERT(VARCHAR(MAX),?))))';
//run prepare and bindParam
$tbe = $msDB->prepare($q1);
$tbe->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
$tbe->bindParam(1,$d1);
$tbe->bindParam(2,$d2);
Even if I casted the bound parameters as type INT, they were still passed to MSSQL as text causing the failure.
It would be my suggestion however to simply use the original workaround of just passing in the variables directly like so and have the variables be double quoted strings:
$q1 = "SELECT ID FROM SURVEY_QUESTION_RESPONSE AS t1 WHERE EXISTS
(SELECT * FROM SURVEY_RESPONSE AS tN
WHERE (tN.ID = t1.SURVEY_RESPONSE_ID)
AND (t1.SELECTION = '$s')
AND (tN.RESPONSE_DATE BETWEEN '$date1' AND '$date2'))";
The quoted string is much easier to handle, and far less prone to error because it need not be converted at all and is simply passed right along to MSSQL Server without issue.
I noticed a slight performance hit with the converts because of the extra processing by MSSQL Server. The query took about half a second longer to run with the conversion than without.
Cheers to #meda for helping with the resolution!
I get this error today and take a lot of my hours.
After upgrading PHP Version to PHP7, using sqlsrv:database driver instead of dblib:dbname make this problem occurs.
To avoid this, still using dblib:dbname event PHP7 FPM already support with sqlsrv:database driver.

PHP PDO doesn't select data

When I get data without "prepare" and "execute", code is working fine. Example:
$this->db->query("select {$val} from {$table_name} where username={$username}")->fetch();
But this code always return False:
$this->db->prepare("select :val from :table_name where username = :username")
->execute(array(':username'=>$username,':val'=>$val,':table_name'=>$this->table_name));
HELP!:(
Thank you for your answers. Now my code is looking here:
$q=$this->db->prepare("select pass from nm_users where username = :username");
return $q->execute(array('username'=>$username));
Return value is True, but I can't get data from DB.
Don't try to use PDO as a fluent interface. You can't do this:
$db->prepare()->execute();
The reason is that fluent interfaces work only if the function is guaranteed to return an object that has in this case an execute method.
But prepare() returns false on error. The value false isn't an object, and doesn't have an execute() method.
You need to check for false after every prepare() and after every execute():
$stmt = $this->db->prepare("select :val from :table_name where username = :username");
if ($stmt === false) {
$err = $this->db->errorInfo();
error_log($err[2]);
}
$result = $stmt->execute(array(':username'=>$username,':val'=>$val,':table_name'=>$this->table_name));
if ($result === false) {
$err = $stmt->errorInfo();
error_log($err[2]);
}
If you do this, you'll find that an error was reported on your prepare():
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''user' where username = 'bill'' at line 1
The reason is that query parameters are only for constant values. You can't use them for table names, column names, SQL keywords, expressions, lists of values, etc.
I'm inferring that :val is also meant to be a dynamic column name, and that's not allowed either. But in that case, it won't result in an error, it'll just substitute a literal string with the value of $val for every row returned.
In other words, substituting the table name with a parameter is wrong because you can't do a query like SELECT * FROM 'user' (literal string, not table name), and that's how the parameter will act. It's simply invalid SQL.
But the dynamic column name will do a query like SELECT 'val' FROM ... and that's legal, but won't select from the column named val, it'll select the literal string constant 'val'.
Parameters cannot be set for table-names etc. and have to be set in the array without the colon:
$dbSelect=$db->prepare("select aField from aTable where username = :username")
$dbSelect->execute(array('username' => $username));
Replace aField and aTable with standard str_replace or sth similar.
The table name must be contained inside the query when you 'prepare' it, it cannot be added dynamically as the rest of the arguments. Therefore you have to use a combination of two strategies to finalize your query:
$stmnt=sprintf('select %1$s from %2$s where username=:username',
$val, $this->table_name);
if (FALSE===($query=$this->db->prepare($stmnt)))
exit('Buggy statement: '.$stmnt);
$query->execute(array(':username'=>$username));
Unfortunately this also means you have to take care that $this->table_name is escaped correctly!

Categories