Retrieve data from multiple tables - php

I have 3 tables, want to write the select result but I don't know how to use 'join' 'inner' 'outer', etc, would be grateful if someone help me.
Tables: manufacturers / products / products_description
$manufacturer_query = "SELECT manufacturers_id from manufacturers WHERE manufacturers_name='" . $m ."'";
$manufacturer = mysql_query($manufacturer_query);
$mresult = mysql_fetch_array($manufacturer);
$products_query = "SELECT p.products_id, pd.products_description FROM products p, products_description pd WHERE p.products_id=pd.products_id AND p.manufacturers_id=" . $mresult['manufacturers_id'];
$products = mysql_query($products_query);
$presult = mysql_fetch_array($products);
$c = 0;
while ($presult){
$c++;
echo $c . ' - ' . $prod['p.products_id'] . ' - '.$prod['pd.products_description'].' - ' . $mresult['manufacturers_id'] . '<br>';
}

For your second query ($products_query), you already have a join.
The comma between the two table names (products and products_description) is a join operator.
This could be rewritten, replacing the comma join operator with the the JOIN keyword, and relocating the join predicate into an ON clause e.g.
SELECT p.products_id
, pd.products_description
FROM products p
JOIN products_description pd
ON pd.products_id=p.products_id
AND p.manufacturers_id = 1
This is an "inner" join in that rows for p.manufacturers_id = 1 will be returned ONLY if (at least one) row exists in the products table that matches that, AND if there is also at least one row in the products_description table that has a value in products_id that matches a row (or rows) returned from the products table.
Adding the keyword INNER before the JOIN keyword does not change the statement, it has no effect.
Given these table contents:
TABLE: products
manufacturers_id products_id
1 222
1 333
1 444
TABLE: products_description
products_id products_description
222 foo
444 fee
444 fi
444 fo
444 fum
That query would return:
p.products_id pd.products_description
222 foo
444 fee
444 fi
444 fo
444 fum
If you add the LEFT keyword before the JOIN keyword, then the query would be an OUTER join, and the query would also return a row like:
p.products_id pd.products_description
333 (NULL)
where a row from the table on the left side of the JOIN has no matching row found in the table on the right side.
If you want the rows returned in a particular sequence, then include an ORDER BY clause at the end of your query, e.g:
ORDER BY p.products_id, pd.product_description
Absent that, MySQL is free to return the rows in an arbitrary order.
The keywords INNER and OUTER are entirely optional. They have no effect on how the statement is processed, and this is why most SQL developers omit these keywords.
A LEFT JOIN is an OUTER join. Rows will be returned from the table on the left even when no matching row is found in the table on the right.
(For a cross join, we do usually include the CROSS keyword, even though it has no effect, just because it serves as documentation that the omission of an ON clause is intentional.)
The call to mysql_fetch_array should be in the loop:
$products = mysql_query($products_query);
$c = 0;
while ($prod = mysql_fetch_array($products) ) {
$c++;
echo $c . ' - ' . $prod['products_id'] . ' - '.$prod['products_description'].' - ' . $mresult['manufacturers_id'] . '<br>';
}
The mysql_ interface is DEPRECATED. New development should use either the mysqli_ or PDO interface.

try this with INNER JOIN
$manufacturer_query = "SELECT m.manufacturers_id , p.products_id, pd.products_description FROM manufacturers m
INNER JOIN products p ON p.manufacturers_id= m.manufacturers_id
INNER JOIN products_description pd ON p.products_id=pd.products_id
WHERE manufacturers_name='" . $m ."'";
$products = mysql_query($manufacturer_query);
$presult = mysql_fetch_array($products);
$c = 0;
while ($presult){
$c++;
echo $c . ' - ' . $prod['p.products_id'] . ' - '.$prod['pd.products_description'].' - ' . $mresult['manufacturers_id'] . '<br>';
}
EDIT
$manufacturer_query = "SELECT m.manufacturers_id mm, p.products_id pp, pd.products_description ppd FROM manufacturers m INNER JOIN
products p ON p.manufacturers_id= m.manufacturers_id INNER JOIN
products_description pd ON p.products_id=pd.products_id WHERE manufacturers_name='" . $m ."'";
$products = mysql_query($manufacturer_query);
$c = 0;
while ($presult = mysql_fetch_array($products)){
$c++;
echo $c . ' - ' . $presult['pp'] . ' - '.$presult['ppd'].' - ' . $presult['mm'] . '<br>';
}

