Inner Join with PHP - php

I checked through a few different questions previously asked but they were more advanced than what I need at the moment. I need a simple way to join two tables and display the results so that I can then manipulate them in any way I want once it is collecting the data the way I need it to. The code below is very simple... Yet I am having trouble. First I create a class that connects to the database then I created a method to query the database and join to tables based on common columns. After that I would like the loop to go through the top four results based on their title name which are 'gold', 'silver', 'platinum', 'palladium' I just want to make sure that the join request is working. Please view the code below and maybe you can tell me why the results I keep getting are
1 Gold
1 Gold
1 Gold
1 Gold
Literally I get Gold 4 times when I need a list of all 4 precious metals.I thought that when the while loop runs through I would get each one as it is supposed to run through all 4 rows and there are no more yet it runs through the same 1st row and brings back 1 Gold every time. Both the id and the metals title name. If I am missing something please feel free to ask and I will add it for you if it helps.
class testJoin{
public function __construct($dbCon){
$this->dbConnection = $dbCon;
}
function testingJoin($dbCon) {
if($results = $this->dbConnection->query("SELECT metal.id, metal.title, price.metalId FROM metal INNER JOIN price ON metal.id = price.metalId ORDER BY metal.title LIMIT 0,4")){
while($data = $results->fetch_assoc()){
printf("<p style=\"display:inline;\">%s</p>
<p style=\"display:inline;\">%s</p><br />", $data['id'], $data['title']);
}
}
$dbCon->close();
}
}

JOIN creates a cross-product of the matching rows in the two tables. If there are multiple price rows for each metal, you'll get all those different prices, and then you take the first 4 rows of this.
If you want to limit the number of metals, but not the total number of rows, you can join with a subquery:
SELECT metal.id, metal.title, price.metalId
FROM (SELECT id, title
FROM metal
ORDER BY title
LIMIT 4) AS metal
JOIN price ON metal.id = price.metalId
Or if you want to get just one row per metal, you can use GROUP BY
SELECT metal.id, metal.title, price.metalId
FROM metal
JOIN price ON metal.id = price.metalId
GROUP BY metal.id
ORDER BY metal.title
LIMIT 4

Here you have no reason to join to prices table at all
SELECT metal.id, metal.title
FROM metal
ORDER BY metal.title
Cos u added to result nothing from there.
If u really need join to prices and display results by "not repeated" metal names, u should just GROUP results
SELECT metal.id, metal.title
FROM metal
INNER JOIN price ON (metal.id = price.metalId)
GROUP BY metal.id
ORDER BY metal.title
After that you can retrieve some useful data from prices table, for example average price for each metal
SELECT metal.id, metal.title, AVG(price.price) AS metal_price
FROM metal
INNER JOIN price ON (metal.id = price.metalId)
GROUP BY metal.id
ORDER BY metal.title
Also you should understand difference between LEFT JOIN and INNER JOIN.
LEFT - will fetch ALL needed rows from first table (metal) and add results from second (prices) even if there is no such metal in prices table (then results from second table will be NULL). (metal.id = price.metalId) can be understanded as "ALL metals with some prices, if they have"
INNER - will fetch ONLY those rows from first table which are presented in second table, by "JOIN ON" condition. (metal.id = price.metalId) can be understanded as "THOSE metals WHICH HAVE prices"
https://pp.vk.me/c623725/v623725696/14ae4/459rNGJwMJc.jpg

Related

mysql - group results by id and print

