I have query like this,
SELECT * FROM users ORDER BY score
So, the result is like this.
Array
(
[0] => stdClass Object
(
[userid] => 3
[user] => John Doe
[score] => 50
)
[1] => stdClass Object
(
[userid] => 1
[user] => Mae Smith
[score] => 38
)
[2] => stdClass Object
(
[userid] => 2
[user] => Mark Sam
[score] => 26
)
)
But, I want to add a rank using find_in_set query. So the result might be like this. So that the user can view their ranks when they login to their account.
Array
(
[0] => stdClass Object
(
[userid] => 3
[user] => John Doe
[score] => 50
[rank] => 1
)
[1] => stdClass Object
(
[userid] => 1
[user] => Mae Smith
[score] => 38
[rank] => 2
)
[2] => stdClass Object
(
[userid] => 2
[user] => Mark Sam
[score] => 26
[rank] => 3
)
)
I tried this one.
$listOfUser = array();
foreach($users as $user) {
$listOfUser[] = $user->userid;
}
And used another query
$userid = 2 // => id of loggedin user
SELECT *, find_in_set($userid, $listOfUser) as rank FROM users where userid=$userid ORDER BY score
So, I got this result
Array
(
[1] => stdClass Object
(
[userid] => 2
[user] => Mark Sam
[score] => 26
[rank] => 3
)
)
Which is somehow correct. But, is there another way of querying that result using only one SQL query and without using foreach loop?
Something like this.
$userid = 2 // => id of loggedin user
SELECT *, find_in_set($userid, (SELECT * FROM users ORDER BY score)) as rank FROM users where userid=$userid ORDER BY score
But I got this error Subquery returns more than 1 row
If You don't insist on using find_in_set, you can get result with simple join. You ask for list of users (p) and for each user you ask, how many users have better score than him or her (c):
SELECT p.userid, COUNT(c.userid) AS rank
FROM users AS p
LEFT JOIN users AS c ON c.score > p.score
GROUP BY p.userid
This works even if you add other conditions, like WHERE p.userid = 123.
If more users have the same score, the ranks would look like 0,1,2,2,2,5,6.
In your query, you can add counter, like this:
set #n:=0;
SELECT #i := #i + 1 AS rank, * FROM users ORDER BY score
The rank here is relative to the score distribution across all users. I believe you should try something originally proposed in this answer:
SELECT users.*,
#rownum := #rownum + 1 as rank
FROM users
CROSS JOIN (select #rownum := 0) r
ORDER BY score DESC
What it does is basically order all users by score, and assign each of them an incremental value "rank". So the top scorer would have a rank of 1, the second scorer would have a rank of 2 etc.
Keep in mind that this solution is not "fair" - each user will have a different rank, even if all users have the same score. If you try to rank users as they do in sports (if two top competitors have the same score, they both take 1st place, and the next best competitor takes 3rd place, not second), you should think of a different solution.
Let's say I have these 3 tables:
Person table
id | name
1 | Sam
Dress table
id | person_id |name
1 | 1 |shorts
2 | 1 |tshirt
Interest table
id | person_id | interest
1 | 1 | football
2 | 1 | basketball
(Above is just a simplified example, in real I have a lot many tables to join)
I need to show all these details on a page, so combined all into 1 left join query mainly for performance. Now the result we get should be messy with repeated results for combinations of dresses and interests for a person. To fix this I will need to manually loop through to arrange into an array that I want to consume. My query looks something like this (am I doing it right?):
select p.id, d.name, i.interest
from person as p
left join dress as d on p.id = d.person_id
left join interest as i on p.id = i.person_id
where p.id = 1;
What is a better way to do this? I am aware that I can also use GROUP_CONCAT to avoid repetition.
UPDATED WITH OUTPUT
I want my final result to look like this (I know I need to loop through to get this format), what would be the best way to query my tables to achieve this?
[
[
'id' => 1,
'dresses' => [
[
'id' => 1,
'name' => 'shorts',
...more columns
],
[
'id' => 2,
'name' => 'tshirt',
..more columns
]
],
'interests' => [
'football',
'basketball'
]
]
]
Amount of data vs. flexibility:
Personally, for your task - let's suppose it's a bit more complex than it is presented, ok? - I wouldn't recommend you to use any sql functions (like group_concat, etc) at all. You may, of course, get a smaller quantity of data by using them. But you would certainly loose the flexibility you need to read and process the fetched results.
Think about running a query with (maybe a lot) more columns. Would you still want to "beautify" the query if some of them would suddenly require you to apply other sql functions or conditions - like another simple, but tricky GROUP BY clauses? What would happen then with your results reading algorithm? It would have to be (maybe hard)-rethought again.
Resources eaters:
Also, please keep in mind, that all these group_concat functions/selections are eating MySQL resources too.
Indexes and EXPLAIN for optimizations:
I'm just also thinking about a situation, in which you would want to apply indexes to some fields - for searching purposes, for example. And that you would want to check their validity/rapidity with an EXPLAIN command. I sincerely don't know if having group_concat's would make this an easy and transparent task.
Display purposes vs. post-processing?
In general, functions like group_concat are used for display purposes, for example in data grids/tables. But your task requires post-processing of the fetched data.
Already sorted:
That said, in your original question you already presented an sql solution. IMHO, your version is the proper and the flexible one. And your sql statement is already correct. You can maybe apply some ORDER BY conditions, in order to directly build a sorted array from the fetched data.
Fetch data and/or post-processing... Alternatives?
You are trying to fetch a lot of data at once AND to post-process it too. This is a sign, that both, the database AND the PHP engine have to work a lot. Maybe it would be better to project your task in another way. E.g. fetch a lot of data without post-processing. Or fetch a smaller amount of data and allow PHP to post-process it. Look what I've found today on the PDOStatement::fetchAll webpage
PDOStatement::fetchAll - Return Values:
Using this method to fetch large result sets will result in a heavy
demand on system and possibly network resources. Rather than
retrieving all of the data and manipulating it in PHP, consider using
the database server to manipulate the result sets. For example, use
the WHERE and ORDER BY clauses in SQL to restrict results before
retrieving and processing them with PHP.
Uniform array structure:
Is there a special reason to build your resulting array to have a not uniform structure (regarding interests)? Wouldn't it be better to uniformize the array structure? See my results in PHP after post-processing, to understand what I mean vs. the structure you requested.
Code version:
I've prepared a php version - not OOP for this problem - of the data fetching and array building steps. I've commented it and also displayed the data source on which I was testing. In the end I'll also present the results. The steps of building the final array ($personDetails) are straightforward: loop through the fetched data and transfer it only (!) if not already.
Mandatory aliases for same columns from different tables:
I tried to fetch all dress and interest data at once (using wild cards) like this:
SELECT d.*, i.* FROM ...
I ran some tests in PHP and tried some coding options, but, in the end, I concluded: it's impossible to process the feched data in a way like this:
$fetchedData = $statement->fetchAll(PDO::FETCH_ASSOC);
foreach ($fetchedData as $key => $record) {
$dressId = $record['d.id'];
$interestId = $record['i.id'];
//...
}
PHP have not assigned different items in the $record array for the two id columns, whatever I tried. The only one assigned item always corresponds to the last id column in the columns list. So, for a correct output, it's a mandatory task to skip using the wild-cards and to alias all columns having the same name and residing in different tables. Like this:
SELECT d.id AS dress_id, i.id AS interest_id FROM ...
... and the php code:
$fetchedData = $statement->fetchAll(PDO::FETCH_ASSOC);
foreach ($fetchedData as $key => $record) {
$dressId = $record['dress_id'];
$interestId = $record['interest_id'];
//...
}
I'll be honest: even if this situation is somehow intuitiv, I never tested it. I've always used aliasing for the columns with the same names, but now I have the certitude given by the on-code tests too.
Address array item by key vs. search for array item key:
The resulting array ($personDetails) holds the fetched data as follows: each person's id is the KEY of the corresponding details item. Why I did (and recommend) this? Because you may want to directly read a person from the array by just passing the needed id. It's better to address an array item by its - unique - key than to search for it in the whole array.
Oh, almost forgotten: I ran the example on two persons, with different db entries/record numbers.
Good luck.
The code:
Tested on the following tables:
Results of running the query in db editor:
Fetch and process db data in PHP (read_person_details.php):
<?php
// Db configs.
define('HOST', 'localhost');
define('PORT', 3306);
define('DATABASE', 'db');
define('USERNAME', 'user');
define('PASSWORD', 'pass');
define('CHARSET', 'utf8');
/*
* Error reporting.
* To do: define an error handler, an exception handler and a shutdown
* handler function to handle the raised errors and exceptions.
*
* #link http://php.net/manual/en/function.error-reporting.php
*/
error_reporting(E_ALL);
ini_set('display_errors', 1); // SET IT TO 0 ON A LIVE SERVER!
/*
* Create a PDO instance as db connection to db.
*
* #link http://php.net/manual/en/class.pdo.php
* #link http://php.net/manual/en/pdo.constants.php
* #link http://php.net/manual/en/pdo.error-handling.php
* #link http://php.net/manual/en/pdo.connections.php
*/
$connection = new PDO(
sprintf('mysql:host=%s;port=%s;dbname=%s;charset=%s', HOST, PORT, DATABASE, CHARSET)
, USERNAME
, PASSWORD
, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => FALSE,
PDO::ATTR_PERSISTENT => TRUE,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
// Person ID's to fetch.
$personId1 = 1;
$personId2 = 2;
/*
* The SQL statement to be prepared. Notice the so-called named markers.
* They will be replaced later with the corresponding values from the
* bindings array when using PDOStatement::bindValue.
*
* When using named markers, the bindings array will be an associative
* array, with the key names corresponding to the named markers from
* the sql statement.
*
* You can also use question mark markers. In this case, the bindings
* array will be an indexed array, with keys beginning from 1 (not 0).
* Each array key corresponds to the position of the marker in the sql
* statement.
*
* #link http://php.net/manual/en/mysqli.prepare.php
*/
$sql = 'SELECT
p.id AS person_id,
d.id AS dress_id,
d.name AS dress_name,
d.produced_in AS dress_produced_in,
i.id AS interest_id,
i.interest,
i.priority AS interest_priority
FROM person AS p
LEFT JOIN dress AS d ON d.person_id = p.id
LEFT JOIN interest AS i ON i.person_id = p.id
WHERE
p.id = :personId1 OR
p.id = :personId2
ORDER BY
person_id ASC,
dress_name ASC,
interest ASC';
/*
* The bindings array, mapping the named markers from the sql
* statement to the corresponding values. It will be directly
* passed as argument to the PDOStatement::execute method.
*
* #link http://php.net/manual/en/pdostatement.execute.php
*/
$bindings = [
':personId1' => $personId1,
':personId2' => $personId2,
];
/*
* Prepare the sql statement for execution and return a statement object.
*
* #link http://php.net/manual/en/pdo.prepare.php
*/
$statement = $connection->prepare($sql);
/*
* Execute the prepared statement. Because the bindings array
* is directly passed as argument, there is no need to use any
* binding method for each sql statement's marker (like
* PDOStatement::bindParam or PDOStatement::bindValue).
*
* #link http://php.net/manual/en/pdostatement.execute.php
*/
$executed = $statement->execute($bindings);
/*
* Fetch data (all at once) and save it into $fetchedData array.
*
* #link http://php.net/manual/en/pdostatement.fetchall.php
*/
$fetchedData = $statement->fetchAll(PDO::FETCH_ASSOC);
// Just for testing. Display fetched data.
echo '<pre>' . print_r($fetchedData, TRUE) . '</pre>';
/*
* Close the prepared statement.
*
* #link http://php.net/manual/en/pdo.connections.php Example #3 Closing a connection.
*/
$statement = NULL;
/*
* Close the previously opened database connection.
*
* #link http://php.net/manual/en/pdo.connections.php Example #3 Closing a connection.
*/
$connection = NULL;
// Filter the fetched data.
$personDetails = [];
foreach ($fetchedData as $key => $record) {
$personId = $record['person_id'];
$dressId = $record['dress_id'];
$dressName = $record['dress_name'];
$dressProducedIn = $record['dress_produced_in'];
$interestId = $record['interest_id'];
$interest = $record['interest'];
$interestPriority = $record['interest_priority'];
// Check and add person id as key.
if (!array_key_exists($personId, $personDetails)) {
$personDetails[$personId] = [
'dresses' => [],
'interests' => [],
];
}
// Check and add dress details.
if (!array_key_exists($dressId, $personDetails[$personId]['dresses'])) {
$personDetails[$personId]['dresses'][$dressId] = [
'name' => $dressName,
'producedIn' => $dressProducedIn,
// ... (other fetched dress details)
];
}
// Check and add interest details.
if (!array_key_exists($interestId, $personDetails[$personId]['interests'])) {
$personDetails[$personId]['interests'][$interestId] = [
'interest' => $interest,
'interestPriority' => $interestPriority,
// ... (other fetched interest details)
];
}
}
// Just for testing. Display person details list.
echo '<pre>' . print_r($personDetails, TRUE) . '</pre>';
Fetched results in PHP code:
Fetched data ($fetchedData) of two persons:
Array
(
[0] => Array
(
[person_id] => 1
[dress_id] => 1
[dress_name] => shorts
[dress_produced_in] => Taiwan
[interest_id] => 2
[interest] => basketball
[interest_priority] => 2
)
[1] => Array
(
[person_id] => 1
[dress_id] => 1
[dress_name] => shorts
[dress_produced_in] => Taiwan
[interest_id] => 1
[interest] => football
[interest_priority] => 1
)
[2] => Array
(
[person_id] => 1
[dress_id] => 2
[dress_name] => tshirt
[dress_produced_in] => USA
[interest_id] => 2
[interest] => basketball
[interest_priority] => 2
)
[3] => Array
(
[person_id] => 1
[dress_id] => 2
[dress_name] => tshirt
[dress_produced_in] => USA
[interest_id] => 1
[interest] => football
[interest_priority] => 1
)
[4] => Array
(
[person_id] => 2
[dress_id] => 3
[dress_name] => yellow hat
[dress_produced_in] => England
[interest_id] => 4
[interest] => films
[interest_priority] => 1
)
[5] => Array
(
[person_id] => 2
[dress_id] => 3
[dress_name] => yellow hat
[dress_produced_in] => England
[interest_id] => 5
[interest] => programming
[interest_priority] => 1
)
[6] => Array
(
[person_id] => 2
[dress_id] => 3
[dress_name] => yellow hat
[dress_produced_in] => England
[interest_id] => 3
[interest] => voleyball
[interest_priority] => 3
)
)
Filtered data in PHP, e.g. the final array ($personDetails) holding the infos about two persons:
Array
(
[1] => Array
(
[dresses] => Array
(
[1] => Array
(
[name] => shorts
[producedIn] => Taiwan
)
[2] => Array
(
[name] => tshirt
[producedIn] => USA
)
)
[interests] => Array
(
[2] => Array
(
[interest] => basketball
[interestPriority] => 2
)
[1] => Array
(
[interest] => football
[interestPriority] => 1
)
)
)
[2] => Array
(
[dresses] => Array
(
[3] => Array
(
[name] => yellow hat
[producedIn] => England
)
)
[interests] => Array
(
[4] => Array
(
[interest] => films
[interestPriority] => 1
)
[5] => Array
(
[interest] => programming
[interestPriority] => 1
)
[3] => Array
(
[interest] => voleyball
[interestPriority] => 3
)
)
)
)
MySQL (or any other SQL database) does not return results in the nested array format that you describe. So you're going to have to write application code to process the result of the query one way or another.
Writing multiple joins like you have is bound to create a Cartesian product between the joined tables, and this will multiply the size of the result set, if any of them match multiple rows.
I recommend you run a separate query for each type of dependent information, and combine them in application code. Here's an example:
function get_details($pdo, $person_id) {
$sql = "
select p.id, d.name
from person as p
left join dress as d on p.id = d.person_id
where p.id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$person_id]);
$rows = $stmt->fetchAll();
foreach ($rows as $row) {
if (!isset($data[$row['id']])) {
$data[$row['id']] = [
'id' => $row['id'],
'dress' => []
];
}
$data[$row['id']]['dress'][] = $row['name'];
}
$sql = "
select p.id, i.interest
from person as p
left join interest as i on p.id = i.person_id
where p.id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$person_id]);
$rows = $stmt->fetchAll();
foreach ($rows as $row) {
if (!isset($data[$row['id']])) {
$data[$row['id']] = [
'id' => $row['id'],
'interest' => []
];
}
$data[$row['id']]['interest'][] = $row['interest'];
}
return $data;
}
I tested this by calling it in the following way:
$pdo = new PDO("mysql:host=127.0.0.1;dbname=test", "xxxx", "xxxxxxxx");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$result = get_details($pdo, 1);
print_r($result);
Output:
Array
(
[1] => Array
(
[id] => 1
[dress] => Array
(
[0] => shorts
[1] => tshirt
)
[interest] => Array
(
[0] => football
[1] => basketball
)
)
)
Re your comment:
I can't guarantee which method will have better performance. That depends on several other factors, for example the number of rows you need to query, the speed of creating temp tables needed for GROUP_CONCAT() solutions, the network speed of transferring large result sets containing duplicates, and so on.
As with all performance-related questions, the ultimate answer is that you need to test with your data on your server.
What about using a UNION
(
SELECT p.id, d.id AS type_id, d.name, 'dress' AS `type`
FROM person AS p
LEFT JOIN dress AS d ON p.id = person_id
WHERE p.id = 1
)
UNION
(
SELECT p.id, i.id AS type_id , i.interest AS NAME, 'interest' AS `type`
FROM person AS p
LEFT JOIN interest AS i ON p.id = person_id
WHERE p.id = 1
)
You just use group by person id and group_concat and Add distinct on dress and interest otherwise you will get result with duplicate dress and interest.
Query:
select p.id, p.name, group_concat(distinct i.interest) as interests,group_concat(distinct d.name) as dresses
from person as p left
join dress as d on p.id = d.person_id
left join interest as i on p.id = i.person_id
where p.id = 1 group by p.id;
so you will get comma separated interest and dress
Output:
+----+------+---------------------+---------------+
| id | name | interests | dresses |
+----+------+---------------------+---------------+
| 1 | Sam | football,basketball | shorts,tshirt |
+----+------+---------------------+---------------+
Couple of basic ways to do this:
COLLECT ALL INFORMATION AT ONCE
As suggested by #aendeerei, expanding your query:
SELECT p.id AS p_id,
p.name AS p_name,
d.id AS d_id,
d.name AS d_name,
i.id AS i_id,
i.name AS i_name
FROM person as p
LEFT JOIN dress as d on p.id = d.person_id
LEFT JOIN interest as i on p.id = i.person_id
WHERE p.id = 1;
Then in the application code:
$person = [];
foreach ($rows as $row) {
$person['id'] = $row['p_id'];
$person['name'] = $row['p_name'];
if($row['d_id']){
$person['dresses'][$row['d_id']] = [
'id' => $row['d_id'],
'name' => $row['d_name'],
]
}
if($row['i_id']){
$person['interests'][$row['i_id']] = [
'id' => $row['i_id'],
'name' => $row['i_name'],
]
}
}
When you index the dress and interest arrays by their respective IDs, any duplicate data just overwrites the same index. Overwriting could also be avoided with some if(array_key_exists(...)) conditionals.
This idea could be expanded to multiple persons in a $persons array, by indexing each person by their own id.
The downside here is that when people have large numbers of dresses and interests you return a lot of redundant data.. (5 dresses and 5 interests for a person will return their name 25 times).
COLLECT DEPENDENT DATA SEPARATELY
Or as suggested by #BillKarwin, you could run a separate query for each table. I think I'd even be tempted to go one further and separate the person table as well.
SELECT * FROM person WHERE id = 1;
Build person array from single row returned
SELECT * FROM dress WHERE person_id = 1;
Build person's dress array from returned rows if any.
SELECT * FROM interest WHERE person_id = 1;
Build person's interest array from returned rows if any.
This could be expanded to multiple persons by using WHERE person_id IN (...) on the dependent queries using the ids of persons found in the first.
The downside to this is you are running 3 different queries, which could take longer and adds complexity.. and if someone deletes a person in between, you may have some minor concurrency issues to worry about. It could appear that a deleted person still exists, but with no dresses/interests.
here is a little background on what I'm trying to accomplish. I have an array from a MySQL query that is being displayed. I want to sort the array based on a factor. The factor is calculated inline based on the time the article was posted & the number of votes it's received. Something like this:
// ... MySQL query here
$votes = $row['0']
$seconds = strtotime($record->news_time)+time();
$sum_total = pow($votes,2) / $seconds;
So the array thats coming in looks something like this:
Array (
[0] => stdClass Object (
[id] => 13
[news_title] => Article
[news_url] => http://website.com/article/14
[news_root_domain] => website.com
[news_category] => Business
[news_submitter] => 2
[news_time] => 2013-02-18 12:50:02
[news_points] => 2
)
[1] => stdClass Object (
[id] => 14
[news_title] => Title
[news_url] => http://www.website.com/article/2
[news_root_domain] => www.website.com
[news_category] => Technology
[news_submitter] => 1
[news_time] => 2012-10-02 10:03:22
[news_points] => 8
)
)
I want to sort the aforementioned array using the factor I mentioned above. The idea is to show the highest rated articles first on the list (using the calculated factor), instead of the default sorting method that the array comes in. It seems like usort might be my best bet, but let me know what you all think?
Do it all in the query:
SELECT n.*, ( POW(?, 2) / (UNIX_TIMESTAMP(n.news_time) + UNIX_TIMESTAMP(NOW())) ) as rank
FROM news_table n
ORDER BY rank;
Now in order to get the votes you may have to do a subquery or a join, but i cant advise on that because you dont give enough info on where the votes are coming from. You could however supply the votes to the query as well instead of selecting it all in one shot something like:
$sql = sprintf('SELECT n.*, ( POW(%d, 2) / (UNIX_TIMESTAMP(n.news_time) + UNIX_TIMESTAMP(NOW())) ) as rank FROM news_table n ORDER BY rank', $votes);
Aside from that, yes you could use usort, but that would also require you to have the entire recordset in memory to provide accurate sorting, which could be problematic at some point.
I'm not sure if this is even possible straight from a MySQL query (without manipulating the data after), but I have been pondering on something...
Say we have one table, authors, and another books. Authors can have many books, and this is indicated by a author_id column in the books table.
Is there a way to perform a query that:
1) Retrieves a single author record, with a column name 'books' which holds an array of book records, belonging to the author.
2) The same result, but for all authors in the database.
Any input on whether this is possible or not, and any methods would be greatly appreciated!
Update
Here's an example of the desired output of the first query:
stdClass Object
(
[id] => 1
[name] => Test Author
[age] => 28
[books] => Array
(
[0] => stdClass Object
(
[title] => Book 1
[id] => 1
)
[1] => stdClass Object
(
[title] => Book 2
[id] => 2
)
)
)
1) Yes, GROUP_CONCAT() can do that (although I don't this would be the best way to d o it).
2) Yes, GROUP BY author
This is essentially very simple I've just been rather verbose about it. My data is structured as shown at the bottom of my post. My goal is to organise the data in a simple spreadsheet format, 'flattening' the relational database. Something like:
Product1 URL
Product2 URL URL URL
Product3 URL
My initial approach is as below. The reason I'm looking for an alternative is I have > 10,000 rows in the products table and each product has an average of 5 URLs. This generates an enourmous amount of MySQL queries (in fact exceeding my shared hosting resources), there must be a better way, right? My code has been simplified to distill the essence of my question.
My Approach
//Query to get all products
$products = mysql_query("SELECT * FROM products");
$i = 0;
//Iterate through all products
while($product = mysql_fetch_array($products)){
$prods[$i]['name'] = $product['name'];
$prods[$i]['id'] = $product['id'];
//Query to get all URLs associated with this particular product
$getUrls = mysql_query("SELECT * FROM urls WHERE product_id =".$product['id']);
$n = 0;
while($url = mysql_fetch_array($getUrls)){
$prods[$i]['urls'][$n] = $url['url'];
}
}
The Result
To be clear, this is the intended result, the output was expected. I just need a more efficient way of getting from the raw database tables to this multidimensional array. I suspect there's more efficient ways of querying the database. Also, I understand that this array uses a lot of memory when scaled up to the required size. As my aim is to use this array to export the data to .csv I would consider using XML (or something) as an intermediary if it is more efficient. So feel free to suggest a different approach entirely.
//The result from print_r($prods);
Array
(
[0] => Array
(
[name] => Swarovski Bracelet
[id] => 1
[urls] => Array
(
[0] => http://foo-example-url.com/index.php?id=7327
)
)
[1] => Array
(
[name] => The Stranger - Albert Camus
[id] => 2
[urls] => Array
(
[0] => http://bar-example-url.com/index.php?id=9827
[1] => http://foo-foo-example-url.com/index.php?id=2317
[2] => http://baz-example-url.com/index.php?id=5644
)
)
)
My Database Schema
Products
id product_name
1 Swarovski Bracelet
2 The Stranger - Albert Camus
3 Finnegans Wake - James Joyce
URLs
id product_id url price
1 1 http://foo-example-url.com/index.php?id=7327 £16.95
2 2 http://bar-example-url.com/index.php?id=9827 £7.95
3 2 http://foo-foo-example-url.com/index.php?id=2317 £7.99
4 2 http://baz-example-url.com/index.php?id=5644 £6.00
5 3 http://bar-baz-example-url.com/index.php?id=9534 £11.50
URLs.product_id links to products.id
If you've read this, thank you for your incredible patience! I look forward to reading your response. So how can I do this properly?
The SQL
SELECT p.id, p.product_name, u.url
FROM products p, urls u
WHERE p.id = u.product_id ORDER BY p.id
PHP code to "flatten" the urls, I have just seperated the urls with a space.
$i = 0;
$curr_id;
while($product = mysql_fetch_array($products))
{
$prods[$i]['name'] = $product['product_name'];
$prods[$i]['id'] = $product['id'];
if($product['id'] == $curr_id)
{
$prods[$i]['urls'] .= " ".$url['url'];
}
else
{
$prods[$i]['urls'] = $url['url'];
}
$i++;
$curr_id = $product['id'];
}
The Result
[0] => Array
(
[name] => Swarovski Bracelet
[id] => 1
[urls] => http://foo-example-url.com/index.php?id=7327
)
[1] => Array
(
[name] => The Stranger - Albert Camus
[id] => 2
[urls] => http://bar-example-url.com/index.php?id=9827 http://foo-foo-example-url.com/index.php?id=2317 http://baz-example-url.com/index.php?id=5644
)
You are able to pull back that exact information with one query even if you want to pull back the csv of urls.
SELECT
p.id, p.product_name,
GROUP_CONCAT(u.url) AS URLS
FROM products p LEFT JOIN urls u
ON p.id = u.product_id
GROUP BY p.id, p.product_name
And then you have 1 query to your database that brings back what you need.
Or the cleaner approach is how bruce suggested and then let PHP merge all of the results together.
You can accomplish this in one query:
select * from products join urls on products.id = urls.product_id order by products.id
pseudocode:
while(query_result) {
if (oldpid == pid) { print ", \"url\"" }
else { print "pid, \"url\"" }
oldpid = pid;
}