Displaying a multi-table mysql query - php

This is going to be really hard to explain but I'll attempt to do my best without confusing you all. I have the following table design;
Here is my mysqlFiddle to demonstrate the issue: http://sqlfiddle.com/#!2/1363c/1
As you can see, the tables work fine and the query is displaying what shelter has what service but my issue comes when I attempt to display this information on my web. I'm currently using the following code;
<?php
echo '<p>', "<strong>Shelter id</strong>: " . $query['shelter_id'].'</p>';
echo '<p>', "<strong>Shelter name</strong>: " . $query['shelter_name'].'</p>';
echo '<p>', "<strong>Street</strong>: " . $query['street'].'</p>';
echo '<p>', "<strong>City</strong>: " . $query['city'].'</p>';
echo '<p>', "<strong>Postcode</strong>: " . $query['postcode'].'</p>';
echo '<p>', "<strong>Contact Number</strong>: " . $query['phone_number'].'</p>';
echo '<p>', "<strong>Location</strong> : " . $query['location_name'].'</p>';
?>
but with the query I'm using, Its not going going to dupe all the information into one row it is, its going to create multiple row's for the service_name, I think.
Is it possible for me to create a query that will return all service's the shelter can do on one row?
Edit: Will i have to do 2 separate query's? One to get the generic shelter information and then another query to return the services into an array and then just run through them on the page?

how about using GROUP_CONCAT? What it does is it combines the values into comma separated value. And you can later use Explode() in PHP.
SELECT shelter_name,
GROUP_CONCAT(services.service_name) service_name
FROM shelter
INNER JOIN shelter_service
ON shelter.shelter_id= shelter_service.shelter_id
INNER JOIN services
ON shelter_service.service_id = services.service_id
GROUP BY shelter.shelter_id, shelter_name
ORDER BY shelter_name
SQLFiddle Demo

GROUP_CONCAT() use only if you have a limited number of rows (10-100), if the data is larger will slow your query a lot.
2 queries (one for types one for data)
keep this query and modify the result (my favorite way).
On server or JS loop trough your data and restructure it (add 1 extra dimension)
var new_data = {};
for(var i in data)//data is your $query
{
var row = data[i];
//create another layer of the array (make it in 2 dimensions)
if (typeof(new_data[row['Service_name']) == 'undefined)
new_data['Service_name'] = [];
//add only this kind of type data
new_data['Service_name'].push(data);
}
//now you can remove the data and use new_data
.4. Make sure the query is primarly sorted by Service name, and when you loop at display (echo) you show the current service name.
$current_name = '';
for(...)
//if we passed to a new service in the loop
if ($query['service_name'] != $current_name) {
echo '<h3>'.$query['service_name'].'</h3>;//display the new service
$current_name = $query['service_name'];
}

Related

PHP displays only the first value in a for() loop insinde another foreach() loop

I have small issue with a for() loop that displays values inside another foreach() loop in a dynamic way.
A small background of the code:
The output table is created dynamically based on the values obtain from one mysql query SELECT (which is the 1st select) from 1 column (that has name "coloane") in a table called "categories";
The values stored in the column "coloane" are actually the name of some columns from another table called "articles"
The first mysql select is to obtain the values from the column "coloane" so I can create the second.
After the second table returns rows (num_rows() > 0) I start to create the table.
The table has 2 static columns that are always present, and the rest of the columns are added dynamically based on different criteria.
The plot:
I explode the values obtained from the first select - from the column "coloane" - to make the [th]'s for the table header;
this explode I store it under the variable "$rows_th";
to make the [th]'s for the table header, i use a foreach() loop (and here I also remove any underscores);
after that I proceed to create the [tbody] and the [td]'s using another foreach(), only that this foreach is done for the values of the second select;
inside this foreach() I create another loop, using the for() function on the variable "$rows_th" so the table has the same length;
here the values are combined like this:
'<td id="'.$i.'">'.$values_td[$rows_th[$i]].'</td>';
The main issue is that, using the format listed above, it will only display the first value!
When I tried to verify the "$values_td[$rows_th[$i]]" using the is_string() function, I discovered that the first value is a string, and after that, every other value is not a string...
The test performed:
if (is_string($values_td[$rows_th[$i]]))
print '<td id="'.$i.'">Value found!!!</td>';
} else {
print '<td id="'.$i.'">No luck...</td>';
}
Any ideas on what might be going wrong ?
I know I'm missing something, but I just can't seem to figure it out :-/
The entire code is listed here: http://pastebin.com/4MFifh92
In the mean time, with the help from a friend, I finally found what was going wrong.
The variable turns from string to number because in the table called "categories", the column named "coloane" has the values inside devided by a "," (comma) and a " " (space), like so
------------------------------------------
| coloane |
------------------------------------------
make, model, location, comments, status
------------------------------------------
To fix this, I found out that I can try to do a str_replace in a simple foreach() loop :)
Something like this:
$i=0;
foreach($rows_th as $values_th) {
$i++;
$cols_td = str_replace(' ', '', $values_th);
$actual_value = $values_td->$cols_td;
print '<td id="'.$i.'">'.$actual_value.'</td>';
}
And this did the trick :)
Hope this will help others that came across with issues similar to this :)
Best regards,
Mike
Can't you just do something like:
SELECT * FROM results_query_header_by_cat WHERE `id` = 3;
Then:
SELECT * FROM results_query_content_by_cat WHERE `id_category` = 3;
Then with the results:
echo '[thead]';
foreach ( $results as $result ){
foreach( $result['coloane'] as $header ){
echo '[th]'.$header.'[/th]';
}
}
echo '[/thead][tbody]';
foreach ( $records as $record ){
echo '[tr][td]' .
$result['producator'] . '[/td][td]' .
$result['model'] . '[/td][td]' .
$result['imei'] . '[/td][td]' .
$result['locatie'] . '[/td][/tr]';
}
echo '[/tbody]';
Is that what you're looking for?
e: I haven't tried this but I'd imagine it's something like that. Just:
get the headers by category ID
get the records by category ID.
Echo the first result set as headers with a loop,
echo the second result set as table rows with columns in a loop.