Related

Trying to join 3 mysql tables but I do not get the expected result. What is mistake?

The below mysql query
SELECT *
FROM alerts_list l, alerts_data d, alerts_push_data p
WHERE p.push_data_hash = d.alerts_data_hash
AND p.push_data_alert_id = l.alerts_id
AND d.alerts_data_id = l.alerts_id
AND d.alerts_data_hash = 'JiaYRSVNZxgE'
shows the results of JiaYRSVNZxgE by joining three tables.
Here are the tables that I use and the columns that I want to connect between them:
table alerts_list
column: alerts_id
table alerts_push_data
column: push_data_alert_id
column: push_data_hash
table alerts_data
column: alerts_data_id
column: alerts_data_hash
What I want to achieve is:
connect push_data_alert_id with alerts_id
connect alerts_data_id with alerts_id
but show only the results where alerts_data_hash and push_data_hash is "abcdef"
Unfortunately my query results to no results found, but there are results in reality.
What I am doing wrong?
You could use MySQL JOINS to perform that operation quite easily like so:
<?php
// USING NORMAL JOIN:
// WE NOW ADD AN EXTRA LAYER (THE VARIABLE $fldVal)
// IN THE CASE THAT YOUR VALUE ('JiaYRSVNZxgE') IS DYNAMIC...
$fldVal = 'JiaYRSVNZxgE';
$sql = "SELECT DISTINCT *
FROM alerts_list l
JOIN alerts_data d ON d.alerts_data_id=l.alerts_id
JOIN alerts_push_data p ON p.push_data_alert_id=l.alerts_id
WHERE d.alerts_data_hash='" . $fldVal . "'";
// USING LEFT JOIN:
// WE NOW ADD AN EXTRA LAYER (THE VARIABLE $fldVal)
// IN THE CASE THAT YOUR VALUE ('JiaYRSVNZxgE') IS DYNAMIC...
$fldVal = 'JiaYRSVNZxgE';
$sql = "SELECT DISTINCT *
FROM alerts_list l
LEFT JOIN alerts_data d ON d.alerts_data_id=l.alerts_id
LEFT JOIN alerts_push_data p ON p.push_data_alert_id=l.alerts_id
WHERE d.alerts_data_hash='" . $fldVal . "'";
// USING INNER JOIN:
// WE NOW ADD AN EXTRA LAYER (THE VARIABLE $fldVal)
// IN THE CASE THAT YOUR VALUE ('JiaYRSVNZxgE') IS DYNAMIC...
$fldVal = 'JiaYRSVNZxgE';
$sql = "SELECT DISTINCT *
FROM alerts_list l
INNER JOIN alerts_data d ON d.alerts_data_id=l.alerts_id
INNER JOIN alerts_push_data p ON p.push_data_alert_id=l.alerts_id
WHERE d.alerts_data_hash='" . $fldVal . "'";
::AND YET A NEW UPDATE WITH GROUP BY CLAUSE::
<?php
// WE NOW ADD AN EXTRA LAYER (THE VARIABLE $fldVal)
// IN THE CASE THAT YOUR VALUE ('JiaYRSVNZxgE') IS DYNAMIC...
$fldVal = 'JiaYRSVNZxgE';
$sql2 = "SELECT DISTINCT *
FROM alerts_list AS A_LIST
LEFT JOIN alerts_push_data A_PUSH ON A_PUSH.push_data_alert_id=A_LIST.alerts_id
LEFT JOIN alerts_data A_DATA ON A_DATA.alerts_data_hash=A_PUSH.push_data_hash
WHERE A_DATA.alerts_data_hash='" . $fldVal . "'
GROUP BY A_LIST.alerts_id";
TEST-CASE QUERY
SELECT DISTINCT *
FROM alerts_list AS A_LIST
LEFT JOIN alerts_push_data A_PUSH ON A_PUSH.push_data_alert_id=A_LIST.alerts_id
LEFT JOIN alerts_data A_DATA ON A_DATA.alerts_data_hash=A_PUSH.push_data_hash
WHERE A_DATA.alerts_data_hash='iSg2loGJDaWs'
GROUP BY A_LIST.alerts_id
RESULT
And these are to be expected because I simulated only 2 Rows in all the other Tables except the alerts_list which has 10 Rows.
Result of dumping the Query Above
Table: alerts_list
Table: alerts_data
Table: alerts_push_data

