Basic Understanding Needed: PHP MySQLi Row Navigation - php

I'm using PHP and MySQLi and following a tutorial-type example and have some basic behavioral questions. A php code sample follows (hm, hope I format it correctly, 1st post here):
<?php
$db = new mysqli('hostLiteral', 'userLiteral', 'passwordLiteral', 'people');
$results = $db->query("SELECT * FROM people")
if($results->num_rows) {
while($row = $results->fetch_object()) {
$records[] = $row;
}
$results->free();
}
?>
I used a DBMS many, many years ago and perhaps it's that behavior that's keeping me from understanding this.
My perception is that when the 'SELECT * FROM people' is executed, MySQL would scan the entire database, get all the pertinent data, and end up 'pointing' at the last record in the database. So when the 'while' loop begins, I would think that's where it's starting, it would only get the last record or row and be done.
But even if it did somehow start at the top of the database at the first row, how does it know to move through the database unless 'fetch_object()' is somehow telling it to move to the next row?
I'm certain I'm missing basic SQL behavior here. But I've not found a good explanation to dispel these concepts that were placed in my head years back.
Thanks for any aid in understanding!

The query leaves the cursor pointing at the first row of your result set rather than the last. Since the general use case is to move forward through the rows, it would make little sense to leave the cursor at the last row.
In addition, fetch_object does exactly what you surmise. It gets the current row and advances the cursor to the next row. It's interesting that the documentation doesn't state that explicitly (like it does with fetch_row, but the example given makes it clear enough that that's what it does:
/* fetch associative array */
while ($obj = mysqli_fetch_object($result)) {
printf ("%s (%s)\n", $obj->Name, $obj->CountryCode);
}

Related

PHP While loop Update stopping at first row

I made a while loop that will tell if there are items in the transaction history and will put it back in the inventory as the transaction ends but the problem is. It fetches an error called
Uncaught Error: Call to a member function fetch_assoc() on bool in
So I tried the query's one by one and it works.
I tried to experiment and comment the other query and adding a counter++; to tell if the loop works. The problem I found is that after the first if else the counter only add's 1 Where as if I only try this it loops 4 which is right
Query I tried to check the number of loops
$counter=0;
$sql="SELECT * FROM brb_backtransaction WHERE trans_uk='$curr_trans' ";
if($rs=$con->query($sql)){
while ($row=$rs->fetch_assoc()){
$counter++
}
}
It echoes 1234 so it's correct but when the first if else happen it only echo'es 1
$counter =0;
$sql="SELECT * FROM brb_backtransaction WHERE trans_uk='$curr_trans' ";
if($rs=$con->query($sql)){
while ($row=$rs->fetch_assoc()){
$item = $row['trans_item'];
$quan = $row['trans_quantity'];
$sqlsitem="SELECT itmQuantity FROM brb_inventory WHERE itmName='$item'";
if($rs=$con->query($sqlsitem)){
$quanrow = $rs->fetch_assoc();
$currquan = $quanrow['itmQuantity'];
$counter++;
}
I remove other query's as I think this is the problem I ran a total of 3 queries in the while loop.
For your while loop to work it needs access to your original data stream as stored in the $rs variable.
However, further down in your code you are replacing the contents of the $rs variable with a totally new data stream. Therefore, the while loop no longer has access to the original data stream as it is basically throw away.
To solve this, change the second instance to another variable name such as $rs2. That way you have two completely different variables for two different data streams.
With that said, your code is also open to injection attacks. I would recommend looking into PDO and prepared statements.
Also, Tangentially Perpendicular is correct in using SQL JOINS

function render makes website 500% slow! can anyone fix that please?

Function render makes website 500% slow! Can anyone fix that please ?
Someone told me :
because it sends a database request on each iteration of the loop (it's not the only problem with this chunk of code but it's the most taxing one)
Yes I understand what that means. His way is:
you need to get all of the data before you start building the menu,
then you just insert the data instead of requesting more data on each
iteration
But i don't know how i must do it!
<?php
$menu_html='';
function render_menu($parent_id,$actmenuid)
{
$obj = new Database();
$con = $obj->dbconnectt();
global $menu_html;
$result=mysqli_query($con, "select * from tbl_menu where parent_id='$parent_id'");
if(mysqli_num_rows($result)==0) return;
if($parent_id==0){
$menu_html.='<ul class="topnav">';
}else{
$menu_html.='<ul>';
}
while($row=mysqli_fetch_array($result)) {
$childnum = $obj->recordcount("SELECT * FROM tbl_menu WHERE parent_id='".$row['id']."'");
if($childnum == 0){
$linkvalue='/category/'.$row['id'].'.html';
} else{
$linkvalue='#';
}
if($row['id']==$actmenuid && $actmenuid !=NULL){
$actv='class="active"';
}else{
$actv='';
}
$menu_html.='<li '.$actv.'>'.$row['title'].'';
render_menu($row['id'],$actmenuid);
$menu_html.='</li>';
}
$menu_html.='</ul>';return $menu_html;
}
if($isDsh==false){
echo render_menu(0,$actmenuid);
}
?>
Depending on how many records you have, try removing this query from inside the loop since it's running for every record on the first query.
$childnum = $obj->recordcount("SELECT * FROM tbl_menu WHERE parent_id='".$row['id']."'");
Change it a single query like this where it returns counts for each parent idea, and place it outside of the loop:
$parentcount = mysqli_query($con, ("SELECT parent_id, count(*) FROM tbl_menu GROUP BY parent_id");
There may be other issues, so please post the database structure and number of records that you're working with too.
Don't make recursive queries.
Having "more than 1000" rows is not too big. You can simply call everything from the table into php, then perform the recursive html build in php this will have a memory overhead, but far less processing overhead because you only ever make one trip to the db.
Alternatively (when your db table is prohibitively large), you should avoid gathering rows unnecessarily by adding a new column. The new column will store all "descendants" for the respective row when the row is INSERTed or update it when it is UPDATEd. Then you only need to reference this column when needing to call specific rows. In other words, do the recursive processing only once (when writing to the db) AND not when needing to display the data. This will, again, produce a finite result set in one query which can then be recursively traversed to build the desired output.
basically you need to do what #spudly has suggested.
But there is a small catch in his solution which depending on the number of the rows in yous tbl_menu table you may use a big chunk of memory to fetch all the records.
you can optimise it more with using his solution but changing the query to:
select
parent_tbl_menu.id,
count(child_tbl_menu.id) as cnt
from
tbl_menu as parent_tbl_menu
left join
tbl_menu as child_tbl_menu
on parent_tbl_menu.id = child_tbl_menu.parent_id
where
parent_tbl_menu.parent_id = ?
group by
parent_tbl_menu.id
This way you will only fetch the child records of a specific parent.
And please consider using prepared statements as your code has sql injection vulnerability.
Connect (from PHP to MySQL) only once for the entire web page.
Don't put a SELECT inside a loop if you can do all the work in a single SELECT, such as with a JOIN. (Exception: A "hierarchical" table needs the nested SELECT. Exception to the exception: MySQL 8.0 and MariaDB 10.2 can do it with a "recursive CTE".)
Don't fetch all the columns (SELECT *) when all you want it is a recordcount. Instead, SELECT COUNT(*) ... and use the number returned.
1000 of anything is probably excessive for a web page. Re-think the UI.

Use your fetch data from Mysql and reuse [duplicate]

$data=$stmt->fetchAll(); //Dumping the data shows the result. It is also setting the cursor at the end
while($data=$stmt->fetch())
{
//Does not enters loop
//If fetchAll() removes it work as usual
}
I know It dont need to fetch data twice. But my main question is How to reset cursor position in PDO?
AFAIK there is no possibility to reset cursor position with PDO - that might something to do with compatibility with some databases, that don't support resetting internal cursors.
If you want to iterate twice over the results, fetch it to the array and iterate over this array:
<?php
$results = $stmt->fetchAll();
foreach($results as $row) {
// first
}
foreach($results as $row) {
// second
}
Edit Some databases support scrollable cursors. To use that, add PDO::CURSOR_SCROLL flag to prepare method (see examples at PDOFetch documentation page). But that only adds possibility to move forward or backward, not rewind completely. Also, not all databases support that type of cursor (e.g. MySQL doesn't).

Trouble retrieving data using PDO syntax, PHP

I'm brand new to the PDO syntax and I'm liking the learning curve! I'm refactoring code - migrating over from archaic mysqli_* methods.
Predictably, I've run into some snags, being a novice. One of the big ones is (forgive if this is a dumb question) retrieving data from the DB and echoing it out on the page. Here is what I have so far:
$getURLid = $_GET['id'];
$idQuery = $connection->prepare("SELECT * FROM pages WHERE page_id = :getURLid");
$idQuery->execute(array(':getURLid' => $getURLid));
$idRetrieved = $idQuery->fetchAll(); // This is the part I'm unclear on.
When I echo $idRetrieved['value'] to the page, nothing shows up. I'm missing something or misunderstanding how it works. Maybe fetchAll isn't what I should be using.
If it is, is a loop necessary to retrieve all rows? I was under the impression that fetchAll would loop through them automatically based on what I've read.
Thanks for the help.
Read the doco for PDOStatement::fetchAll closely. It returns an array of row data.
The type of data representing each row depends on your fetch mode which by default is PDO::FETCH_BOTH. This would mean each row is an array with both numeric and associative keys. If you're only going to access the data associatively, I'd recommend using PDO::FETCH_ASSOC, eg
$idRetrieved = $idQuery->fetchAll(PDO::FETCH_ASSOC);
You would then either need to loop or access each row via its index, eg
foreach ($idRetrieved as $row) {
echo $row['value'];
}
// or
echo $idRetrieved[0]['value']; // assuming there's at least one row.

Newbie trying to decipher merge command associated code

Someone retired in our group and I'm trying to figure out what his merge statement (and associated code) does so I can determine how to convert some (not all) values to integer before sending up. See comments below for questions. I am an absolute newbie with Microsoft SQL and took a class in php a few years ago, but don't have much experience. I've tried googling the merge command but I'm having trouble with a couple parts in it. See my questions below. (// ?)
I've looked at:
http://php.net/manual/en/pdo.query.php
http://stackoverflow.com/questions/4336573/merge-to-target-columns-using-source-rows
http://pic.dhe.ibm.com/infocenter/iseries/v7r1m0/index.jsp?topic=%2Fsqlp%2Frbafymerge.htm
I realize these are basic questions but I'm trying to figure it out and nobody around here knows.
function storeData ($form)
{
global $ms_conn, $QEDnamespace;
//I'm not sure what this is doing?? I thought this was where it was sending data up??
$qry = "MERGE INTO visEData AS Target
USING (VALUES (?,?,?,?,?,?,?,?,?,?))
AS Source (TestGUID,pqID, TestUnitID, TestUnitCountID,
ColorID, MeasurementID, ParameterValue,
Comments, EvaluatorID, EvaluationDate)
ON Target.pqID = Source.pqID
AND Target.MeasurementID=Source.MeasurementID //what is this doing?
AND Target.ColorID=Source.ColorID //what is target and source?
WHEN MATCHED THEN
UPDATE SET ParameterValue = Source.ParameterValue,
EvaluatorID = Source.EvaluatorID, //where is evaluatorID and source? My table or table we're send it to?
EvaluationDate = Source.EvaluationDate,
Comments = Source.Comments
WHEN NOT MATCHED BY TARGET THEN
INSERT (TestGUID,
pqID, TestUnitID, TestUnitCountID,
ColorID, MeasurementID,
ParameterValue, Comments,
EvaluatorID, EvaluationDate, TestIndex, TestNumber)
VALUES (Source.TestGUID, Source.pqID,
Source.TestUnitID,
Source.TestUnitCountID,
Source.ColorID, Source.MeasurementID, Source.ParameterValue,
Source.Comments, Source.EvaluatorID, Source.EvaluationDate,?,?);";
$pqID = coverSheetData($form);
$tid = getBaseTest($form['TextField6']);
$testGUID = getTestGUID($tid);
$testIndex = getTestIndex ($testGUID);
foreach ($form['visE']['parameters'] as $parameter=>$element)
{
foreach ($element as $key=>$data)
{
if ( mb_ereg_match('.+evaluation', $key) === true )
{
$testUnitData = getTestUnitData ($form, $key, $tid, $testGUID);
try
{
//I'm not sure if this is where it's sent up??
//Maybe I could add the integer conversion here??
$ms_conn->query ($qry, array(
$testGUID, $pqID,
$testUnitData[0], $testUnitData[1], $testUnitData[2],$element['parameterID'], $data, $element['comments'] $QEDnamespace->userid, date ('Y-m-d'), $testIndex, $tid));
}
catch (Zend_Db_Statement_Sqlsrv_Exception $e)
{
dataLog($e->getMessage());
returnStatus ("Failed at: " . $key);
}
}
}
}
}
This is a bit long for a comment. If you are using SQL Server, then look at the SQL Server documentation on merge. All the SQL Server documentation is on line, and it is very easy to find via Google (and perhaps even easier using Bing).
The purpose of the MERGE command is to do both inserts and updates in one step. Basically, you have a table that has new data ("source") and a table to be updated ("target"). When a record matches, then update the existing record in the target with matching record in source. When a record doesn't match, then insert it into target.
The main advantage of MERGE over two statements is not necessarily the elegant and intuitively obvious syntax. The main advantage is that all the operations occur in a single transaction, so either they all succeed or all fail as one.
The syntax actually isn't that bad. I would recommend that you set up a test database and try a few examples on your own, so you at least understand the syntax. Then, return to this code. When doing so, print out the resulting merge statement and put it in SQL Server Management Studio, where you will have nice color coded key words for the statement. Then go through it step by step, and you'll probably find that it makes lots of sense.

Categories