check if last row or not when running loops in PHP - php

I am using this PHP Code to run queries to a MySQL Database:
$i=0;
$display='[';
$stmt = $pdo_conn->prepare("SELECT * from tickets where status = :status and deleted = :deleted ");
$stmt->execute(array(':status' => 'Open', ':deleted' => ''));
$records = $stmt->fetchAll(PDO::FETCH_ASSOC);
$counter = count($records);
foreach($records as $result) {
$i++;
$stmt = $pdo_conn->prepare("SELECT * from contacts where sequence = :sequence ");
$stmt->execute(array(':sequence' => $result["contact"]));
$contact = $stmt->fetch();
$display.='{';
$display.='"customer":"'.$contact["forename"].' '.$contact["surname"].'",';
$display.='"subject":"'.$result["subject"].'"';
if($counter == $i) {
$display.='},';
} else {
$display.='}';
}
}
$display.=']';
I would ideally like the end result to show as the below:
[{"customer":"Carol","subject":"Fax not working"}{"customer":"Clive","subject":"VoIP Issues"}{"customer":"Leigh ","subject":"company Antaeus"}{"customer":"Debbie","subject":"emails"}{"customer":"Kim","subject":"Printer setup"}{"customer":"Sue ","subject":"Phone"}{"customer":"Sandra","subject":"Debbie's computer "}{"customer":"Daniel","subject":"Email Attachments"}{"customer":"Lara","subject":"Internet Issues"}]
However, at the moment it looks like:
[{"customer":"Carol","subject":"Fax not working"}{"customer":"Clive","subject":"VoIP Issues"}{"customer":"Leigh ","subject":"company Antaeus"}{"customer":"Debbie","subject":"emails"}{"customer":"Kim","subject":"Printer setup"}{"customer":"Sue ","subject":"Phone"}{"customer":"Sandra","subject":"Debbie's computer "}{"customer":"Daniel","subject":"Email Attachments"}{"customer":"Lara","subject":"Internet Issues"},]
Notice the comma on the end before the ]
How can i make sure that comma does not show at the end, I tried using the if statement in my loop with the counters ($counter and $i) but that didn't work

You can do this without any loops and a single inner join. Instead of manually constructing a JSON string, you are advised to use json_encode() to do it for you. Run a single join query, and collect the rows into a single array which is encoded as JSON..
This eliminates the need for all your other machinery - loops, and counters.
// A single join query will return everything you need from both tables.
// Customer names can be concatenated here into a single Customer field
// You only appear to need tickets.subject.
// And reading your queries, the table relation appears to be tickets.contact = contacts.sequence
$sql = '
SELECT
t.subject AS Subject,
CONCAT(c.forename, ' ', c.surname) AS Customer
FROM
tickets t
INNER JOIN contacts c ON t.contact = c.sequence
WHERE status = :status AND deleted = :deleted;
';
$stmt = $pdo_conn->prepare($sql);
if ($stmt) {
$stmt->execute(array(':status' => 'Open', ':deleted' => ''));
// Fetch all rows...
$rows = $stmt->fetchAll();
// Now $rows looks like the 2D array needed, you can directly JSON encode it
$output = json_encode($rows);
}
// Inspect it...
echo $output;
I'll point out that although you are absolutely right to be in the habit of bound parameters, there actually isn't a need for them here since the strings 'Open' and '' are static and known. You can just call a plain query() on this instead of prepare()/execute().
// No user input, static strings only, means no need for params
$sql = "
SELECT
t.subject AS Subject,
CONCAT(c.forename, ' ', c.surname) AS Customer
FROM
tickets t
INNER JOIN contacts c ON t.contact = c.sequence
WHERE status = 'Open' AND deleted = '';
";
// Just a simple query() call
$result = $pdo_conn->query($sql);
if ($result) {
$rows = $result->fetchAll();
// etc...
}

Related

How can I use mysqli to SELECT and both DELETE and RETURN a single row based on WHERE condition

