Joining table not directly related to main table in cakephp - sql

I have three tables whose structure is is similar to below :
employees offices postings
________ ________ ___________
id id employees_id
name name offices_id
So I want to know how can I get the office name from Employee model. Putting office within $hasOne array shows Unknown column 'office.employees_id' in 'on clause'. What should I do to get the office name in the results ?

If you want to stick to your database, you might use has and belongs to many relation.
In employee.php:
public $hasAndBelongsToMany = [
'Office'=> [
'foreign_key' => 'employees_id',
'joinTable' => 'postings',
'associationForeignKey' => 'offices_id',
]
];
Then you can get office from employee model.
What I really want to suggest is that put a column office_id in employees table, and put
$public $belongsTo = ['office'];
in your employee model.
PS: Brackets [] is supported in php 5.4 or newer , if you are using php 5.3 or lower, you may want to replace it with array().

Related

Doctrine ResultSetMappingBuilder Join Same Entity Two times

I want to implement a Join on Same entity 2 times on 2 different associated fields with 2 tables.
but it seems addJoinedEntityFromClassMetadata () Does not support it?
For e.g. I want to specify same Entity class parameter 2 times with different alias.
addJoinedEntityFromClassMetadata("Entity\User","u1".....) and addJoinedEntityFromClassMetadata("Entity\User","u2".....) and
Please suggest if it is supported.
of course it's supported .
You have to set different aliases to your entities, et specify aliases for duplicated column like this :
addJoinedEntityFromClassMetadata('Entity\User', 'user1', OKey, array ( 'id' => 'user1id' ));
addJoinedEntityFromClassMetadata('Entity\User', 'user2', OKey, array ( 'id' => 'user2id' ));
* Okey is the name of the relationship column on your root entity

Rails ActiveRecord Join Query With conditions

I have following SQL Query:
SELECT campaigns.* , campaign_countries.points, offers.image
FROM campaigns
JOIN campaign_countries ON campaigns.id = campaign_countries.campaign_id
JOIN countries ON campaign_countries.country_id = countries.id
JOIN offers ON campaigns.offer_id = offers.id
WHERE countries.code = 'US'
This works perfectly well. I want its rails active record version some thing like:
Campaign.includes(campaign_countries: :country).where(countries: {code: "US"})
Above code runs more or less correct query (did not try to include offers table), issue is returned result is collection of Campaign objects so obviously it does not include Points
My tables are:
campaigns --HAS_MANY--< campaign_countries --BELONGS_TO--< countries
campaigns --BELONGS_TO--> offers
Any suggestions to write AR version of this SQL? I don't want to use SQL statement in my code.
I some how got this working without SQL but surely its poor man's solution:
in my controller I have:
campaigns = Campaign.includes(campaign_countries: :country).where(countries: {code: country.to_s})
render :json => campaigns.to_json(:country => country)
in campaign model:
def points_for_country country
CampaignCountry.joins(:campaign, :country).where(countries: {code: country}, campaigns: {id: self.id}).first
end
def as_json options={}
json = {
id: id,
cid: cid,
name: name,
offer: offer,
points_details: options[:country] ? points_for_country(options[:country]) : ""
}
end
and in campaign_countries model:
def as_json options={}
json = {
face_value: face_value,
actual_value: actual_value,
points: points
}
end
Why this is not good solution? because it invokes too many queries:
1. It invokes query when first join is performed to get list of campaigns specific to country
2. For each campaign found in first query it will invoke one more query on campaign_countries table to get Points for that campaign and country.
This is bad, Bad and BAD solution. Any suggestions to improve this?
If You have campaign, You can use campaign.campaign_countries to get associated campaign_countries and just get points from them.
> campaign.campaign_countries.map(&:points)
=> [1,2,3,4,5]
Similarly You will be able to get image from offers relation.
EDIT:
Ok, I guess now I know what's going on. You can use joins with select to get object with attached fields from join tables.
cs = Campaign.joins(campaign_countries: :country).joins(:offers).select('campaigns.*, campaign_countries.points, offers.image').where(countries: {code: "US"})
You can than reference additional fields by their name on Campaign object
cs.first.points
cs.first.image
But be sure, that additional column names do not overlap with some primary table fields or object methods.
EDIT 2:
After some more research I came to conclusion that my first version was actually correct for this case. I will use my own console as example.
> u = User.includes(:orders => :cart).where(:carts => { :id => [5168, 5167] }).first
> u.orders.length # no query is performed
=> 2
> u.orders.count # count query is performed
=> 5
So when You use includes with condition on country, in campaign_countries are stored only campaign_countries that fulfill Your condition.
Try this:
Campaign.joins( [{ :campaign_countries => :countries}, :offers]).where('`countries`.`code` = ?', "US")

YII: Dropdownlist with relation