I have "reservation" table (mySql) that contain number of columns: res_id, hotel_id, hotel_name, from_date, to_date.
I would like to select and print html table for each hotel (i'm using PHP). the result should be a title - the name of the hotel, and bellow it a list of reservation for the specific hotel.
I can do GROUP BY:
Select * FROM reservation GROUP BY hotel_id
I'm not sure if it's the right way to do it, and how do i print the results without checking all the time if the hotel_id was changed?
Thank you in advanced
GROUP BY is definitely NOT the right way to approach this. One method would be:
SELECT *
FROM reservation
ORDER BY hotel_id;
You would then loop through the result sets. When the hotel name changes, you would put in the title of the hotel.
Note: This is a poor data model if it has both the hotel id and name in reservation. This would normally be in hotel and you would connect the tables using JOIN:
SELECT h.hotel_name, r.*
FROM hotels h JOIN
reservation r
ON r.hotel_id = h.hotel_id
ORDER BY hotel_id;
Using a LEFT JOIN, you can even get hotels with no reservations.
How is it that the hotel_id would change? As per your question it seems that hotel_id is a column made for join with a "hotels" table, isn't it?
Regarding the "group by", why would you group by hotel? This would make you loose reservations data, unless you were using some sort of group_concat.
If you want to get the reservations from a specific hotel you could loop through your hotels table and inside your loop you can do:
SELECT * FROM reservations WHERE hotel_id='QUERIED_HOTEL_ID'
Then show the results.
Or you could simply
SELECT * FROM reservations
And when you get the fetched results you can make a multidimensional php array with 'hotel_id' as top level key and 'res_id' as secondary, like this:
$reservations_by_hotel = [];
do {
$resId = $row['res_id'];
$hotelId = $row['hotel_id'];
$reservations_by_hotel[$hotelId][$resId] = $row;
} while ($row = $result->fetch_assoc());

How to create new table for the output of DISTINCT COUNT to be distributed in rows, not in column?

My query displays the DISTINCT count of buyers with corresponding ticketserial#. I need to automatically calculate the SOLD and BALANCE column and save into the database either into the existing table (table1) with the rows that corresponds to the ticketserial. I've already exhausted my brain and did google many times but I just can't figure it out. So I tried another option by trying to create a new table into the database for the output of DISTINCT COUNT but I didn't find any sample query to follow, so that I could just use INNER JOIN for that new table with table1, with that the PRINTED, SOLD are in the same table, thus I can subtract these columns to obtain the values for the BALANCE column.
Existing table1 & table2 are records in the database via html form:
Table1
Ticket Serial Printed Copies SOLD(sold) Balance
TS#1234 50 ?(should be auto ?
TS#5678 80 ?(should be auto ?
(so on and so forth...)
Table2
Buyer Ticket Serial
Adam TS#1234
Kathy TS#1234
Sam TS#5678
(so on and so forth...)
The COUNT DISTINCT outputs the qty. of sold tickets:
<td> <?php print '<div align="center">'.$row['COUNT(ticketserial)'];?></td>
...
$query = "SELECT *, COUNT(ticketserial) FROM buyers WHERE ticketsold != 'blank' GROUP BY
ticketserial ";
It's COUNT output looks like this:
Ticket Serial------Distinct Count
TS#1234 7
TS#5678 25
(so on and so forth...)
I tried to update the SOLD column and BALANCE column by UPDATE or INSERT and foreach loop but only the first row in table was updated.
Table1
Ticket Serial Printed Copies Sold Balance
TS#1234 50 **7** 0
TS#5678 80 **0** 0
TS#8911 40 **0** 0
(so on and so forth...)
Note: The fieldname "sold" in table1 is not the same with the fieldname "ticketsold" in table2 as the former is quantity and the later is ticketserials.
Your question is a bit hard to follow. However this looks like a left join on a aggregate query:
select
t1.ticket_serial,
t1.printed_copies,
coalesce(t2.sold, 0) sold,
t1.printed_copies - coalesce(t2.sold, 0) balance
from table1 t1
left join (
select ticket_serial, count(*) sold
from table2
group by ticket_serial
) t2 on t2.ticket_serial = t1.ticket_serial
If you are looking to update the main table:
update table1 t1
left join (
select ticket_serial, count(*) sold
from table2
group by ticket_serial
) t2 on t2.ticket_serial = t1.ticket_serial
set
t1.sold = coalesce(t2.sold, 0),
t1.balance = t1.printed_copies - coalesce(t2.sold, 0)
I would not actually recommend storing the sold and balance in the main table - this is derived information that can be easily computed when needed, and would be tedious to maintain. If needed, you could create a view using the first above SQL statement, which will give you an always up-to-date perspective at your data.

Joining 2 tables is not working correctly

Idk what i am doing wrong.I am still new to php and mysqli and wanted to know what I am doing wrong.What I have on my 1st list are name of the food,description and what kind of food they are.On my 2nd list I have the ratings of the foods.
TableImg
So lets say for example I click on korean food all the foods with the cuisine_type = korean will be displayed on my page. When mysqli_fetch_array($query_type) it displays kimchi and ribs but when i replace $query_fetch it only displays the first one which is kimchi.My goal is to get all the food from cuisine_type display correctly and their ratings attached to them.
<?php
if(isset($_GET['type'])){
$type = (string)$_GET['type'];
}
(Displays only 1 of them)-> $query_fetch = mysqli_query($connect,"SELECT
cuisine.*,AVG(ratings.cuisine_rating) AS rt FROM cuisine
LEFT JOIN ratings ON cuisine.dish_name = ratings.cuisine_name WHERE
cuisine_type = '$type'");
(Displays everything)-> $query_type = mysqli_query($connect,"SELECT * FROM cuisine WHERE
cuisine_fetch ='$type'");
while($show = mysqli_fetch_array($query_type)){
echo "
<div class='box'>
<img class='foodimg' src='dish/".$show['dish_img'].".jpg'>
<figcaption class='title'>".$show['dish_name']."</figcaption>
<div class='text'>".$show['text']."</div>
</div>
</div>";
}
?>
What you want is to do your average using GROUPing.
SELECT c.*, avg(r.rating) AS rt FROM cuisine c
LEFT JOIN ratings r
ON c.dish_name = r.dish_name
WHERE c.cuisine_type = 'korean'
GROUP BY dish_name
The code can be tested via SQLFiddle.
You will need to replace where i hard coded korean with your php variable to make it work in your system.
The issue you had was you didn't specify how to group your rows, this caused the server to put them into all one group. Most DBMS would have errored when you tried to select columns not in the grouping (which was no columns) without an aggregate (like avg), though it appears yours does not, it allows you to group this way and simply return the first row for every non aggregate.
NOTE you still may need some kind of null handling for avg(r.rating), since Korean Ribs has no ratings, it will return an average rating of NULL. You can handle this by using IFNULL like this: IFNULL(avg(r.rating),2.5) AS rt. I would suggest using maybe 2.5 if rating is out of 5, so your no rating is the middle number, though you can use 0 or 5 or anything.
Assuming the TableImg is correct, you're not using the same names in the query as in the db.
SELECT
cuisine.*,
AVG(ratings.cuisine_rating) AS rt
FROM cuisine
LEFT JOIN ratings ON
cuisine.dish_name = ratings.cuisine_name
WHERE
cuisine_type = '$type'
Use this instead:
SELECT
cuisine.*,
AVG(ratings.Rating) AS rt
FROM cuisine
LEFT JOIN ratings ON
cuisine.dish_name = ratings.dish_name
WHERE
cuisine_type = '$type'

PHP Query within a while fetch loop

I was wondering if it was possible to run a query inside a while loop which is used to display the content of a SQL table.
Here is the code if I'm not clear enough :
$sql="SELECT * FROM hotels WHERE rooms>0";
$req=$db->query($sql);
while($row=$req->fetch()){
//The second query to check how many place is left
$req2=$db->query('SELECT COUNT(*) FROM people WHERE idhotels='.$row["idhotels"].';');
echo "hey".$req2;
$left_rooms= $row["rooms"] -$req2;
echo '<option value="'.$row["idhotels"].'">'.$row["name_hotel"].' ('.$left_rooms.' rooms left)</option>';
}
What I'm trying to do here, is to display a list of hotels with the number of rooms left. The problem is I have to count how many rooms are taken before displaying the number of rooms left, hence the second request.
My code obviously doesn't work, but I can't figure out why.
Can someone help me ?
Many thanks !
Why not using a join and a group by so you only have one query ?
$sql="SELECT h.idhotels,h.name_hotel,count(*) FROM hotels h inner join people p on h.idhotels = p.idhotels WHERE h.rooms>0 group by h.idhotels,h.name";
while($row=$req->fetch()){
// Here do whatever you want with each row
}
Have you tried to calculate your left rooms in the database with a joined query like:
SELECT rooms - COUNT(*) AS left_rooms FROM hotels h WHERE rooms > 0 JOIN people p ON (p.idhotels = h.idhotels) GROUP BY h.idhotels, h.name ORDER BY left_rooms ASC;

How do I echo out a name -only if- specific rows in one table match specific rows in another based on an id?

Here's my problem: I'm making a crafting system for a game, and I already have my database filled with information for resources required to craft items.
Here are what my relevant tables look like:
table #edible_resources
(edible_resource_id, edible_resource_name, hunger_points, degeneration_id)
table #edible_ground
(id, resource, amount, location)
table #req_crafting_edible
(req_crafting_edible_id, edible_resource_id, req_resource_amount, created_item_id)
table #items
(item_id, item_name, degeneration_id, is_grounded, is_stackable, can_equip, can_edit)
What I want to do is -only- echo out the craftable item's name if, and only if -all- required resources (and their required amounts) are on the ground in the location of the character.
I have a query that comes close:
SELECT items.item_name, items.item_id FROM items
INNER JOIN req_crafting_edible
ON req_crafting_edible.created_item_id = items.item_id
INNER JOIN edible_ground
ON edible_ground.resource = req_crafting_edible.edible_resource_id
AND edible_ground.amount >= req_crafting_edible.req_resource_amount
WHERE edible_ground.location = $current_location
GROUP BY items.item_name
ORDER BY items.item_name
But this shows me craftable items regardless if I have ALL the required items in the area. It shows me items as long as I have -one- of their required resources.
Is there a way to only show the name of a craftable item only if I have -all- the required resources (and their amounts) in edible_ground where location = $current_location?
For more information on what I've tried:
$get_char = mysql_query("SELECT current_char FROM accounts WHERE account_id ='".$_SESSION['user_id']."'");
$current_char = mysql_result($get_char, 0, 'current_char');
$get_loc = mysql_query("SELECT current_location FROM characters WHERE character_id = $current_char");
$current_location = mysql_result($get_loc, 0, 'current_location');
//---------------------------------------------------------------COOKED FOOD
$get_food = mysql_query("SELECT items.item_name, items.item_id FROM items
INNER JOIN req_crafting_edible
ON req_crafting_edible.created_item_id = items.item_id
INNER JOIN edible_ground
ON edible_ground.resource = req_crafting_edible.edible_resource_id
AND edible_ground.amount >= req_crafting_edible.req_resource_amount
WHERE edible_ground.location = $current_location
GROUP BY items.item_name
ORDER BY items.item_name");
while ($food = mysql_fetch_array($get_food)){
echo $food['item_name'].'<br>';
}
This returns:
Baked Fish
Charred Fish
Fish Soup
Glazed Berry
Cake
Grilled Fish
Sashimi
Seafood Soup
Sushi
Udon
On the ground:
1 fish
1 honey
Even though fish soup, berry cake, udon etc needs much more than just the one fish that's in the area.
Can anyone help me figure this out? I'd be forever grateful; I've spent a few days already trying to myself. Please?
And before anyone says anything, I know I need to start using mysqli; unfortunately I didn't even realize it existed when I started to make the game (and learn PHP at the same time months ago), so I'll have to painfully go back and change it all in an update.
You want a HAVING clause to check the count of the records you are grouping through the INNER JOINs.
HAVING count(*) = (
SELECT count(*)
FROM req_crafting_edible
WHERE req_crafting_edible.created_item_id = items.item_id
)
Edit:
So basically you need to know two pieces of information:
How many different resources are required
Do each of those resources have the required amounts
The first is solved by the sub query above.
Your query as-is satisfies the second point but only for 1 resource.
HAVING basically does some special magic on your group clause. HAVING count(*) means there are X records being grouped together. Because of how the join works, you will have 1 item.name for each resource. The sub select gives you the count of how many different resources, and therefore grouped records, are needed for that item. Comparing that sub query with the count(*) of the grouping ensures you have all the needed resources.
And here is the final query, modifying your code above:
SELECT items.item_name, items.item_id
FROM items
INNER JOIN req_crafting_edible
ON req_crafting_edible.created_item_id = items.item_id
INNER JOIN edible_ground
ON edible_ground.resource = req_crafting_edible.edible_resource_id
AND edible_ground.amount >= req_crafting_edible.req_resource_amount
WHERE edible_ground.location = $current_location
GROUP BY items.item_name
HAVING count(*) = (
SELECT count(*)
FROM req_crafting_edible
WHERE req_crafting_edible.created_item_id = items.item_id
)
ORDER BY items.item_name
You only actually want the data from the items table, right? If so I would move to using an exists model:
SELECT I.item_name, I.item_id FROM items I
WHERE NOT EXISTS
(SELECT created_item_id
FROM req_crafting_edible R
WHERE R.created_item_id = I.item_id
AND NOT EXISTS
(SELECT G.resource
FROM edible_ground G
WHERE G.resource = R.edible_resource_id
AND edible_ground.location = $current_location
AND G.amount >= R.req_resource_amount))
ORDER BY I.item_name
I don't have your database to check this, but the logic goes like this:
Find the items that don't have any unsatisfied requirements.
Find the unsatisfied requirements for the current item. (IE. Find
requirements that don't have resources on the ground)
Find the edible resources that match the current requirement, are at
this location, and have enough.
I don't work in mysql as much at the moment, but let me know if this doesn't work.

Categories