How can I do a mysqli php query so a SELECT request both RETURNS the row AND DELETES the same row in the MySQL DB?
This php script works fine to query and return a single row based on max numerical zipcode (an example) but my modifications do not complete the delete row:
<?php
echo phpversion();
// Create connection
$con = mysqli_connect("localhost", "mysqluser", "mysqlpass", "mysqldb");
// Check connection
if (mysqli_connect_errno()) {
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
// Select all from designated assigned bot
$sql = "SELECT * FROM mysqltable WHERE zipsize=( SELECT max(zipsize) FROM mysqltable ) LIMIT 1;";
// Confirm there are results
if ($result = mysqli_query($con, $sql)) {
// We have results, create an array to hold the results
// and an array to hold the data
$resultArray = array();
$tempArray = array();
// Loop through each result
while ($row = $result->fetch_object()) {
// Add each result into the results array
$tempArray = $row;
array_push($resultArray, $tempArray);
}
// Encode the array to JSON and output the results
echo json_encode($resultArray);
}
// Close connections
mysqli_close($con);
?>
I tried adding this line in various places to delete the row to no success using Id, which is a unique code for every row in the table (also tried with $result and $resultArray instead of $row):
$delsql= mysqli_query($conn,"DELETE FROM mysqltable WHERE Id= $row['Id']");
If you want to select the data and then immediately delete that selected data then you need to run two separate queries. I don't really know why you have so much unnecessary code there, but I would do it this way:
<?php
// Create connection
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$con = new mysqli("localhost", "mysqluser", "mysqlpass", "mysqldb");
$mysqli->set_charset('utf8mb4'); // always set the charset
// Select all from designated assigned bot
$sql = 'SELECT * FROM mysqltable WHERE zipsize=( SELECT max(zipsize) FROM mysqltable ) LIMIT 1';
$resultArray = $con->query($sql)->fetch_all(MYSQLI_ASSOC);
echo json_encode($resultArray);
// now delete
$sql = 'DELETE FROM mysqltable WHERE zipsize=( SELECT max(zipsize) FROM mysqltable ) LIMIT 1';
$resultArray = $con->query($sql);
If you don't need nested arrays and you only ever fetch a single row then you can replace fetch_all(MYSQLI_ASSOC) with fetch_assoc() instead.
Careful! These queries can have unexpected results due to the possibility of selecting a different row than the one you delete unless you specify ORDER BY.
If you have a primary/unique key in the table to uniquely identify the rows you are selecting/deleting then you can store the list of ids and then execute a WHERE IN() query.
For example:
// Select all from designated assigned bot
$sql = 'SELECT * FROM mysqltable WHERE zipsize=( SELECT max(zipsize) FROM mysqltable )';
$resultArray = $con->query($sql)->fetch_all(MYSQLI_ASSOC);
echo json_encode($resultArray);
// get all ids from our array
$ids = array_column($resultArray, 'Id');
// now delete
$sql = 'DELETE FROM mysqltable WHERE Id IN('.str_repeat('?,', count($ids) - 1) . '?)';
$stmt = $con->prepare($sql);
$stmt->bind_param(str_repeat('s', count($ids)), ...$ids);
$stmt->execute();

Merge two SELECT statements and return one JSON response

I would like to merge the results of two select statements and return one single JSON response. Basically, I want to return id, name, company, and email for every record found, but also return the signature column if the ticket matches the POST data.
Also, is it possible to remove any duplicate records in the final array?
$selectTable = "tickets_info";
$selectColumns = "id, name, company, email";
$stmt = $conn->prepare("SELECT $selectColumns FROM $selectTable WHERE created BETWEEN DATE_SUB(NOW(), INTERVAL 30 DAY) AND NOW() ORDER BY id DESC");
$stmt->execute();
if (isset($_POST['ticket'])) {
$stmt2 = $conn->prepare("SELECT signature FROM tickets_info WHERE ticket = ?");
$stmt2->execute(array($_POST['ticket']));
}
$results = $stmt->fetchAll();
if (isset($_POST['ticket'])) {
$results = $stmt2->fetchAll();
}
if ($results) {
header("Content-Type: application/json");
echo json_encode($results);
}
I would loop through the query response with something along these lines:
$stmt = $conn->prepare("SELECT id, name, company, email, signature, ticket FROM tickets_info WHERE created BETWEEN DATE_SUB(NOW(), INTERVAL 30 DAY) AND NOW() ORDER BY id DESC");
$stmt->execute() ;
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
$response[$i]['id'] = $row['id'] ;
$response[$i]['name'] = $row['name'] ;
$response[$i]['company'] = $row['company'] ;
$response[$i]['email'] = $row['email'] ;
if ($row['ticket'] == $_POST['ticket']) $response[$i]['signature'] = $row['signature'] ;
else $response[$i]['signature'] = NULL ;
$i++ ;
}
header("Content-Type: application/json");
echo json_encode($response);
You only do one SQL query and loop through the response once. If that row matches the AJAX ticket query, we add the signature to the array of objects. If not, we set the signature value to null.
The right way to do is use Join . If you don't want to do it with join you can first put the results in two different arrays. then merge those two seperate arrays and then filter any duplicate arrays.
Remember you want to have the same keys for both Sql Queries.

How do I structure an array in this desired format that is run inside a foreach() loop?

