Group results after join? - php

So, I have three tables. Movies, movies_genres and genres. I want to get a movie by its Id, and also join its genres in the result. I managed to join the results, but it doesn't display as i want it to. I'm not sure if what I'm asking is possible.
This is my query:
SELECT `movies`.*, GROUP_CONCAT(genres.id) AS genre_id, GROUP_CONCAT(genres.name) AS genre_name
FROM (`movies`)
INNER JOIN `movies_genres`
ON `movies_genres`.`movie_id` = `movies`.`id`
INNER JOIN `genres`
ON `genres`.`id` = `movies_genres`.`genre_id` WHERE `movies`.`id` = 19908
GROUP BY `movies`.`id`
The query was generated by Codeigniters Active Record class, here is the Codeigniter code if that helps:
$this->db->select('movies.*, GROUP_CONCAT(genres.id) AS genre_id, GROUP_CONCAT(genres.name) AS genre_name');
$this->db->from('movies');
$this->db->where('movies.id', $movie_id);
$this->db->join('movies_genres', 'movies_genres.movie_id = movies.id', 'inner');
$this->db->join('genres', 'genres.id = movies_genres.genre_id', 'inner');
$this->db->group_by('movies.id');
Here is the result i'm currently getting:
Array
(
[id] => 19908
[movie_title] => Zombieland
[overview] => An easily spooked guy...
[genre_id] => 28,12,35,27
[genre_name] => Action,Adventure,Comedy,Horror
)
And this is what I want:
Array
(
[id] => 19908
[movie_title] => Zombieland
[overview] => An easily spooked guy...
[genres] => array(
0 => array(
'id' => 28,
'name' => Action
),
1 => array(
'id' => 12,
'name' => Adventure
),
1 => array(
'id' => 35,
'name' => Comedy
),
1 => array(
'id' => 27,
'name' => Horror
)
)
)
Is this possible, and if so, how?