PHP multiple entries

I'm creating a small project with PHP/MYSQL but i can't get my query working the way i need it. I have 2 tables
Table 1 (char):
Id, name.
Table 2 (spells):
Id, char, spell_name.
I'm getting the output:
Name Spell1
Name Spell2
Name Spell3
But I need it to be:
Name Spell1
Spell2
Spell3
Here's my query:
$query = "SELECT char.name AS name, spells.spell_name AS spell
FROM char, spells
WHERE (char.id = spells.spell_name)";
Any ideas?
I think you're gonna have to first get the ID of the character to query, and then pull the spells s/he has access to. Example:
$char_id = 0; // value would be assigned arbitrarily.
$query = "SELECT *
FROM 'spells' s
WHERE s.char = $char_id;";
$result = $pdo->query($query);
while($row = $result->fetchObj()){
// do something with the spells obj here
}
With SQL, you need to grab full rows at a time, so I believe the situation you want isn't possible.
As Goldentoa11 wrote. Make two selects, or create query with two result sets (more selects in one command), or accept current state (is normal and you can verify data consistency). I prefer current state, but sometimes use any of described solution (based on query frequency, size of result etc.).
If you need to list such data, you can than use something like this:
$currentName = null;
while ($row = mysql_fetch_object($result))
{
if ($currentName != $row->name)
{
echo "<b>" . $row->name . "</b><br />";
$currentName = $row->name;
}
echo $row->spell_name . "<br />";
}

Parsing one col of multiple rows php