In the following scenario, $communityPlayerIds is an array of the Id's of people in a community, and $noPlayers is the count of that array.
e.g
$communityPlayerIds = [2,5,6]
$noPlayers = 3
The following function should do the following:
Run an sql query for the number of times represented by $noPlayers, each time retrieving the desired data of a different $communityPlayerId.
At the moment this is creating one new array, players of 24 items, 8 for each player.
public function getCommunityForm($communityId, $noPlayers, $communityPlayersIds){
$returnValue = array();
$i = 0;
foreach ($communityPlayersIds as $cPI){
$sql = " SELECT player1_result, player1_name, date , results_id FROM `results` WHERE player1_id = '".$cPI."' AND community_id = '".$communityId."' UNION ALL SELECT player2_result, player2_name,date, results_id FROM `results` WHERE player2_id = '".$cPI."' AND community_id = '".$communityId."' ORDER BY date DESC Limit 8";
$result = $this->conn->query($sql);
if (mysqli_num_rows($result) === 0) {
$returnValue[] = ['status' => "nil"];
}
if($result != null && (mysqli_num_rows($result) >= 1)){
while($row = $result -> fetch_array(MYSQLI_ASSOC)){
if(!empty($row)){
$returnValue['players'][$i] = $row;
$i++;
}
}
}
}
return $returnValue;
}
What I want is to return a single array, that has within it 3 separate arrays, 1 for each query run.
How do I do this?
Use two separate counters. Use the $i counter for the queries, and another counter for the rows of each query.
In our code, move the increment of $i to the end of the foreach loop, so it gets incremented only one time each pass through that outer loop.
$i = 0
foreach ($communityPlayersIds as $cPI){
$sql = "...";
// process each query
$i++;
}
Within the body of the foreach loop, when you process the rows returned by a query, use another counter for the rows. Initialize before the loop, and increment as the last step in the loop.
And add another dimension to your result array
$rn = 0;
while($row = $result->fetch_array(MYSQLI_ASSOC)){
//
$returnValue['players'][$i][$rn] = ... ;
rn++;
}
EDIT
As Paul Spiegel notes, the $rn counter isn't strictly necessary. An assignment to an array using empty square brackets will add a new element to an array.
while($row = $result->fetch_array(MYSQLI_ASSOC)){
//
$returnValue['players'][$i][] = ... ;
}
First thing I did was simplify your database query (well, I at least made it more efficient) by getting rid of the UNION with the second query.
Next, I set up a prepared statement. This reduces all the overhead of repeated queries to the database. It's been a few years since I worked with mysqli but I believe it should be working, as long as your ID columns are all numbers. I would hope so, but your original code had quotes around them. If they're strings, change iiiii to sssss, and seriously reconsider your database schema (more on that below.)
You don't need a counter since you already have the player ID, just use that as the array index.
public function getCommunityForm($communityId, $noPlayers, $communityPlayersIds){
$sql = " SELECT IF(player1_id=?, player1_result, player2_result) AS result, IF(player1_id=?, player1_name, player2_name) AS name, date, results_id FROM `results` WHERE (player1_id=? OR player2_id=?) AND community_id=? ORDER BY date DESC Limit 8";
$stmt = $this->conn->prepare($sql);
foreach ($communityPlayersIds as $cPI) {
$stmt->bind_param("iiiii", $cPI, $cPI, $cPI, $cPI, $communityId);
$stmt->execute();
if ($result = $stmt->get_result()) {
while($row = $result->fetch_array(MYSQLI_ASSOC)){
$returnValue['players'][$cPI][] = $row;
}
}
}
}
return $returnValue;
}
And for free, here's the PDO version. I'd strongly recommending looking into PDO. It's more modern and less verbose than mysqli. You'll notice no binding of parameters, we get to used named parameters, and getting an array out of it is much easier.
public function getCommunityForm($communityId, $noPlayers, $communityPlayersIds){
$sql = " SELECT IF(player1_id=:pid, player1_result, player2_result) AS result, IF(player1_id=:pid, player1_name, player2_name) AS name, date, results_id FROM `results` WHERE (player1_id=:pid OR player2_id=:pid) AND community_id=:cid ORDER BY date DESC Limit 8";
$stmt = $this->conn->prepare($sql);
foreach ($communityPlayersIds as $cPI) {
if ($stmt->execute([":pid"=>$cPI, ":cid"=>$communityID])) {
$returnValue['players'][$cPI] = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
return $returnValue;
}
As for your database schema, you should not have a column for player names in your result table. How many times are names repeated in that table? What if a user wanted to change their name? You should instead have a player table, and then use a join to pull in their details.

Mysql return joined table as sub-array

I'm using my mysqli query as JSON output.
This is my query:
$sql = "SELECT p.*,comments.*,COUNT(comments.id) AS numComments FROM people AS p
LEFT JOIN comments ON comments.people_id = p.id
WHERE p.name LIKE '%".$data."%'";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
// output data of each row
while($row = $result->fetch_assoc()) {
$myArray[] = $row;
}
echo json_encode($myArray);
} else {
echo 0;
}
What I want to do, is to group all the person comments and place it under the person data. for example:
The desired output is that I will be able to access the comments not directly from the query, but from the person itself.
for example:
echo $person['comment'][0]['content']
Currently, I access it like any of the query data..
echo $person['content']
JSON OUTPUT:
As you can see, the person data and the comment data is mixed in the same array. I want the comments to have an array of their own.
Any ideas?
Edit: Seem like I have problem with the query. it returns only the first comment!
You need so-called eager loading for this task. It will involve two queries, but it will be way better than one.
You have to select your people first, then get people ids and query comments table for the comments. And finally you will have to combine the two arrays together.
I will use PDO as it will take 5 times less code to write, thanks to some neat PDO features.
$stmt = $conn->prepare("SELECT * FROM people WHERE name LIKE ?");
$stmt->execute(["%$data%"]);
$people = $stmt->fetchAll(PDO::FETCH_UNIQUE);
$ids = array_keys($people);
$in = str_repeat('?,', count($ids) - 1) . '?';
$stmt = $db->prepare("SELECT people_id, * FROM comments WHERE people_id IN ($in)");
$stmt->execute($ids);
$comments = $stmt->fetchAll(PDO::FETCH_GROUP);
foreach($people as $id=>$row)
{
$row['comments'] = $comments[$id];
$people[$id] = $row;
}

Php stuck because to long load on mysqli query in php function, how to fix?

i'm using a php function to get dome mysql data from other mysql host than my webserver.
Function:
public function theMysqli($build){ // $build is given by othe code (no usedata)
$mysqli = new mysqli($server, $user, $password, $database);
$catid = array( /* +/- 40 id's */ ); //data is $catid = configs::songcats();
$type = array( /* +/- 15 captical letters */ ); //data is $type = configs::songtype();
$limit = 20;
$lstart = $_post['page'];
if($lstart == ''){
$lstart = 0;
}
else{
$lstart = $lstart * $limit;
}
$sletter = $_POST['letter'];
$search = $sletter.'%';
$catquery = "SELECT songid FROM category WHERE catID IN('".implode("', '", $catid)."')";
if ($db = $mysqli->query($catquery)){
while($row = $db->fetch_array()){
$idsong[] = $row;
}
$db->close();
}
foreach($idsong as $gt){
$songid[] = $gt['songid'];
}
// songid is a array over the 30000 values
$countquery = "SELECT id FROM songlist WHERE songtype IN('".implode("', '", $type)."') AND id IN('".implode("', '", $songid)."') AND songname LIKE '".$search."'";
if ($db = $mysqli->query($countquery)){
$countr = $db->num_rows;
$db->close();
}
$pages = ceil($countr / $limit);
$songquery = "SELECT id, songname, artist, copyright, duration FROM songlist WHERE songtype IN('".implode("', '", $type)."') AND id IN('".implode("', '", $songid)."') AND songname LIKE '".$search."' ORDER BY songname ASC LIMIT $lstart, $limit";
if ($db = $mysqli->query($songquery)){
while($row2 = $db->fetch_array()){
$result[] = $row2;
}
$db->close();
}
if($built == 'counter'){
$final == $pages;
}
else if($build == 'gresult'){
$final == $result;
}
return $final;
}
Now my problem is the load time he need for this script it will be to long. Even when i set php.ini so that execute may be 300sec he will stuck by loading the page. Now i know you can get data grom mutiple mysql tables by one query but i can't find any solution to do that in combination with php implode function.
Total rows i must get by $_POST['letter'] M is +/- 1200; (web radio mp3 database)
Can someone help me to fix this function so i get no timeout's anymore.
Thanks
The problem here is that you're fetching a list from the database, and then sending that list back as part of a query. You should really be doing most of this stuff in SQL, using either JOIN or nested queries. This will make your program much faster.
First, create a table for all of your catids and types. Your catquery should then be:
SELECT songid
FROM category
WHERE catID IN (
SELECT id
FROM catids
)
Use the same sort of pattern to join your queries together. It looks like you can cut down most of your code here to just one SQL query. You'll save a ton of time and memory by not having to send all that data back and forth between your program and the database.
Some reading material for you:
SQL Joins: http://beginner-sql-tutorial.com/sql-joins.htm
SQL Subquery: http://beginner-sql-tutorial.com/sql-subquery.htm

Categories