DB table:
Mcourse(Master course )-> contains Course Names
Lcourse(Linked
Course- courses belongs to a college) -> contains foreign key
Mcourse_Id. & college Id.
Nw the problem is
I want to display list of courses available in a college using dropdownlist.
So sql query is:
select Lcourse_Id, Mcourse_Name* from Lcourse inner join Mcourse on Lcourse_Mcourse_Id=Mcourse Id..
*Id & value pair for dropdownlist
I could do this usin createCommand..Its working pretty fine. But i cant do this usin Relations ..Help me.
Let's imagine for a minute that your Mcourse table is called courses and model for that table is called Courses, your Lcourse table is called courses_colleges and your colleges table is colleges and model for that table is Colleges
Now, You should have Courses model with relations:
public function relations() {
return array(
'colleges' => array(self::MANY_MANY, 'Colleges', 'courses_colleges(course_id, college_id)')
);
}
Your Colleges model should have similar relations:
public function relations() {
return array(
'courses' => array(self::MANY_MANY, 'Courses', 'courses_colleges(college_id, course_id)')
);
}
Now if you want to print out a dropdown with all courses available for a certain college. In your controller action method get the model of that college including its courses:
public function actionShow() {
$id = 1; // We set just some sample id. You could get it from request ofc.
$college = Colleges::model()->with('courses')->findByPk($id);
$this->render('show', array('college'=>$college));
}
Now in your view print out this:
echo CHtml::dropDownList('courses', '', CHtml::listData($college->courses, 'id', 'name'));
Where 'id' and 'name' are columns of your Courses model.
Something like that.
The error is in the listData() function in your view, specifically that you don't have a mc_Id in your Lcourse model.
As you haven't clarified the model that each of those relationships are assigned with, it's impossible to guess what you should substitute for 'mc_Id' in your view - check your Lcourse model to determine the proper column name.

How can I retrieve the newest record in each group in a DBIx::Class resultset search?

I'm using group_by in a DBIx::Class resultset search. The result returned for each group is always the row in the group with the lowest id (i.e the oldest row in the group). I'm looking for a way to get the row with the highest id (i.e. the newest row in the group) instead.
The problem is fundamentally the same as this:
Retrieving the last record in each group
...except that I'm using DBIx::Class not raw SQL.
To put the question in context:
I have a table of music reviews
review
------
id
artist_id
album_id
pub_date
...other_columns...
There can be multiple reviews for any given artist_id/album_id.
I want the most recent reviews, in descending date order, with no more than one review per artist_id/album_id.
I tried to do this using:
$schema->resultset('Review')->search(
undef,
{
group_by => [ qw/ artist_id album_id / ],
order_by => { -desc => 'pub_date' },
}
);
This nearly works, but returns the oldest review in each group instead of the newest.
How can I get the newest?
For this to work you are relying on broken database behaviour. You should not be able to select columns from a table when you use group by unless they use an aggregate function (min, max etc.) or are specified in the group by clause.
In MySQL, even the manual admits this is wrong - though it supports it.
What I think you need to do is get the latest dates of the reviews, with max(pub_date):
my $dates = $schema->resultset('Review')->search({},
{
select => ['artist_id', 'album_id', {max => 'pub_date'}],
as => [ qw(artist_id album_id recent_pub_date) ],
group_by => [ qw(artist_id album_id) ],
}
);
Then loop through to get the review:
while (my $review_date = $dates->next) {
my $review = $schema->resultset('Review')->search({
artist_id => $review_date->artist_id,
album_id => $review_date->album_id,
pub_date => $review_date->get_column('recent_pub_date'),
})->first;
}
Yep - it's more queries but it makes sense - what if two reviews are on the same date - how should the DB know which one to return in the select statement?

Bug in Kohana 3 ORM?

Sorry to ask all these questions about Kohana. They usually get ignored. I think I just found a bug. I'm making a join between two tables that are not directly related.
$results = ORM::factory('foo')->join("bar")->on("foo.foreign_id", "=", "bar.id");
This generates a query that does not resolve the table names explicitly:
SELECT * FROM `foo` JOIN `bar` ON (`foo`.`foreign_id` = `bar`.`id`)
Which gives (in phpMyAdmin) a table that looks like this:
id time foreign_id blah_int id baz
4 1291851245 3 0 3 52501504
Notice there are two id columns, one for the foo table and one for bar. This is a real problem. Because now, in my results, if I loop through...
foreach ($results as $result) {
echo $result->id; // prints 3!!!
}
Because my results should be foo objects, I expect to get an id of 4, but it's giving me 3 because of the join. Is this a bug in the ORM library? Should I be using a different method to restrict my results from the query? I really don't want to do two separate queries where I load all the bars id's, and then load my foos that way, but it looks like I have to.
You have to use the Database object to build raw queries, not ORM, like this:
$results = DB::select()->from('foo')->join('bar')->on("foo.foreign_id", "=", "bar.id")->execute();
You will need to specific some column aliases however to make your query work unless you use ORM as it was intended.
Using ORM
If you want to use ORM, you need to define the relationships in your model. You mention that they share a relationship with another table so in your case you could use a has many through relationship like this:
protected $_has_many = array(
'bars' => array('model' => 'bar', 'through' => 'other_table', 'foreign_key' => 'foreign_id'),
);
Although your example as given suggests that a straight has_many relationship would work:
protected $_has_many = array(
'bars' => array('model' => 'bar','foreign_key' => 'foreign_id'),
);
This would allow you to access all of the bars using a statement like
$bars = $results->bars->find_all();
foreach($bars as $bar)
{
echo $bar->id; // should echo 4, assuming one record in bars with id 4
}
The Kohana 3.1 ORM Reference Guide is good place to start if you want to learn more about ORM and relationships
Using the Kohana database object and query builder
If you prefer ad hoc queries and are doing joins using the query builder you will likely have colliding column names regardless if you are using Kohana or just raw queries (pop "SELECT * FROM foo JOIN bar ON (foo.foreign_id = bar.id)" into MySQL and you will get the exact same result).
Kohana, just like MySQL allows you to define column aliases for precisely this reason. (See here for more information)
Rewrite your query as follows:
$results = DB::select('id', 'time', 'foreign_id', array('bar.id', 'bar_id'), 'baz')->from('foo')->join("bar")->on("foo.foreign_id", "=", "bar.id")->execute();
This will return:
id time foreign_id blah_int bar_id baz
4 1291851245 3 0 3 52501504