a new webserver has been stood up for me. It is Ubuntu 20.04 and has PHP 7.4.3 on it. I am working with MS SQL Server 2012. I can send my SQL query to the DB server and see it in SQL Server Profiler. I can copy it from the profiler and it runs fine in SQL Management Studio but, I am having a problem when I try to echo the results to my page. When I use sqlsrv_num_rows I get -1. Could I get a hand please?
<?php
if(isset($_POST['getPassBtn'])){
#DATABASE LOGIN
include './php/inc/dbLogin/mydb.php';
#FORM POST VARIABLES
$badge = $_POST['empId'];
//$badge = (int)$badge;
#check badge entry for paramiters
if($badge == '' || $badge < 10000 || $badge > 30000){
echo '<br/> ERROR : Invalid Badge Number <br/>';
die();
}
$sql = "USE mydb SET NOCOUNT ON SELECT badgeNumber, userPassword FROM dbo.users WHERE badgeNumber = '$badge' ORDER BY badgeNumber ASC --getTestData.php";
//$sql = "USE toolsMeskwaki SELECT * FROM bingoProgressive.users ORDER BY badgeNumber ASC --getTestData.php";
$params = array();
$options = array( 'Scrollable' => 'buffered');
$stmt = sqlsrv_query( $conn, $sql, $params, $options);
#check if query returns false
if( $stmt === false ) {
die( print_r( sqlsrv_errors(), true));
}else{
echo '<br/> Query sent to SQL Server <br/>';
}
/*
$row_count = sqlsrv_num_rows( $stmt );
if ($row_count === false){
echo "Error in retrieveing row count.";
}else{
echo $row_count;
}
*/
#Fetching Data by array
while($row = sqlsrv_fetch_array($stmt)){
echo 'badgeNumber '.$row['badgeNumber'];
echo 'userPassword '.$row['userPassword'];
}
#release query
sqlsrv_free_stmt($stmt);
#CLOSE DATA BASE CONNECTION
sqlsrv_close($conn);
echo "<br/> SQL Server Connection closed.<br />";
}else{
echo '<br/> click GET btn to connect SQL Server <br/>';
}
?>
You're submitting three different statements:
USE mydb
SET NOCOUNT ON
SELECT badgeNumber, userPassword FROM dbo.users WHERE badgeNumber = '$badge' ORDER BY badgeNumber ASC
It works in SQL Management Studio for two reasons:
Auto-magic statement detection, although deprecated for several years (you're now expected to use ; as delimiter), is still supported.
The query tool is specifically designed to run several statements at once.
We know that sqlsrv_query() supports it because it isn't returning false. But, given that you're running three statements, you need you use sqlsrv_next_result() twice to move to the third result set.
On a side note:
You can use sqlsrv_connect() to provide the initial database. You only need to switch DBs if you use several of them and you don't want to add the name as mydb.dbo.users.
SQLSRV supports prepared statements (see sqlsrv_prepare() and sqlsrv_execute()).
Related
I am generating a SQL insert statement within a PHP for loop.
The SQL string generated is a large number of individual SQL statements like this:
INSERT INTO tbl VALUES(1,2,3);
INSERT INTO tbl VALUES(4,5,6);
INSERT INTO tbl VALUES(7,8,9);
etc...
Then I execute with:
$InsertResult = sqlsrv_query($conn, $InsertSQL);
The problem is that only the first 312 statements get executed instead of the full 2082 lines (only 312 rows are inserted into the table).
When I output the $InsertSQL variable to the JavaScript console and then execute it manually in SSMS it works perfectly and inserts all 2082 lines. Only when I run the $InsertSQL variable through sqlsrv_query does it not go to completion.
I also don't get any errors and the query result comes back true as tested in this line:
if(!$InsertResult) die('Problem with Insert query: ' . $InsertSQL);
When I searched for a solution to this problem I saw that (although it's not mentioned in the PHP manual site) sqlsrv_query apparently has a string character limit on the $SQL variable (around 65k characters).
See the other StackOverflow article here:
length restriction for the sql variable on sqlsrv_query?
I figured this was the problem and so created a shorter version of the string (by only adding in the column values that I actually wanted to import). This much shorter version however, still only Inserts the first 312 lines! So now it seems this is NOT related to the max string length. In fact, if it was, I should only get around 250 lines (after 250 statements I'm at about 65k characters).
I can also execute each insert statement individually but of course this takes much longer. In my testing, it takes 90s or so to do it this way where as running the combined statement manually in SMSS takes only around 40s.
Note that I've also looked into SQL Server's Bulk Insert however I won't be able to send the file to the machine where SQL Server is installed (SQL Server and Web servers are on separate computers). From my understanding this eliminates this possibility.
Any help is much appreciated as I can't even figure out what it is that is limiting me, never mind fix it and I'd hate to have to execute one line at a time.
Explanations:
There is a known issue with this driver, posted on GitHub, about executing large SQL statements. One part of the provided solution are the following explanations:
Seems like when executing a large batch of SQL statements, Microsoft SQL Server may stop processing the batch before all statements in the batch are executed. When processing the results of a batch, SQL Server fills the output buffer of the connection with the result sets that are created by the batch. These result sets must be processed by the client application. If you are executing a large batch with multiple result sets, SQL Server fills that output buffer until it hits an internal limit and cannot continue to process more result sets. At that point, control returns to the client. This behavior is by design.
Client app should flush all the pending result sets. As soon as all pending result sets are consumed by the client, SQL Server completes executing the batch. Client app can call sqlsrv_next_result() until it returns NULL.
So, I don't think that there is a limit for the SQL statement length, only the size of a PHP string variable ($InsertSQL in your case) is limited to the maximum allowed PHP memory limit. The actual reason for this unexpected behaviour is the fact, that with SET NOCOUNT OFF (this is by default) and a large number of single INSERT statements, the SQL Server returns the count of the affected rows as a result set (e.g. (1 row affected)).
Solution:
I'm able to reprodiuce this issue (using SQL Server 2012, PHP 7.1.12 and PHP Driver for SQL Server 4.3.0+9904) and you have the following options to solve this problem:
Flush the pending result sets using sqlsrv_next_result().
Execute SET NOCOUNT ON as first line in your complex T-SQL statement to stop SQL Server to return the count of the affected rows as a resultset.
Use parameterized statement using sqlsrv_prepare()\sqlsrv_execute()
Table:
CREATE TABLE MyTable (
Column1 int,
Column2 int,
Column3 int
)
One complex statement (using sqlsrv_query() and sqlsrv_next_result()):
<?php
// Connection info
$server = 'server\instance';
$database = 'database';
$username = 'username';
$password = 'password';
$cinfo = array(
"Database" => $database,
"UID" => $username,
"PWD" => $password
);
// Statement with sqlsrv_query
$sql = "";
for ($i = 1; $i <= 1000; $i++) {
$sql .= "INSERT INTO MyTable (Column1, Column2, Column3) VALUES (".$i.", 0, 0);";
}
$stmt = sqlsrv_query($con, $sql);
if ($stmt === false) {
echo "Error (sqlsrv_query): ".print_r(sqlsrv_errors(), true);
exit;
}
// Clean the buffer
while (sqlsrv_next_result($stmt) != null){};
// End
sqlsrv_free_stmt($stmt);
sqlsrv_close($con);
echo "OK";
?>
One complex statement (using sqlsrv_query() and SET NOCOUNT ON):
<?php
// Connection info
$server = 'server\instance';
$database = 'database';
$username = 'username';
$password = 'password';
$cinfo = array(
"Database" => $database,
"UID" => $username,
"PWD" => $password
);
// Connection
$con = sqlsrv_connect($server, $cinfo);
if ($con === false) {
echo "Error (sqlsrv_connect): ".print_r(sqlsrv_errors(), true);
exit;
}
// Statement with sqlsrv_query
$sql = "SET NOCOUNT ON;";
for ($i = 1; $i <= 1000; $i++) {
$sql .= "INSERT INTO MyTable (Column1, Column2, Column3) VALUES (".$i.", 0, 0);";
}
$stmt = sqlsrv_query($con, $sql);
if ($stmt === false) {
echo "Error (sqlsrv_query): ".print_r(sqlsrv_errors(), true);
exit;
}
// End
sqlsrv_free_stmt($stmt);
sqlsrv_close($con);
echo "OK";
?>
Parameterized statement (using sqlsrv_prepare() and sqlsrv_execute()):
<?php
// Connection info
$server = 'server\instance';
$database = 'database';
$username = 'username';
$password = 'password';
$cinfo = array(
"Database" => $database,
"UID" => $username,
"PWD" => $password
);
// Connection
$con = sqlsrv_connect($server, $cinfo);
if ($con === false) {
echo "Error (sqlsrv_connect): ".print_r(sqlsrv_errors(), true);
exit;
}
$sql = "INSERT INTO MyTable (Column1, Column2, Column3) VALUES (?, ?, ?);";
$value1 = 0;
$value2 = 0;
$value3 = 0;
$params = array(&$value1, &$value2, &$value3);
$stmt = sqlsrv_prepare($con, $sql, $params);
if ($stmt === false ) {
echo "Error (sqlsrv_prepare): ".print_r(sqlsrv_errors(), true);
exit;
}
for ($i = 1; $i <= 1000; $i++) {
$value1 = $i;
$value2 = 0;
$value3 = 0;
$result = sqlsrv_execute($stmt);
if ($result === false) {
echo "Error (sqlsrv_execute): ".print_r(sqlsrv_errors(), true);
exit;
}
}
// End
sqlsrv_free_stmt($stmt);
sqlsrv_close($con);
echo "OK";
?>
EDIT
Why is only the first row being echoed and not all rows that meet the WHERE condition?
$sql="SELECT from_name, to_name FROM private_messages WHERE from_id='$var' OR to_id='$var'" ;
$sql2 = mysql_query($sql);
$row = mysql_fetch_array($sql2);
echo $row['from_name'];
echo $row['to_name'];
Use a "while fetch" loop.
Here's an example of the pattern, using the mysqli interface.
(NOTE: The PHP mysql interface is deprecated. New development should use PDO or mysqli.)
if ($sth = mysqli_query($con, $sql) {
//echo "#debug: query returned a result set";
while ($row = mysqli_fetch_assoc($sth)) {
//echo "#debug: fetched next row";
echo $row['from_name'];
}
//echo "#debug: exited while loop, last row already fetched";
} else {
//echo "#debug: query execution returned FALSE, handle error";
}
This is the same as the pattern used with the deprecated mysql interface. (Is there some reason you are using that interface? N.B. Do not mix mysqli_ and mysql_ functions.
Is there a way to handle multiple result sets from a single prepared query when the result sets have different columns?
I have a procedure like this:
CREATE PROCEDURE usp_CountAndList (in_SomeValue int)
BEGIN
SELECT COUNT(*) AS ListCount FROM Table WHERE SomeValue = in_SomeValue;
SELECT
Name, Cost, Text
FROM Table WHERE SomeValue = in_SomeValue
ORDER BY
Name
LIMIT 25;
END
And my PHP code looks like this:
$some_value = $_POST["SomeValue"];
if($some_value != null) {
$dbh = mysqli_connect(...connection stuff...) or die ('I cannot connect to the database.');
$query = $dbh->prepare("CALL usp_CountAndList( ? );");
$query->bind_param("i", $some_value);
if($query->execute() == true) {
$meta = $query->result_metadata();
$fields = $meta->fetch_fields();
var_dump($fields);
$query->store_result();
$query->bind_result($list_count);
while($query->fetch()) {
print_r("<TR>");
print_r("<TD>" . $list_count ."</TD>");
print_r("</TR>\n");
}
$query->free_result();
$dbh->next_result();
$meta = $query->result_metadata();
$fields = $meta->fetch_fields();
var_dump($fields);
$query->store_result();
$query->bind_result($name, $cost, $text);
while($query->fetch()) {
print_r("<TR>");
print_r("<TD>" . $name . "</TD>");
print_r("</TR>\n");
}
$query->free_result();
$dbh->next_result();
}
else {
print_r("Query failed: " . $query->error . "<BR>\n");
exit(0);
}
$query->close();
$dbh->close();
}
The issue I'm running into is that it looks like I'm getting the same meta-data for the second result set, even though it is returning a completely different set of columns, which means that my second bind_result call results in the following error:
PHP Warning: mysqli_stmt::bind_result() [<a href='mysqli-stmt.bind-result'>mysqli-stmt.bind-result</a>]: Number of bind variables doesn't match number of fields in prepared statement
I've banged my head against this for a while and am just not clear on what I'm doing wrong...it almost seems like a mysqli bug. Does anyone have some example code to show how to do what I'm attempting?
Basically there are a few requirements to make this work properly...
MYSQL 5.5.3 or higher
PHP 5.3 for mysqlnd support
This is a compile time setting so if you are using shared hosting you probably cannot change this.
Basically in your example, use $query->next_result() instead of $dbh->next_result().
mysqli_stmt::next_result
I've only found one SO example of some else attempting to do this.
I am trying to convert some old PHP ODBC queries over to PDO Prepared statements and am getting an error I cannot find too much information on.
The Error is:
"[DataDirect][ODBC Sybase Wire Protocol driver][SQL Server]There is no host variable corresponding to the one specified by the PARAM datastream. This means that this variable '' was not used in the preceding DECLARE CURSOR or SQL command. (SQLExecute[3801] at ext\pdo_odbc\odbc_stmt.c:254)"
I am searching for a single row in the database using a 6 digit ID that is stored in the database as a VARCHAR but is usually a 6 digit number.
The database connection is reporting successful.
The ID passed by the query string is validated.
The prepared statement results in the above error.
The backup straight ODBC_EXEC statement in the else clause returns the data I am looking for.
//PDO Driver Connect to Sybase
try {
$pdo = new PDO("odbc:Driver={Sybase ASE ODBC Driver};NA=server,5000;Uid=username;Pwd=password;");
$pdo_status = "Sybase Connected";
} catch(PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
if((isset($_GET['id'])) AND ($_GET['id'] != "")) {
//Validate ID String
if(!preg_match("/^[A-Za-z0-9]{5,7}/",$_GET['id'])) {
$query1_id = FALSE;
echo "Invalid ID";
exit;
} else {
$query1_id = $_GET['id'];
}
$query1 = $pdo->prepare("SELECT * FROM People WHERE PersonId= ?");
$query1->execute(array($query1_id));
if($query1->errorCode() != 0) {
$person_data = $query1->fetch(PDO::FETCH_ASSOC);
echo "Person Data from PDO: ";
print_r($person_data);
} else {
$errors = $query1->errorInfo();
echo $errors[2];
//Try the old way to confirm data is there.
$odbc_query1 = "SELECT * FROM People WHERE PersonId='$query1_id' ";
$person_result = odbc_exec($conn,$odbc_query1) or die("Error getting Data, Query 1");
$person_data = odbc_fetch_array($person_result);
echo "Person Data from ODBC_EXEC: ";
print_r($person_data);
}
It also fails if I use:
$query1 = $pdo->prepare("SELECT * FROM People WHERE PersonId= :id ");
$query1->execute(array(":id"=>$query1_id));
Does anyone have experience with this error?
Edit: Sybase Manual says this about the error...
Error 3801: There is no host variable corresponding to the one specified by the PARAM datastream. This means that this variable `%.*s' was not used in the preceding DECLARE CURSOR or SQL command.
Explanation:
Adaptive Server could not perform the requested action. Check your command for missing or incorrect database objects, variable names, and/or input data.
Which is odd because my error (quoted at the top) doesn't tell me which variable has no host.
Also fails if I use...
$query1 = $pdo->prepare("SELECT * FROM People WHERE PersonId= :id ");
$query1->bindParam(':id',$query1_id,PDO::PARAM_STR); //Or PARAM_INT
$query1->execute();
The query works if I place the variable in the query like this...
$query1 = $pdo->prepare("SELECT * FROM People WHERE PersonId= '$query1_id'");
So I think it has something to do with the parameter not being bound to the placeholder but I can't figure out why.
If I can't work this out I'll have to revert to building my query as a string and hoping my input validation is bullet proof.
Your problem seems to be with the default data type PHP assigns to variables in the placeholders. The SQL Statement is looking for a number but PHP is interpreting it as something else. You can prevent this using quotes around the placeholder variable. Notice that in the statements that work you have apostrophes ('') around the value that PHP sees:
$query1 = $pdo->prepare("SELECT * FROM People WHERE PersonId= '$query1_id'");
Try this when using the placeholder it should be the same:
$query1 = $pdo->prepare("SELECT * FROM People WHERE PersonId= ':id'");
i have written a basic stored procedure using mysql
DELIMITER //
CREATE PROCEDURE `sp_sel_test`()
BEGIN
SELECT * FROM category c;
END//
DELIMITER ;
now i m calling it from php
the php code is:
<?php
$txt = $_GET['id'];
$name = $_GET['name'];
$con = mysql_connect("localhost","four","password");
if (!$con)
{
die('Could not connect: ' . mysql_error());
}
mysql_select_db("fourthes_a", $con);
//$result = mysql_query("select * from new_c where name like %". $name ."% or c_name like %" . $name . "% order by name asc;");
$result = mysql_query("call sp_sel_test()");
if ($result === FALSE) {
die(mysql_error());
}
while($row = mysql_fetch_array($result))
{
echo $row['category_id'] . " " . $row['c_name'];
?>
<br />
<?php
}
mysql_close($con);
echo $txt;
?>
now its giving the error
PROCEDURE fourthes_a.sp_sel_test can't return a result set in the given context
mysql_query() returns false when the query fails. You didn't check if your sproc query succeeded, so most likely you're passing that boolean FALSE to the fetch function, which is rightfully complaining.
Rewrite your code like this, as a bare mininum, for proper error handling:
$res = mysql_query('call sp_sel_test()');
if ($res === FALSE) {
die(mysql_error());
}
Never ever assume a query succeeded. Even if the SQL syntax is perfect, there's far too many other reasons for a query to fail to NOT check if it worked.
You need to set client flags while connecting for using stored procedures with php. Use this:
mysql_connect($this->h,$this->u,$this->p,false,65536);
See MySQL Client Flags for more details. PHP MySQL does not allow you to run multiple statements in single query. To overcome this you must tell PHP to allow such queries by setting CLIENT_MULTI_STATEMENTS flag in your connection.
I know last answer was a year ago, but...
CREATE PROCEDURE sp_sel_test(OUT yourscalarvariable INT/TEXT...)
Statements that return a result set cannot be used within a stored function. This includes SELECT statements that do not use INTO to fetch column values into variables, SHOW statements, and other statements such as EXPLAIN. For statements that can be determined at function definition time to return a result set, a Not allowed to return a result set from a function error occurs (ER_SP_NO_RETSET_IN_FUNC). For statements that can be determined only at runtime to return a result set, a PROCEDURE %s can't return a result set in the given context error occurs (ER_SP_BADSELECT).
So, your select shoould be like this:
SELECT * FROM category **INTO** c;
http://www.cs.duke.edu/csl/docs/mysql-refman/stored-procedures.html