The query you listed will have n rows (where n = # of movies) whereas the query it seems you want will have many more rows (# of movie_genre's entries). You're probably better off leaving that query as it is, and doing some post processing.
Consider:
After you get it, just run your result (e.g. $result) array through something like:
foreach($result as &$row)
{
// Split over commas
$gi_elements = explode(',', $row['genre_id']);
$gn_elements = explode(',', $row['genre_name']);
// Build genre
$row['genre'] = array();
for($i=0; $i<count($gi_elements); $i++)
{
$row['genre'][] = array('id' => $gi_elements[$i], 'name' => $gn_elements[$i]);
}
// Cleanup
unset($row['genre_id']);
unset($row['genre_name']);
}
Afterwards, $results will look exactly as you wish without extra database work.
EDIT: Fixed some typos.

Related

Trying to merge two results of SQL queries - ToDoList App

I am trying to merge two results of two queries in MYSQL using PHP, but I am puzzled how to do it! I am using PDO. I am programming for a hobby and am trying to make a to do list app just like a Trello board. However, I just can't figure out how to merge two results from different tables in a database.
The idea is as follows:
I have a table called 'task_lists' with the content:
'list_id => 1, list_name = 'listOne'
'list_id => 2, list_name = 'listTwo'
And a table called 'tasks':
task_id => 1, list_id => 1, task_name => 'taskOfListOne', duration => 5, is_done => 1
task_id => 2, list_id => 1, task_name => 'anotherTaskOfListOne', duration => 5, is_done => 1
task_id => 3, list_id => 2, task_name => 'taskOfListTwo', duration => 10, is_done => 0
And I am trying to create an array that is merged between the two results as something like:
(I know this is a rough picture of how the array is supposed to look like)
$result = [array]
[list_id] = 1, [list_name] = 'listOne' =>
[array][list_id] = 1, ['task_name] = taskOfListOne,[duration] = 5, ['is_done'] => 1
[array][list_id] = 1, ['task_name] = anotherTaskOfListOne,[duration] = 5, ['is_done'] => 1
[list_id] = 2, [list_name] = 'listTwo' =>
[array][list_id] = 2, ['task_name] = taskOfListTwo,[duration] = 5, ['is_done'] => 1
Is this even possible? I have tried a Union sql query and methods like nested foreach statements, but none of them worked for me. Am I missing something here?
PS: Sorry for my bad english.
Have you tried a left join?
SELECT TL.`list_id`, TL.`list_name`, T.`task_name`, T.`duration`
FROM task_lists AS TL
LEFT JOIN tasks as T ON TL.`list_id` = T.`list_id`
And then in PHP you build the array in the format you want.
Later edit:
Simple PHP example to parse SQL data as you asked (to remove duplicated info):
<?php
// $mysql_rows -> here is your query result, fetched as associative array
$filtered_array = array();
foreach ($mysql_rows as $row){
// Initiate record if is not already initiated
if (!isset($filtered_array[ $row['list_id'] ])){
$filtered_array[ $row['list_id'] ] = array(
'list_id' => $row['list_id'],
'list_name' => $row['list_name'],
'tasks' => array()
);
}
// Add tasks
$filtered_array[ $row['list_id'] ]['tasks'][] = array(
'task_name' => $row['task_name'],
'duration' => $row['duration'],
'is_done ' => $row['is_done ']
);
}
// Optional: if you want to remove list_id from $filtered_array key names, uncomment the next line
// $filtered_array = array_values($filtered_array);
?>

CakePHP join with comma separated ids

Here I want to join two table with comma separated ids
For example my data is like:
[Restaurant] => Array
(
[RST_ID] => 171
[RST_NAME] => oneone
[RST_IMAGE] =>
[RST_CAT_ID] => 2,4,6
[RST_CT_ID] => 27
[RST_IS_TOP] => 3
[RST_QR_CODE] =>
[RST_CREATED_DATE] => 1394536725
[RST_MODIFIED_DATE] => 1394536725
[RST_STATUS] => 1
)
[Category] => Array
(
[CAT_ID] => 2
[CAT_NAME] => Vegetarian
[CAT_CREATED_DATE] => 1375175962
[CAT_MODIFIED_DATE] => 1375175962
[CAT_STATUS] => 1
)
My Model Code:
var $belongsTo = array(
'Category' => array(
'className' => 'Category',
'foreignKey' => 'RST_CAT_ID',
'conditions' => array('Category.CAT_ID IN ( Restaurant.RST_CAT_ID)')
)
);
Real Query:
SELECT
`Restaurant`.`RST_ID`, `Restaurant`.`RST_NAME`, `Restaurant`.`RST_IMAGE`,
`Restaurant`.`RST_CAT_ID`, `Restaurant`.`RST_CT_ID`, `Restaurant`.`RST_IS_TOP`,
`Restaurant`.`RST_QR_CODE`, `Restaurant`.`RST_CREATED_DATE`,
`Restaurant`.`RST_MODIFIED_DATE`, `Restaurant`.`RST_STATUS`,
`Category`.`CAT_ID`, `Category`.`CAT_NAME`, `Category`.`CAT_CREATED_DATE`,
`Category`.`CAT_MODIFIED_DATE`, `Category`.`CAT_STATUS`, `City`.`CT_ID`,
`City`.`CT_NAME`, `City`.`CT_CREATED_DATE`, `City`.`CT_MODIFIED_DATE`,
`City`.`CT_STATUS`
FROM `dailybit_dailybites`.`restaurant` AS `Restaurant`
LEFT JOIN `dailybit_dailybites`.`category` AS `Category`
ON (`Restaurant`.`RST_CAT_ID` = `Category`.`CAT_ID`
AND `Category`.`CAT_ID` IN ( `Restaurant`.`RST_CAT_ID`))
LEFT JOIN `dailybit_dailybites`.`city` AS `City`
ON (`Restaurant`.`RST_CT_ID` = `City`.`CT_ID`)
WHERE 1 = 1
So what’s the solution here?
It's giving me just one category data that for first id only.
First have a look at this question: MySQL search in comma list
As you can see the belongsTo query is just generating a join on the single id, CakePHP by default doesn't respect this special case. You will have to alter your query and pass all the ids manually, but your DB design is bad and it doesn't follow the CakePHP conventions at all.
How do you prevent duplicates (which would waste space)
How do you remove a given value (Requires custom function, leading to possibility of errors?
How do you respond to performance issues as the size of my tables increase?
Instead of changing the query you should change this awkward DB design. You want to use HABTM here and a join table: Restaurant hasAndBelongsToMany Categoryy.
restaurants <-> restaurants_categories <-> categories
If you insist on using this bad DB design you'll have to use bindModel() and set the conditions manually:
'conditions' => array('FIND_IN_SET (Category.CAT_ID, ' . $listOfIds. ')')
I haven't tested this, try it yourself, see FIND_IN_SET() vs IN()
You'll have to have another method that gets you all the ids you want here before. Like I said, this is ineffectice and bad design.
You have to set your foreign Key false and find_in_set condition
var $belongsTo = array(
'Category' => array(
'className' => 'Category',
'foreignKey' => false,
'conditions' => array('FIND_IN_SET(Category.CAT_ID,Restaurant.RST_CAT_ID)')
)
);
// you can pass an array at the place of 'Restaurant.RST_CAT_ID'

How to turn sql result to multi-dimensional array dynamically?

Here is the query string.
$query = "SELECT t.id, t.assignee, t.owner,
d.code, d.status, d.target_completion_date,
d.target_extension_date, d.submission_date, d.approval_date,
d.revision_start_date, d.revision_completion_date, d.message,
ty.name, f.orig_name, f.new_name,
b.payment_date, b.discount, b.total_cost, b.amount_payed, b.edit_level,
b.billing_type, b.pages, b.words
FROM tasks t
INNER JOIN details d ON t.detail_id = d.id
INNER JOIN billing b ON t.billing_id = b.id
INNER JOIN TYPE ty ON d.document_type_id = ty.id
INNER JOIN files f ON t.file_id = f.id
WHERE t.assignee = 'argie1234'";
And this is the array i would like the query result to turn into.
$user = array('allTask'=>array(array('taskid' => 1,
'assignee'=>'argie1234',
'owner'=>'austral1000',
'details' => array( 'code' => 'E',
'status'=>'TC',
'targetCompletionDateUTC'=>'1379401200',
'targetExtentionDateUTC'=>'1379401200',
'submissionDateUTC'=>'1379401200',
'approvalDateUTC'=>'1379401200',
'revisionStartDateUTC'=>'1379401200',
'revisionCompletionDateUTC'=>'1379401200',
'messageToEditor'=>'Please work on it asap.',
'documentType' => 'Thesis'),
'file' => array('orig_name' =>'originalname.docx',
'new_name' => 'newname.docx'),
'billing'=>array('paymentDate'=>'July 26,2013 12:40',
'discount' => '0',
'totalRevisionCharge' => '$20.00',
'totalAmountPayed' => '$20.00',
'revisionLevel' => '1',
'chargeType'=> '1',
'numPages' => '60',
'numWords' => '120,000' ) ),
array('taskid' => 12,
'assignee'=>'argie1234',
'owner'=>'usaroberto',
'details' => array( 'code' => 'E',
'status'=>'TC',
'targetCompletionDateUTC'=>'1379401200',
'targetExtentionDateUTC'=>'1379401200',
'submissionDateUTC'=>'1379401200',
'approvalDateUTC'=>'1379401200',
'revisionStartDateUTC'=>'1379401200',
'revisionCompletionDateUTC'=>'1379401200',
'messageToEditor'=>'Please work on it asap.',
'documentType' => 'Thesis'),
'file' => array('orig_name' => 'originalname.docx',
'new_name' => 'newname.docx'),
'billing'=>array('paymentDate'=>'July 26,2013 12:40',
'discount' => '0',
'totalRevisionCharge' => '$20.00',
'totalAmountPayed' => '$20.00',
'revisionLevel' => '1',
'chargeType'=> '1',
'numPages' => '60',
'numWords' => '120,000' ) ),
'account' => array( 'username' => 'marooon55',
'emailadd' => 'marooon#yahoo.com',
'firstname' => 'Maroon',
'initial' => 'E',
'lastname' => 'Young',
'country' => 'Australia',
'gender' => 'M',
'password' =>'360e2801190744a2af74ef6cbfdb963078b59709',
'activationDate' => '2013-09-13 14:30:34') );
How can i create the above array? I sure know how to define multi dimensional array, regretfully though i am having difficulty creating this complex array dynamically. As a beginner i don't even know where to begin.
Here is an example that might help you out. Try starting with simple multi dimensional arrays, once you get a hold of it, you can move onto building complex ones. You will then find that the array you want to build is not really difficult than you initially thought it to be.
$mycomplexarray = array('key1' => array('val1', 'val2'),
'key2' => array('val3', 'val4' => array('val5', 'val6')
)
);
You could create the array just as you have here. I'm not gonna write the whole thing out, but something like this...
$result = $mysqli->query($query); // however you query the db is up to you.
$row = $result->fetch_assoc(); //same as query use your prefered method to fetch
$user = array('allTask'=>array(array('taskid' => $row['id'],
'assignee'=>$row['assignee'],
'owner'=>$row['owner'],
'details' => array( 'code' => $row['code'],
'status'=>$row['status'],
...etc, Hope this makes sense for you.
Set up a structure array first that defines which columns will be stored in a sub array like
$struc=array('Id'->0, 'assignee'->0, 'owner'->0,
'code'->'detail', 'status'->'detail', 'target_completion_date'->'detail',
'target_extension_date'->'detail', 'submission_date'->'detail', 'approval_date'->'detail',
'revision_start_date'->'detail', 'revision_completion_date'->'detail', 'message'->'detail',
'name'->'file', 'orig_name'->'file', 'new_name'->'file',
'payment_date'->'billing', 'discount'->'billing', 'total_cost'->'billing', 'amount_payed'->'billing', 'edit_level'->'billing', 'billing_type'->'billing', 'words');
In your while ($a=mysqli_fetch_assoc($res)) loop you can now use this structure to decide whether you want to store an element directly in your target array or whether you want to place it in the subarray named in this structure array. Like
$res=mysqli_query($con,$sql);
$arr=array();
while($a=mysqli_fetch_assoc($res)) {
// within result loop: $a is result from mysqli_fetch_assoc()
$ta=array(); // temp array ...
foreach ($a as $k => $v){
if ($struc[$k]) $ta[struc[$k]][$k]=$v;
else $ta[$k]=$v;
}
$arr[]=$ta; // add to target array
}
This is the complete code, no more is needed. It was typed up on my iPod, so it is NOT tested yet.
The generated array should be equivalent to your $user['allTask'] array.

CakePHP conditional query with controller that 'hasAndBelongsToMany'

I apologize for the horrible title, I couldn't think of how to explain my problem.
In my database I have the following tables, articles, tags, and articles_tags. An article can have many tags.
Currently I am able to grab all the articles, with all the tags, but I want to be able to find articles based upon it's tags.
My select is simple:
$articles = $this->Article->find('all', array(
// extra condition to check for tag, maybe?
'conditions' => array('Article.status' => 'active'),
'limit' => $this->articles_per_page,
'offset' => ($page_num-1)*$this->articles_per_page
));
My return from the database is as follows:
Array
(
[0] => Array
(
[Article] => Array
(
[id] => 1
)
[Tag] => Array
(
[0] => Array
(
[id] => 1
[name] => Ruby
[slug] => ruby
[uses] => 1
[ArticlesTag] => Array
(
[id] => 1
[article_id] => 1
[tag_id] => 1
)
)
)
)
What do I do if I only want to return the articles with a Ruby tag?
Try this
// In your Article model
function getArticleByTagSql($tag)
{
$dbo = $this->getDataSource();
$subQuery = $dbo->buildStatement(
array(
'fields' => array('DISTINCT(ArticlesTag.article_id)'),
'table' => "articles_tags",
'joins' => array(
array('table' => 'tags',
'alias' => 'Tag',
'type' => 'INNER',
'conditions' => array('ArticlesTag.tag_id = Tag.id')
)
),
'alias'=>"ArticlesTag",
'conditions' => array("Tag.name"=>Sanitize::clean($tag_words)),
'order' => null,
'group' => "ArticlesTag.article_id"
),
$this
);
$subQuery = ' Article.id IN (' . $subQuery . ')';
return $dbo->expression($subQuery);
}
// In your Articles Controller
$this->paginate['conditions'][] = $this->Article->getArticleByTagSql($tag_name);
$this->paginate['conditions'][] = array('Article.status' => 'active');
$this->paginate['limit'] = $this->articles_per_page;
// or as per your example
$articles = $this->Article->find('all', array(
// extra condition to check for tag, maybe?
'conditions' => array('Article.status' => 'active',$this->Article->getArticleByTagSql($tag_name)),
'limit' => $this->articles_per_page,
'offset' => ($page_num-1)*$this->articles_per_page
));
For conditions like this you can use the LinkableBehavior. It's designed for exact your wanted result. In case you don't want to use it, you have to do a query on the tag controller:
$this->Tag->find('all', array('conditions' => array('Tag.name' => 'ruby')));
Not possible directly.
The easiest way is to make the query through the tag controller
I'm updating this question with an answer for CakePHP 3.x.
You can find all articles for a given tag using an INNER JOIN. The benefit of this join is that it keeps articles as the primary table in the query. Making this an easy solution when you're paginating results by a HABTM join.
$tag_id = 3; // the ID of the tag
$query = $this->Articles->find('all')
->innerJoin('articles_tags', [
'Articles.id = articles_tags.article_id',
'articles_tags.tag_id' => $tag_id
]);
The first join condition has to be the array key. If you use a => array assignment then CakePHP will pass the articles_tags.article_id as a string argument to the join condition. Which won't work.
You can also stack innerJoin for multiple HABTM conditions. For example; find all articles for a tag and also for a category. You can also use the in query expression to match multiple tags.
You can also use the above in pagination like this $articles = $this->paginate($query);
I'm sure this information is out on the web, but this question was the top of google's results. So maybe this will help others.

How do I combine two arrays in PHP based on a common key?

I'm trying to join two associative arrays together based on an entry_id key. Both arrays come from individual database resources, the first stores entry titles, the second stores entry authors, the key=>value pairs are as follows:
array (
'entry_id' => 1,
'title' => 'Test Entry'
)
array (
'entry_id' => 1,
'author_id' => 2
I'm trying to achieve an array structure like:
array (
'entry_id' => 1,
'author_id' => 2,
'title' => 'Test Entry'
)
Currently, I've solved the problem by looping through each array and formatting the array the way I want, but I think this is a bit of a memory hog.
$entriesArray = array();
foreach ($entryNames as $names) {
foreach ($entryAuthors as $authors) {
if ($names['entry_id'] === $authors['entry_id']) {
$entriesArray[] = array(
'id' => $names['entry_id'],
'title' => $names['title'],
'author_id' => $authors['author_id']
);
}
}
}
I'd like to know is there an easier, less memory intensive method of doing this?
Is it possible you can do a JOIN in the SQL used to retrieve the information from the database rather than fetching the data in multiple queries? It would be much faster and neater to do it at the database level.
Depending on your database structure you may want to use something similar to
SELECT entry_id, title, author_id
FROM exp_weblog_data
INNER JOIN exp_weblog_titles
ON exp_weblog_data.entry_id = exp_weblog_titles.entry_id
WHERE field_id_53 = "%s" AND WHERE entry_id IN ("%s")
Wikipedia has a bit on each type of join
Otherwise the best option may be to restructure the first array so that it is a map of the entry_id to the title
So:
array(
array(
'entry_id' => 1,
'title' => 'Test Entry 1',
),
array(
'entry_id' => 3,
'title' => 'Test Entry 2',
),
)
Would become:
array(
1 => 'Test Entry 1',
3 => 'Test Entry 2',
)
Which would mean the code required to merge the arrays is simplified to this:
$entriesArray = array();
foreach ($entryAuthors as $authors) {
$entriesArray[] = array(
'id' => $authors['entry_id'],
'title' => $entryNames[$authors['entry_id']],
'author_id' => $authors['author_id']
);
}
I've rearranged some of my code to allow for a single SQL query, which looks like:
$sql = sprintf('SELECT DISTINCT wd.field_id_5, wd.entry_id, mb.email, mb.screen_name
FROM `exp_weblog_data` wd
INNER JOIN `exp_weblog_titles` wt
ON wt.entry_id=wd.entry_id
INNER JOIN `exp_members` mb
ON mb.member_id=wt.author_id
WHERE mb.member_id IN ("%s")
AND wd.entry_id IN ("%s")',
join('","', array_unique($authors)),
join('","', array_unique($ids))
);
This solves my problem quite nicely, even though I'm making another SQL call. Thanks for trying.
In response to your comment on Yacoby's post, will this SQL not give the output you are after?
SELECT exp_weblog_data.entry_id, exp_weblog_data.field_id_5 AS title_ie, exp_weblog_titles.author_id
FROM exp_weblog_data LEFT JOIN exp_weblog_titles
ON exp_weblog_data.entry_id = exp_weblog_titles.entry_id
WHERE exp_weblog_data.field_id_53 = "%S"
Every entry in exp_weblog_data where field_id_53 = "%S" will be joined with any matching authors in exp_weblog_titles, if a an entry has more than one author, two or more rows will be returned.
see http://php.net/manual/en/function.array-merge.php

Categories