Average and comparative left join MySQL

I'm trying to fetch information from 2 different MySQL tables.
The primary table is this one:
From this one I use the information to get the average rate from this:
How can I write an SQL query that will get me the average rating by counting all the rows with rating_house = house_id, and sort it by highest rating and if equal ratings, the one with the most rates.
This is what I have come up with myself:
$sql = "SELECT l.location_address, "
. "r.rating_structure+r.rating_inventory+r.rating_service/3 AS average "
. "FROM houses h "
. "LEFT JOIN rating r ON h.house_id = r.rating_house "
. "LEFT JOIN location l ON h.house_address = l.location_id "
. "WHERE h.house_deleted IS NULL SORT BY average DESC LIMIT 10";
$result = $db_connect->prepare($sql);
if($result->execute()){
while($user_data = $result->fetch(PDO::FETCH_ASSOC)){
$user_data['location_address']."<br>";
}
}
However I get no output?
For performance use INNER JOINs instead of LEFT JOINs if you know that the related rows must exist.
SELECT l.location_address,
(SUM(r.rating_structure)+SUM(r.rating_inventory)+SUM(r.rating_service))/3 AS average
FROM houses h
INNER JOIN rating r ON h.house_id = r.rating_house
INNER JOIN location l ON h.house_address = l.location_id
WHERE h.house_deleted IS NULL
GROUP BY l.location_address
ORDER BY 2,
(SUM(r.rating_structure)+SUM(r.rating_inventory)+SUM(r.rating_service)) DESC
LIMIT 10
Below query should return houses sorted by average ratings:
select h.house_id, (sum(hr.rating_structure) + sum(rating_inventory) + sum(rating_service))/3 as "average"
from house h left outer join house_rating hr on h.house_id = hr.rating_house
group by h.house_id
order by average

php sql query returns multiple rows for each individual record, misuse of joins?

