I am busy with a ticket systeem and i have the question in a table, users information in an other table and the responses in an other table.
What i want is to get the Question (Table 1) with the user information by user ID and all the responses (also with user information by id)
Now i have the following code:
public function ticketSingle($id=""){
$sql = "
SELECT ticketSubmitted.*, users.Name, users.email, users.phone, ticketResponse.*
FROM ticketSubmitted
INNER JOIN users
ON users.id = ticketSubmittedUserId
JOIN ticketResponse
ON ticketId = ticketSubmittedID
WHERE ticketSubmittedID = '".$id."'
";
$result = $this->run($sql, $bind);
return $result[0];
}
Table 1 = ticketSubmitted (the question, with an userID "ticketSubmittedUserId")
Table 2 = users (The user information)
Table 3 = ticketResponse (The table with the reactions)
But if i Print the results i get only one record of the TicketResponse and what i want is all the reactions.
Can someone help me out?
This is what the function return:
Array
(
[ticketSubmittedID] => 1
[ticketSubmittedUserId] => 1
[ticketSubmittedDate] => 2018-02-05 16:00:00
[ticketSubmittedTitle] => Question Title
[ticketSubmittedMessage] => Hello World!
[ticketSubmittedUserIp] => XXX.XXX.XXX.XXX
[ticketSubmittedStatus] => 1
[Name] => John Doe
[email] => john#doe.com
[phone] => 0612345678
[ticketResponseId] => 1
[ticketId] => 1
[ticketUserId] => 2
[ticketMessage] => Hello Reaction
[ticketDate] => 2018-02-05 17:05
[ticketIp] => XXX.XXX.XXX.XXX
)
You are returning only the first row return $result[0]; 0 in the first index of the result. so you should return all eg:
return $result;
and loop over the result for manage what you need
foreach($result as $key => $value){
echo $value['ticketResponseId'];
}
You can check the real return content using
var_dump($result);
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.
I have 3 tables: tco_articles, tco_module_eostext and tco_articles_modules. My tco_articles has unique id key. One for each article. My tco_module_eostext has unique instance_id that belongs to each article.
My tco_articles_modules contains all article_ids, but have 9 times as much instance_ids that are used in other tables.
So I can have article_id with instance_id that when you query in the tco_module_eostext will return empty.
I'd like to make a query that will return correct body text for the correct article.
So far I have:
global $wpdb;
$posts = array();
$ids = $wpdb->get_results('SELECT DISTINCT instance_id, article_id FROM tco_articles_modules', ARRAY_A);
This returns array with all the instances and ids like:
Array
(
[0] => Array
(
[instance_id] => 928615
[article_id] => 129396
)
[1] => Array
(
[instance_id] => 928616
[article_id] => 129396
)
[2] => Array
(
[instance_id] => 928617
[article_id] => 129396
)
[3] => Array
(
[instance_id] => 928618
[article_id] => 129396
)
You can see that the article_ids are the same but instance_id. When you put
$wpdb->get_results('SELECT body FROM tco_module_eostext WHERE instance_id=928617 ', ARRAY_A);
You may get empty, but for
$wpdb->get_results('SELECT body FROM tco_module_eostext WHERE instance_id=928618 ', ARRAY_A);
You could have some body text.
This is my problem. I need to go through all of them and filter out the not empty ones and assign them correct article. I managed to output the articles
foreach ($ids as $key => $value) {
$instance_ID = $value['instance_id'];
$article_ID = $value['article_id'];
$article_out = $wpdb->get_results('SELECT * FROM tco_articles WHERE id='.$article_ID.' ', ARRAY_A);
$posts[$article_ID] = $article_out[0];
}
Which returns something like:
Array
(
[129396] => Array
(
[id] => 129396
[headline] => Bla bla bla title
[intro] => This is cool article intro
[needs_review] => 0
[published] => 2014-12-16 09:17:00
[unpublished] =>
[hidden] => 0
[no_single_page] => 0
[permalink] => link-perma-link-here
[internal_slug] =>
[type_id] => 3
[thread_id] => 0
[news_id] => 0
[header_game_id] => 0
[puff_hh_id] => 0
[puff_title] =>
[hdrcol_id] => 900
[review_queued] =>
[lock_timeout] => 0
[created] => 2014-12-16 09:17:00
[updated] => 2015-01-16 13:51:30
[created_by] => 84142
[updated_by] => 84142
)
...
Now I'd like to append the body text from the tco_module_eostext table.
Is there a query I can use to do this automatically or to do this one at the time and then append to the $posts array?
The foreach method of querying is kinda slow when you have 180000+ posts.
Any help is appreciated.
If you are sure that there is always only one row in tco_module_eostext against each article_id, you can use JOIN (inner join), which will only show one row for each article_id.
SELECT a.*, t.body
FROM tco_articles a
JOIN tco_articles_modules m ON m.article_id = a.id
JOIN tco_module_eostext t ON m.instance_id = t.instance_id
//WHERE .....
But, this will not show any row of some articles if there is no entry in other two tables for that article_id. But there is still way to solve this. We can use LEFT OUTER JOIN and then make sure we only make the join if there is any row in tco_module_eostext for any instace_id. This will make sure you get at least the article info from tco_articles table when there is no data in other tables.
SELECT a.*, t.body
FROM tco_articles a
LEFT OUTER JOIN tco_articles_modules m ON m.article_id = a.id AND EXISTS ( SELECT NULL FROM tco_module_eostext WHERE instance_id = m.instance_id )
LEFT OUTER JOIN tco_module_eostext t ON m.instance_id = t.instance_id
//WHERE .....
Why not use a query with join?
Not tested!:
SELECT
article.*,
text.body as bodytext
FROM
tco_articles_modules AS modules LEFT OUTER JOIN
tco_module_eostext AS text ON
modules.instance_id = text.instance_id LEFT OUTER JOIN
tco_articles AS article ON
article.id = modules.article_id
it should get all articles with the assigned article_id from tco_articles_modules
Have a look at OUTER Join - you may want to replace this with an INNER JOIN for faster Queries. Also you ma want an WHERE condition for filtering in the query. Watch also out for the right indexing in mysql table - each joining column should be indexed - this will get much more faster results.
I’ve seen the following question on StackOverflow, Intelligent MySQL GROUP BY for Activity Streams posted by Christian Owens 12/12/12.
So I decided to try out the same approach, make two tables similar to those of his. And then I pretty much copied his query which I do understand.
This is what I get out from my sandbox:
Array
(
[0] => Array
(
[id] => 0
[user_id] => 1
[action] => published_post
[object_id] => 776286559146635
[object_type] => post
[stream_date] => 2015-11-24 12:28:09
[rows_in_group] => 1
[in_collection] => 0
)
)
I am curious, since looking at the results in Owens question, I am not able to fully get something, and does he perform additional queries to grab the actual metadata? And if yes, does this mean that one can do it from that single query or does one need to run different optimized sub-queries and then loop through the arrays of data to render the stream itself.
Thanks a lot in advanced.
Array
(
[0] => Array
(
[id] => 0
[user_id] => 1
[fullname] => David Anderson
[action] => hearted
[object_id] => array (
[id] => 3438983
[title] => Grand Theft Auto
[Category] => Games
)
[object_type] => product
[stream_date] => 2015-11-24 12:28:09
[rows_in_group] => 1
[in_collection] => 1
)
)
In "pseudo" code you need something like this
$result = $pdo->query('
SELECT stream.*,
object.*,
COUNT(stream.id) AS rows_in_group,
GROUP_CONCAT(stream.id) AS in_collection
FROM stream
INNER JOIN follows ON stream.user_id = follows.following_user
LEFT JOIN object ON stream.object_id = object.id
WHERE follows.user_id = '0'
GROUP BY stream.user_id,
stream.verb,
stream.object_id,
stream.type,
date(stream.stream_date)
ORDER BY stream.stream_date DESC
');
then parse the result and convert it in php
$data = array(); // this will store the end result
while($row = $result->fetch(PDO::FETCH_ASSOC)) {
// here for each row you get the keys and put it in a sub-array
// first copy the selected `object` data into a sub array
$row['object_data']['id'] = $row['object.id'];
$row['object_data']['title'] = $row['object.title'];
// remove the flat selected keys
unset($row['object.id']);
unset($row['object.title']);
...
$data[] = $row; // move to the desired array
}
you should get
Array
(
[0] => Array
(
[id] => 0
[user_id] => 1
[fullname] => David Anderson
[verb] => hearted
[object_data] => array (
[id] => 3438983
[title] => Grand Theft Auto
[Category] => Games
)
[type] => product
[stream_date] => 2015-11-24 12:28:09
[rows_in_group] => 1
[in_collection] => 1
)
)
It seems that you want a query where you can return the data you're actually able to get plus the user fullname and the data related to the object_id.
I think that the best effort would be to include some subqueries in your query to extract these data:
Fullname: something like (SELECT fullname FROM users WHERE id = stream.user_id) AS fullname... or some modified version using the stream.user_id, as we can't identify in your schema where this fullname comes from;
Object Data: something like (SELECT CONCAT_WS(';', id, title, category_name) FROM objects WHERE id = stream.object_id) AS object_data. Just as the fullname, we can't identify in your schema where these object data comes from, but I'm assuming it's an objects table.
One object may have just one title and may have just one category. In this case, the Object Data subquery works great. I don't think an object can have more than one title, but it's possible to have more than one category. In this case, you should GROUP_CONCAT the category names and take one of the two paths:
Replace the category_name in the CONCAT_WS for the GROUP_CONCAT of all categories names;
Select a new column categories (just a name suggestion) with the subquery which GROUP_CONCAT all categories names;
If your tables were like te first two points of my answer, a query like this may select the data, just needing a proper parse (split) in PHP:
SELECT
MAX(stream.id) as id,
stream.user_id,
(select fullname from users where id = stream.user_id) as fullname,
stream.verb,
stream.object_id,
(select concat_ws(';', id, title, category_name) from objects where id = stream.object_id) as object_data,
stream.type,
date(stream.stream_date) as stream_date,
COUNT(stream.id) AS rows_in_group,
GROUP_CONCAT(stream.id) AS in_collection
FROM stream
INNER JOIN follows ON 1=1
AND stream.user_id = follows.following_user
WHERE 1=1
AND follows.user_id = '0'
GROUP BY
stream.user_id,
stream.verb,
stream.object_id,
stream.type,
date(stream.stream_date)
ORDER BY stream.stream_date DESC;
In ANSI SQL you can't reference columns not listed in your GROUP BY, unless they're in aggregate functions. So, I included the id as an aggregation.
I got 2 relational tables:
table "categories"
id int(11)
title varchar(255)
table "posts"
id int(11)
title varhcar(255)
category_id int(11) // foreign key
If I select the "categories" table, I would like to get a PHP array with al the categories (as in "SELECT * categories") but including an inner array with all its posts:
Array (
/* first category */
[0] = Array (
[id] => 1
[title] => "Rock"
/* all its posts */
[posts] => Array (
[0] = Array(
[id] = 100
[title] = "Rock post title"
[category_id] = 1
)
[1] = Array(
[id] = 101
[title] = "Other rock post title"
[category_id] = 1
)
)
/* second category */
[1] = Array (
)
/* ... */
)
If I just made a "join" query I get all the results combined, something like:
id title id title category_id
1 Rock 100 "Rock post title" 1
2 Rock 101 "Other rock post" 1
3 Rock 102 "Final rock post" 1
I don't want to make multiple queries, because I think is inefficient.
Is there anyway to achive the desire result with one query?
I know CakePHP manage to return relational tables results in this format, so I'm looking to achieve the same result.
The join should look something like:
select c.id, c.title, p.id, p.title, p.category_id
from categories c, posts p
where c.id = p.category_id
order by c.id, p.id
First, if you want this functionality consider using an ORM library (such as what CakePHP and other frameworks provide) rather than rolling your own code for a problem that's already been solved.
You cannot do 'inner arrays' in SQL, not without great ugliness (like packing records into a string column then unpacking them in PHP).
But for a quick 'n dirty solution, just use your original join query as long as you rename the post id and title (eg, 'post_id') in the query to avoid confusion with category id. Then loop over the resultset and build your array.
$newArray = array();
foreach($resultset as $row) {
if(!array_key_exists($row['category_id'],$newArray)) {
$newArray[$row['category_id']] = array('id' => $row['category_id'], 'title' => $row['title'], 'posts' => array());
}
$newArray[$row['category_id']]['posts'] = array('id' => $row['post_id'], 'title' => $row['post_title'], 'category_id' => $row['category_id']);
}
I didn't write this code in an editor so I apologize for typos. You get the general idea.