I'm hoping somebody can clear up this issue I am having. None of the other answers on SO seemed to help me out for some reason.
I have 2 tables with a HABTM relationship. Publications have many authors, and authors have many publications. In my case, I am attempting to output a list of all of the publications in the database, along with their corresponding authors.
I have the following tables:
publications:
id title
0 TestPublication
authors:
id firstname lastname middle
0 John Doe A
authors_publications:
id author_id publication_id
0 0 0
The 'id' column of each table is set as the primary key.
My Publication model looks like:
class Publication extends AppModel {
var $name = 'Publication';
var $hasAndBelongsToMany = array(
'Author'=>array(
'className'=>'Author'
)
);
}
And finally, the PublicationsController has the following function:
function index() {
$publications = $this->Publication->find('all');
$this->set('publications', $publications);
}
Here is what publications now contains:
Array ( [0] => Array ( [Publication] => Array ( [id] => 0 [title] => TestPublica [type_id] => 1 ) [Author] => Array ( ) ) )
Why is this? I am expecting (perhaps that is my problem...) that the author John Doe should be present in the Author array. If it should be, where am I going wrong? Do I need a bindModel call somewhere in there?
Or...is the code actually executing the way it should and my expectations are incorrect? If so, how would I return a list of all of the publications along with all of their authors?
Thanks for your time!
I believe I found the problem. I was manually inserting records into the DB for testing purposes. I set the primary key id's on the first record of each table to zero. SQL DBs typically (or always?) start the first record with a primary key index of 1, not 0. I'm not sure WHY cake did not work with this, but changing the id's to 1 solved the problem. Weird...
Related
I have a table that contains an unknown number of elements. These represent business sectors.
Array ([0] => 1 [1] => 3 [2] => 6 [3] => 7)
In this case this activity is linked to topics 1, 3, 6 and 7.
I Have some members who have indicated their fields of interest when they register. Interest fields correspond to sectors of activities.
When a new activity is announced, I would like to send an email to members who have checked at least one common interest with the activity. But I have no idea how to retrieve the email of all those members().
In the database the interests of members is stored like this: 246
The first member would be interested in sectors 2, 4 and 6.
I know how to do if there is only one business sector for the activity (example if value = 1).
function readMemberInterest($value) {
$datas = $this->db->select("email")
->from($this->table)
->like('interet',$value,'both');
return $datas;
}
But I do not know where and how to place my LOOP and my OR (is for example value = Array ([0] => 1 1 => 3 [2] => 6 [3] => 7) ).
Briefly, I want to retrieve email members who have at least one common interest with activity. Any tips?
I hope to be clear, my English is not so great ...
Thanks
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'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
Let's say I have two tables, people and families.
families has two fields - id and name. The name field contains the family surname.
people has three fields - id, family_id and name - The family_id is the id of the family that that person belongs to. The name field is that person's first name.
It's basically a one to many relationship with one family having many people.
I want to get a lists of name sets, ordered by the highest occurrence of the largest set of names across families.
That probably doesn't make much sense...
To explain what I want further, we can score each set of names. The 'score' is the array size * number of occurrences across families.
For example, let's say two names, 'John' and 'Jane' both existed in three families - That set's 'score' would be 2*3 = 6.
How could I get an array of sets of names, and the set's 'score', ordered by each set's score?
Sample Result Set (I've put it in a table layout, but this could be a multi-dimensional array in PHP) - Note this is just randomly thought up and doesn't reflect any statistical name data.
names | occurrences | score
Ben, Lucy | 4 | 8
Jane, John | 3 | 6
James, Rosie, Jack | 2 | 6
Charlie, Jane | 2 | 4
Just to clarify, I'm not interested in sets where:
The number of occurrences is 1 (obviously, just one family).
The set size is 1 (just a common name).
I hope I have explained my somewhat complex problem - if anyone needs clarification please say.
OK, got it:
<?php
require_once('query.lib.php');
$db=new database(DB_TYPE,DB_HOST,DB_USER,DB_PASS,DB_MISC);
$qry=new query('set names utf8',$db);
//Base query, this filters out names that are in just one family
$sql='select name, cast(group_concat(family order by family) as char) as famlist, count(*) as num from people group by name having num>0 order by num desc';
$qry=new query($sql,$db);
//$qry->result is something like
/*
Array
(
[name] => Array
(
[0] => cathy
[1] => george
[2] => jack
[3] => john
[4] => jane
[5] => winston
[6] => peter
)
[famlist] => Array
(
[0] => 2,4,5,6,8
[1] => 2,3,4,5,8
[2] => 1,3,5,7,8
[3] => 1,2,3,6,7
[4] => 2,4,7,8
[5] => 1,2,6,8
[6] => 1,3,6
)
[num] => Array
(
[0] => 5
[1] => 5
[2] => 5
[3] => 5
[4] => 4
[5] => 4
[6] => 3
)
)
$qry->rows=7
*/
//Initialize
$names=$qry->result['name'];
$rows=$qry->rows;
$lists=array();
for ($i=0;$i<$rows;$i++) $lists[$i]=explode(',',$qry->result['famlist'][$i]);
//Walk the list and populate pairs - this filters out pairs, that are specific to only one family
$tuples=array();
for ($i=0;$i<$rows;$i++) {
for ($j=$i+1;$j<$rows;$j++) {
$isec=array_intersect($lists[$i],$lists[$j]);
if (sizeof($isec)>1) {
//Every tuple consists of the name-list, the family list, the length and the latest used name
$tuples[]=array($names[$i].'/'.$names[$j],$isec,2,$j);
}
}
}
//Now walk the tuples again rolling forward, until there is nothing left to do
//We do not use a for loop just for style
$i=0;
while ($i<sizeof($tuples)) {
$tuple=$tuples[$i];
//Try to combine this tuple with all later names
for ($j=$tuple[3]+1;$j<$rows;$j++) {
$isec=array_intersect($tuple[1],$lists[$j]);
if (sizeof($isec)>0) $tuples[]=array($tuple[0].'/'.$names[$j],$isec,$tuple[2]+1,$j);
}
$i++;
}
//We have all the tuples, now we just need to extract the info and prepare to sort - some dirty trick here!
$final=array();
while (sizeof($tuples)>0) {
$tuple=array_pop($tuples);
//name list is in $tuple[0]
$list=$tuple[0];
//count is sizeof($tuple[1])
$count=sizeof($tuple[1]);
//length is in $tuple[2]
$final[]=$tuple[2]*$count."\t$count\t$list";
}
//Sorting and output is all that is left
rsort($final);
print_r($final);
?>
I am sorry I just realized I use a query lib that I can't source in here, but from the comment you will easily be able to create the arrays as in the section "Initialize".
Basically what I do is starting with the pairs I keep an array of the families all the names in the current name list belong to, then intersect it with all not-yet tried names.
Will this work?
SELECT
f.name AS 'surname',
GROUP_CONCAT(DISTINCT p.name ORDER BY p.name) AS 'names',
COUNT(DISTINCT p.name) AS 'distinct_names',
COUNT(p.id) AS 'occurrences',
COUNT(DISTINCT p.name) * COUNT(p.id) AS 'score'
FROM
families f
LEFT JOIN people p ON ( f.id = p.family_id )
GROUP BY
f.id
ORDER BY
f.name
I'm trying to edit a record in the database, but the script gives me the following error:
Fatal error: Call to a member function getColumnType() on a non-object in G:\wamp\www\a11\a\cake\libs\model\model.php on line 980
The strange thing is, if I remove id from the form (so it creates a new record) it doesn't complain. That means that there's something wrong with the id column.
But what?
Just before the save I dump the array that is going to be saved:
Array
(
[Process] => Array
(
[id] => 5
[oobject] => 1
[oproperty] => Number
[wproperty] => Payed
[do] => somecode
[active] => 1
[name] => Testing
[changed_user_id] => 3
[selftrigger] => 1
)
)
The saving code now looks like this:
$d = $this->data;
if ($this->Process->save($d)) {
id exists in the database
I appreciate any help!
The id column is labeled "id" (without quotes), correct? If you are using MySQL, make sure that ID type is int (not any variation) and that it is the primary key with auto-increment. These settings are needed for the model class to function properly.
From this error, it looks like you have set the id column to something other than int.