I have e.g. the following data returned from a query, each name is an item:
id name comment
1 FF hey
1 FF hey back!
2 LL
3 PP i think its great!
3 PP me too
3 PP I'm not sure
4 TT
5 II
6 KK yesterday is the new tomorrow
When I display it, each 'item' has an id and are displayed in DIVs use LI.
As you can see though there are multiple comments sometimes for an 'item', each on a separate line
What I want to do is display each item and then show comments under each item if there are any. So, i can't group by anything at query stage as the comment section is unique, but need to group at display stage
So currently have:
while ($row = mysql_fetch_array($result)){
echo '<li><div class=className><div class=itemName>'.$row[name].'</div>';
if($row[comment]){
echo '<div class=newRow>'.$row[comment].'</div>';
}
echo '</div></li>';
}
Now, this is no good because this will produce multiple displays for the same item with one comment under each.
Can I do this or should I bring in the data differently?
The ideal result is e.g.
FF LL PP etc etc etc
hey i think its great!
hey back! me too
I'm not sure
You can use GROUP_CONCAT() on your mysql query to group all the comments together for each name
SELECT id, name
GROUP_CONCAT(comment) AS comment
FROM table
GROUP BY name;
then explode() the $row[comment] in your php code
while ($row = mysql_fetch_array($result)){
echo '<li><div class=className><div class=itemName>'.$row['name'].'</div>';
if($row['comment'] != ""){
$comments = explode(",",$row['comment']);
foreach($comments as $comment){
echo '<div class=newRow>'.$comment.'</div>';
}
}
echo '</div></li>';
}
Edit
Thanks to #CBroe, I now know that GROUP_CONCAT() has a group_concat_max_len default of 1024. You will want to increase this before running the GROUP_CONCAT() query -
SET [GLOBAL | SESSION] group_concat_max_len = 10240; // must be in multiples of 1024
SELECT id, name
GROUP_CONCAT(comment) AS comment
FROM table
GROUP BY name;
you will also need to be aware of max_allowed_packet as this is the limit you can set var_group_concat_max_len to.
note: mysql_query() does not allow multiple queries, so you will need to do 2 mysql_query(), and you can use SET SESSION ... so that all queries in your current session have that max_len. It would be better to change from mysql_ functions (which are depreciated) and change to mysqli_ or PDO as they offer multiple query option. also check out - http://php.net/manual/en/mysqlinfo.api.choosing.php
Don't confuse data access with output and you will have an easier time attacking this sort of problem.
//Fetch and Sort
$data = array();
while ($row = mysql_fetch_array($result)){
$item = $row['item'];
if(!isset($data[$item]) {
$data[$item] = array():
}
$data[ = $data[$item][] = $row['comment'];
}
//Output
foreach($data as $item => $comments) {
echo '<li><div class=className><div class=itemName>'.$item.'</div>';
foreach($comments as $comment) {
echo '<div class=newRow>'.$comment.'</div>';
}
echo '</div></li>';
}
If you are not getting the data back sorted by id already, add to the end of your query:
ORDER BY name
which will retrieve the same list ordered by id. Then in your while loop, add a variable that keeps track of the last item name that you saw. If it changes, add a new li, otherwise, add the comment on to the end of your current list.

Database data in PHP array

I have a table in phpmyadmin that stores an 'id' (auto inc), 'title' and a 'date'.
I have a webpage where I display the 10 latest items (I simply order by ID).
On that page I print the title and the date. My wish is to also display the number of the posted item, so the first posted item is 1, the second is 2, etc. I cannot simply print the ID from the database because if I delete a row, the numbers aren't straight anymore.
My idea was to put all the data in an array but I have no clue what the best way to do this is and how I could print that item number. So for example when I want to display the 54th item I can easily print $items[54][id] or something and it will show me the number 54 and to display the title I print $items[54][title].
I don't know if there are simpler methods, plus arrays always start at 0, but my items must start at 1.
Besides this page that shows the 10 latest items, there is another page where it gets the title of the item out of the URL. How will I be able to search the title in the array and display the data the same way but only for that requested title?
Thanks in advance!
"SELECT COUNT(id) as cnt FROM mytable";
you can select the count of all database entries.
and then assign it to your iterator
$i = $row['cnt']; // this will hold the ammount of records e.g. 21
// other query
while($row = mysql_fetch_assoc($result)) {
echo $i;
$i--; // this will decrement on every iteration 21, 20 , 19, and so on.
}
First off. I would add a timestamp field to the database and order by that instead as it feels overall more reliable and gives you additional details which may prove handy later.
To create the multidimensional array I would do something like:
$result = mysql_query(...);
$items = array();
while($item = mysql_fetch_assoc($result)) {
$items[] = $item;
}
Now $items[12] for example would give you item number 13 (since it's 0-indexed).
Lastly, to select only the item with a specific title I would use a query which included a WHERE clause, like
"SELECT ... FROM ... WHERE title = '".$title."'"
It's very important to sanitize this variable before using it in the query though.
You can read more about MySQL on a lot of places. A quick googling gave me this: http://www.tutorialspoint.com/mysql/index.htm
You should learn PHP before starting to program in PHP ;) Read and work through the PHP manual and some tutorials!
As to your question it is a simple loop you want to do. One way of doing it as an example.
Fetch the 10 last items from the database in any way you like, following some code, partly pseudo-code.
$markup = '';
for ($i=1; $i<=count($items); $i++)
{
$markup .= 'Item ' . $i . ': ' . $items['date'] . ' - ' . $items['title'];
$markup .= 'read more';
$markup .= PHP_EOL;
}
echo $markup;
I don't know how you print out your data exactly, but I assume there is a loop in there. Simply set a counter that increments by one at every row and print its value.
As for the title search, you'll have to run another query with a WHERE title = '$title' condition, but beware of SQL injection.

Can this PHP code be simplified to improve performance?

The goal of this code, is to get all brands for all stores into one array, and output this to the screen. If a brand exists in multiple stores, it will only be added once.
But I feel I have too many for loops, and that it might choke the CPU on heavy traffic.
Is there a better solution to this?
function getBrands($stores, $bl)
{
$html = "";
//Loop through all the stores and get the brands
foreach ($stores as $store)
{
//Get all associated brands for store
$result = $bl->getBrandsByStore($store['id']);
//Add all brands to array $brands[]
while ($row = mysql_fetch_array($result))
{
//If this is the first run, we do not need to check if it already exists in array
if(sizeof($brands) == 0)
{
$brands[] = array("id" => $row['id'], "name" => $row['name']);
}
else
{
// Check tosee if brand has already been added.
if(!isValueInArray($brands, $row['id']))
$brands[] = array("id" => $row['id'], "name" => $row['name']);
}
}
}
//Create the HTML output
foreach($brands as $brand)
{
$url = get_bloginfo('url').'/search?brandID='.$brand['id'].'&brand='.urlSanitize($brand['name']);
$html.= ''.$brand['name'].', ';
}
return $html;
}
//Check to see if an ID already exists in the array
function isValueInArray($values, $val2)
{
foreach($values as $val1)
{
if($val1['id'] == $val2)
return true;
}
return false;
}
From your comment, you mention "Guide table has X stores and each store has Y brands". Presumably there's a "stores" table, a "brands" table, and a "linkage" table, that pairs store_id to brand_id, in a one-store-to-many-brands relationship, right?
If so, a single SQL query could do your task:
SELECT b.`id`, b.`name`
FROM `stores` s
LEFT JOIN `linkage` l
ON l.`store`=s.`id`
LEFT JOIN `brands` b
ON b.`id`=l.`brand`
GROUP BY b.`id`;
That final GROUP BY clause will only show each brand once. If you remove it, you could add in the store ID and output the full list of store-to-brand associations.
No need to loop through two sets of arrays (one to build up the array of brands, and then one to make the HTML). Especially since your helper function does a loop through -- use the array_key_exists function and use the ID as a key. Plus you can use the implode function to join the links with ', ' so you don't have to do it manually (in your existing code you'd have a comma on the end you'd have to trim off). You can do this without two sets of for loops:
function getBrands($stores, $bl)
{
$brands = array();
//Loop through all the stores and get the brands
foreach ($stores as $store)
{
//Get all associated brands for store
$result = $bl->getBrandsByStore($store['id']);
//Add all brands to array $brands[]
while ($row = mysql_fetch_array($result))
{
if (!array_key_exists($row['id'])
{
$url = get_bloginfo('url') . '/searchbrandID=' .
$brand['id'] . '&brand=' . urlSanitize($brand['name']);
$brands[$row['id']] .= '<a href="' . $url . '" id="' .
$brand['id'] . '" target="_self">' .
$brand['name'] . '</a>';
}
}
}
return implode(', ', $html);
}
That will get you the same effect a little faster. It's going to be faster because you used to loop through to get the brands, and then loop through and build up the HTML. Don't need to do that as two separate loops so it all at once and just store the HTML as you go along. Plus since it's switched to use array_key_exists, instead of the helper you wrote that checks by looping through yet again to see if a brand is in there, you'll see more speed improvements. Hashmaps are nice like that because each element in the hashmap has a key and there are native functions to see if a key exists.
You could further optimize things by writing a better SQL statement with a distinct filter to make it so you don't have to do a while inside a foreach.
How are your tables designed? If you had a store table, a brand table, and a link table that had the relationship between stores and brands, you could just pull in the list of brands from the brand table in one query and not have to do any other logic.
Design your tables so they easily answer the questions you need to ask.
If you need to get all the brands for a certain set of stores then you should consider using a query crafted to do that instead of iterating through all the stores and getting the separate pieces of information.

Categories