I am trying to have my query return each record in my database once, the problem is if the listing has multiple images stored in a seperate table it returns multiple records (one for each image)
here is a snippet of my database layout (the data wrapped in ** are the primary keys)
tbl_listings => **listingID**,title,description,dateListed
tbl_images => **imageID**,filename
tbl_listing_image => **listingID**,**imageID**
this is my code
$stmt = $db->stmt_init();
if($stmt->prepare("SELECT l.listingID, l.title, l.description, l.dateListed, c.category, tn.townID, tn.town, i.filename
FROM tbl_listings AS l
LEFT JOIN tbl_listing_category AS lc ON lc.listingID = l.listingID
LEFT JOIN tbl_category AS c ON c.categoryID = lc.categoryID
LEFT JOIN tbl_listing_type AS lt ON lt.listingID = l.listingID
LEFT JOIN tbl_type AS t ON t.typeID = lt.typeID
LEFT JOIN tbl_listing_town AS ltn ON ltn.listingID = l.listingID
LEFT JOIN tbl_towns AS tn ON tn.townID = ltn.townID
LEFT JOIN tbl_listing_image AS li ON li.listingID = l.listingID
LEFT JOIN tbl_images AS i ON i.imageID = li.imageID
WHERE t.typeID =?"))
{
$type = 1;
$stmt->bind_param("i",$type);
$stmt->execute();
$stmt->bind_result($id,$title,$desc,$date,$cat,$townID,$town,$image);
echo "<ul>";
while($stmt->fetch())
{
$img = "images/listing/$id/$image";
echo "<li><img src='$img' alt='$title' title='$title'>#".$id."<h4>" . $title . "</h4>" . $desc . "<br /><strong> Category:</strong> " . $cat . " - <strong>Location: </strong> - <strong> Posted On:</strong> " . date("i M Y",$date) . "</li><hr>";
}
echo "</ul>";
$stmt->close();
which returns the following results (bearing in mind there is only 1 record in the tbl_listings table)
listingID title description dateListed category townID town filename
1 listing 1 listing desc 1411240751 teaching 11 town a image1.jpg
1 listing 1 listing desc 1411240751 teaching 11 town a image2.jpg
1 listing 1 listing desc 1411240751 teaching 11 town a image3.jpg
so the query is returning 3 records (one for each filename) even though its the same listing
so i tried adding a GROUP BY l.listingID to my query which returned just one record but it also only returned the first image like so
listingID title description dateListed category townID town filename
1 listing 1 listing desc 1411240751 teaching 11 town a image1.jpg
so my question is for each listing how can i return only 1 record but all of its associated images? do i need to run a seperate query within the while loop to get the images?
Would appreciate any help
Cheers
You can use GROUP_CONCAT to list the filenames as a single field:
SELECT l.listingID, l.title, l.description, l.dateListed, c.category, tn.townID, tn.town,
GROUP_CONCAT(i.filename) as filenames
FROM tbl_listings AS l
LEFT JOIN tbl_listing_category AS lc ON lc.listingID = l.listingID
LEFT JOIN tbl_category AS c ON c.categoryID = lc.categoryID
LEFT JOIN tbl_listing_type AS lt ON lt.listingID = l.listingID
LEFT JOIN tbl_type AS t ON t.typeID = lt.typeID
LEFT JOIN tbl_listing_town AS ltn ON ltn.listingID = l.listingID
LEFT JOIN tbl_towns AS tn ON tn.townID = ltn.townID
LEFT JOIN tbl_listing_image AS li ON li.listingID = l.listingID
LEFT JOIN tbl_images AS i ON i.imageID = li.imageID
WHERE t.typeID =?
GROUP BY l.listingID
The advantage is that you have a single query with a single roundtrip time, so it's quite efficient. You do need to split those filenames again, of course, but that is trivial in PHP.
On the other hand, there is a maximum length GROUP_CONCAT can return, and if a category can have a lot of files, you might reach that limit.
Also, if any of the other tables also cause duplication, you might get each file name multiple times. This can be solved using GROUP_CONCAT(DISTINCT filename), so you're still safe, but if you want not only the file names, but also other properties of the file (type, owner?) you are stuck. In that case there is no shame in running a separate query for the filenames or other details that occur multiple times.

Combine the results of two SQL queries

How can I combine the results of these two sql queries either in SQL or PHP .. they're all involving joins .. I would like to combine them both and sort them by orderid .. how can I do that ?
first query
$sqlstr = "SELECT op.* FROM products_to_products_extra_fields AS p
INNER JOIN orders_roster AS r ON p.products_id = r.products_id
INNER JOIN orders_products AS op ON r.orders_id = op.orders_id
INNER JOIN orders AS o on op.orders_id = o.orders_id
WHERE p.products_extra_fields_id = 14
AND p.products_extra_fields_value between '"
. tep_db_input($startdate) . "' and '"
. tep_db_input($enddate) . " 23:59:59'
AND r.roster_status != 'Removed'
AND o.payment_method = 'Institutional Billing'
AND o.orders_status < 100001
GROUP BY o.orders_id
ORDER BY DECODE(o.cc_type, '$salt') ASC";
SECOND query
$sqlstr2 = "SELECT op.* FROM products_to_products_extra_fields AS p
INNER JOIN orders_products AS op ON p.products_id = op.products_id
INNER JOIN orders AS o on op.orders_id = o.orders_id
WHERE p.products_id IN
(SELECT products_id
FROM products_to_products_extra_fields
WHERE p.products_id NOT IN
(SELECT products_id
FROM products_to_products_extra_fields
WHERE products_extra_fields_id = 14)
)
AND o.date_purchased between '"
. tep_db_input($startdate) . "' and '"
. tep_db_input($enddate) . " 23:59:59'
AND o.payment_method = 'Institutional Billing'
AND o.orders_status < 100001
GROUP BY o.orders_id
ORDER BY DECODE(o.cc_type, '$salt') ASC";
If you need them combined on the PHP end, I am going to assume you are left with an array of arrays (MySQL Rows), which you could simply loop through both sets of results and use array_push to push them into 3rd(complete) array. You could also toy around with array_merge but sometimes with multidimensional arrays the end result isnt what you expected.
http://us3.php.net/manual/en/function.array-push.php
http://us3.php.net/function.array-merge
Just make a UNION query and get the results merged on the SQL side. No PHP needed.

Complex Sort Function using Join, SQL, Order Categories by Products' expiry, Opencart

UPDATE
My end goal was to sort categories by the expiry dates of the products within. So the category with a product closest to expiring goes first, categories with all products expired go last. Thanks to Greg, see the complete answer at the bottom of the question.
/UPDATE
This works fine except for the second line (SELECT MIN(date_expires... Any ideas on how to rewrite this line?
SELECT c.category_id,
name,
c.image,
description,
(SELECT MIN(date_expires)
FROM product
WHERE date_expires >= NOW() AND
p.product_id = p2c.product_id) AS expiring
FROM category c
LEFT JOIN category_description cd
ON (c.category_id = cd.category_id)
LEFT JOIN product_to_category p2c
ON (c.category_id = p2c.category_id)
LEFT JOIN product p
ON (p2c.product_id = p.product_id)
WHERE c.parent_id = 0 AND c.status = '1'
GROUP BY c.category_id
ORDER BY expiring
Some sample data:
category:
category_id = 62
category_id = 64
category_id = 63
product_to_category:
product_id = 43 category_id = 62
product_id = 50 category_id = 63
product_id = 56 category_id = 63
product_id = 58 category_id = 62
product_id = 59 category_id = 63
product_id = 60 category_id = 63
product_id = 61 category_id = 63
product_id = 62 category_id = 63
product_id = 63 category_id = 64
product:
product_id = 43 date_expires = 2012-07-11 20:35:00
product_id = 50 date_expires = 2012-06-29 00:00:00
product_id = 56 date_expires = 2012-07-13 00:00:00
product_id = 58 date_expires = 2012-07-26 15:01:00
product_id = 59 date_expires = 2012-07-16 14:12:00
product_id = 60 date_expires = 2012-07-18 08:15:00
product_id = 61 date_expires = 2012-08-02 13:04:00
product_id = 62 date_expires = 2012-08-24 00:00:00
product_id = 63 date_expires = 2012-07-12 19:16:00
ANSWER
My end goal was to get
SELECT c.category_id, minDateExpires.Expiring,
CASE WHEN Expiring > 1 THEN 1 ELSE 2 END as available
FROM category c
LEFT JOIN product_to_category p2c ON (c.category_id = p2c.category_id)
LEFT JOIN product p ON (p2c.product_id = p.product_id)
LEFT JOIN (
SELECT MIN(date_expires) AS expiring, category_id FROM product p join product_to_category p2c on p2c.product_id = p.product_id WHERE date_expires >= GETDATE() GROUP BY category_id
) minDateExpires on p2c.category_id = minDateExpires.category_id
ORDER BY available, expiring
If you are looking for this for OpenCart in particular and you are using the Categories module then go to catalog/model/catalog/category.php and edit function getCategoriesPag() to:
public function getCategoriesPag($data) {
$query = $this->db->query("SELECT c.category_id, name, c.image, description, minDateExpires.Expiring, CASE WHEN Expiring > 1 THEN 1 ELSE 2 END as available FROM " . DB_PREFIX . "category c LEFT JOIN " . DB_PREFIX . "category_description cd ON (c.category_id = cd.category_id) LEFT JOIN " . DB_PREFIX . "category_to_store c2s ON (c.category_id = c2s.category_id) LEFT JOIN " . DB_PREFIX . "product_to_category p2c ON (c.category_id = p2c.category_id) LEFT JOIN " . DB_PREFIX . "product p ON (p2c.product_id = p.product_id) LEFT JOIN (SELECT MIN(date_expires) AS expiring, category_id FROM product p JOIN product_to_category p2c on p2c.product_id = p.product_id WHERE date_expires >= NOW() GROUP BY category_id) minDateExpires on p2c.category_id = minDateExpires.category_id WHERE c.parent_id = 0 AND cd.language_id = '" . (int)$this->config->get('config_language_id') . "' AND c2s.store_id = '" . (int)$this->config->get('config_store_id') . "' AND c.status = '1' GROUP BY c.category_id ORDER BY available, Expiring, c.sort_order");
return $query->rows;
}
You will need to add your own date_expires field to the database and to your back end admin panel.
I don't believe this statement is true:
The original problem lies in the fact that the sub query cannot see the tables you joined to in the main query. So p.product_id and p2c.product_id do not exist in the sub query.
Since that condition is part of an outer join in the main query, that condition is already known to be true or will evaluate to null and thereby cause a null result. Now your sample data suggests you don't need outer joins anyway and I can see that there are expiration dates into the future on many of the rows.
I believe you meant to write this as your subquery. It might be worth a try even though some of the other info we've got suggests it still won't quite be the final solution.
(
SELECT MIN(date_expires)
FROM product as p2
WHERE date_expires >= NOW() AND
p2.product_id = p.product_id
) AS expiring
EDIT
Make the change I just suggested, but also get rid of the GROUP BY clause.
EDIT 2
1) You seem to have attributes in multiple tables that might belong in a single product table. I'll assume that you have good reasons for that.
2) Mysql allows you to mix aggregate and non-aggregate columns, but that is a bad practice and if my theory proves true, that was a big part of the reason we were all stumped for a little while.
3) All of the left outer joins seem be doing what you'd normally use an inner join for. You won't regret doing this right the first time.
4) The expiration date can be looked up via a subquery the way you first wrote it or as another join as done by the other answer. If there is really a one-to-one relationship there, then MIN() and GROUP BY are not necessary and may confuse somebody. (In fact, see edit #3.)
5) Since your subquery evaluates as null for any product with an expiration falling in the past, you may need to ORDER BY a second column or just use the expiration date regardless of whether it's past or future.
EDIT 3
SELECT
c.category_id,
MIN(name) as name,
c.image /* better as a subquery if you can't do an aggregate on a LOB */
MIN(c.description) as description,
/* don't even need the subquery because you've already joined the product table */
CASE
WHEN MIN(p.date_expires) >= NOW() THEN MIN(p.date_expires)
ELSE NULL /* this may not be what you really wanted */
END as expiring_my_first_guess,
/* what was really intended */
MIN( CASE WHEN p.date_expires >= NOW() THEN p.date_expires ELSE NULL END ) as expiring,
FROM category c
INNER JOIN category_description cd
ON (c.category_id = cd.category_id)
INNER JOIN product_to_category p2c
ON (c.category_id = p2c.category_id)
INNER JOIN JOIN product p
ON (p2c.product_id = p.product_id)
WHERE c.parent_id = 0 AND c.status = '1'
GROUP BY c.category_id
ORDER BY expiring
UPDATE TO ANSWER NOW THAT DATA SAMPLES ARE AVAILABLE
Still assuming it's Microsoft SQL Server, this query produces the following result
SELECT c.category_id, minDateExpires.Expiring
FROM category c
LEFT JOIN product_to_category p2c ON (c.category_id = p2c.category_id)
LEFT JOIN product p ON (p2c.product_id = p.product_id)
LEFT JOIN (
SELECT MIN(date_expires) AS expiring, Product_ID FROM product WHERE date_expires >= GETDATE() GROUP BY Product_ID
) minDateExpires on p.product_id = minDateExpires.product_id
ORDER BY expiring
Results:
category_id Expiring
63 NULL
63 NULL
62 NULL
64 NULL
63 2012-07-16 14:12:00.000
63 2012-07-18 08:15:00.000
62 2012-07-26 15:01:00.000
63 2012-08-02 13:04:00.000
63 2012-08-24 00:00:00.000
The null columns are there because there is no min date for those product id's >= the current date/time. Are you actually after the minimum date time grouped by categoryid, not product id? In which case your query could be something like:
SELECT c.category_id, minDateExpires.Expiring
FROM #category c
LEFT JOIN #product_to_category p2c ON (c.category_id = p2c.category_id)
LEFT JOIN #product p ON (p2c.product_id = p.product_id)
LEFT JOIN (
SELECT MIN(date_expires) AS expiring, Category_ID FROM #product p join #product_to_category p2c on p2c.product_id = p.product_id WHERE date_expires >= GETDATE() GROUP BY Category_id
) minDateExpires on p2c.category_id = minDateExpires.category_id
ORDER BY expiring
which gives you
64 NULL
63 2012-07-16 14:12:00.000
63 2012-07-16 14:12:00.000
63 2012-07-16 14:12:00.000
63 2012-07-16 14:12:00.000
63 2012-07-16 14:12:00.000
63 2012-07-16 14:12:00.000
62 2012-07-26 15:01:00.000
62 2012-07-26 15:01:00.000

Categories