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
Related
Hello i'm trying to get all users which have had payments at least 6 months over the given period (which must be a year). I've written SQL which works fine, but i have difficulties trying to convert it to nhibernate.
SQL:
SELECT COUNT(UserId) AS paidMonthsCount, UserId FROM (
SELECT DISTINCT UserId,
YEAR(PayDate) as _year,
MONTH(PayDate) as _month
FROM Payments
WHERE PayDate >= '2014-04-02T00:00:00' AND PayDate < '2015-04-02T23:59:00'
)result GROUP BY result.UserId
i have converted inner SQL:
var subQuery = Session.QueryOver(() => paymentAlias)
.SelectList(list => list
.Select(Projections.Distinct(Projections.Property<VelferdPayment>(p => p.Client.Id)).WithAlias(() => userWithHelp.Id))
.Select(p => p.AssignmentYear).WithAlias(() => userWithHelp.AssignmentDate)
)
.WhereRestrictionOn(p => p.AssignmentDate)
.IsBetween(parameters.FromDate)
.And(parameters.ToDate);
which selects the distinct part and i have the other part which is selecting from result:
var query = Session.QueryOver(() => userWithHelp).
SelectList(list => list
.SelectCount(p=> p.Id).WithAlias(()=> userWithHelpCount.Count)
.SelectGroup(p => p.Id).WithAlias(() => userWithHelpCount.Id)
)
.TransformUsing(Transformers.AliasToBean<UserWithHelpCount>())
.List<UserWithHelpCount>();
How can i queryover the subQuery results or is it possible to write single request to SQL. Working for a long time please help.
In general, with NHibernate we can only (or mainly) query Entities, not TABLES. Other words, we firstly map tables or views or even some <subselect>s into entities. The below mapping of the User (C# object User)
<class name="user" table="[dbo].[user_table]" ...
Will allow us to create query over C# User.
session.QueryOver<User>()...
Behind the scene it will generate FROM clause, which will contain the content of table attribute, i.e. FROM [dbo].[user_table]
That's it. There is no other way how to set the generated FROM clause. Just by mapping.
But there is a way which allow us to use existing ADO.NET connection to create custom query and even convert its result to some entity, or DTO. It is CreateSQLQuery() API:
17.1.5. Returning non-managed entities
It is possible to apply an IResultTransformer to native sql queries. Allowing it to e.g. return non-managed entities.
sess.CreateSQLQuery("SELECT NAME, BIRTHDATE FROM CATS")
.SetResultTransformer(Transformers.AliasToBean(typeof(CatDTO)))
This query specified:
the SQL query string
a result transformer
The above query will return a list of CatDTO which has been instantiated and injected the values of NAME and BIRTHNAME into its corresponding properties or fields.
So, we can use native SQL SELECT statements to get any results. We can even create some custom DTO and let NHibernate to transform result into them...
What I have already and what I'm trying to do:
I have a website that lets users make posts advertising themselves to other users. Now, I'm trying to implement a filter for these posts but filter by values in the users table, posts table and three others. This is where I'm running into some trouble.
Code that works, as is, no filter:
$posts = Post::with('user','lookingfors','playstyles', 'postcomments')->orderBy('id', 'DESC')->paginate(10);
return View::make('posts/index', compact('posts'));
This is what I've tried:
$posts = User::leftjoin('posts', function($join)use($region){
$join->on('users.id', '=', 'posts.user_id');
$join->on('playstyle_post.post_id', '=', 'posts.id'); // join pivot, then join pivot to playstyles
$join->on('playstyle_post.playstyle_id', '=', 'playstyles.id');
//$join->on;
})->where('users.region_id', 'like', $region)->get();
This one keeps bringing up this error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column
'playstyle_post.post_id' in 'on clause' (SQL: select * from users
left join posts on users.id = posts.user_id and
playstyle_post.post_id = posts.id and
playstyle_post.playstyle_id = playstyles.id where
users.region_id like ?) (Bindings: array ( 0 => 6, ))
Also:
$posts = Post::with('lookingfors', 'playstyles', 'postcomments')
->leftjoin('users', 'posts.user_id', '=', 'users.id')
//->join('playstyle_post', 'posts.id', '=', 'playstyle_post.post_id')
->paginate(10);
This one is odd ( well for me ), when 'leftjoin' is commented out obviously it works just as before, but when I have it as it is now it displays the view as it should and even pulls all the right values from the users table but does not display any of the information thats being requested through the 'with'.
I've tried a couple other things in the last two weeks, but I've already thrown them out when they didn't work. This has been kicking my ass for the better half of a month and I can't wrap my head around why I can't get it to work.
So really, my trouble right now is just trying to join the tables and returning it to the view in an eloquent fashion so I can keep treating it the same. The filter itself will be a problem of its own, but getting the joins to work has proved to be the hard part.
Instead of passing a bunch of relationship names to the with() function, try passing an array, setting keys as the names of the relationships and the values as callback functions and pass in the query for actual editing. Also will have to use use ($region) where needed. Hopefully you get the idea.
I put in some examples for adding filters on each relationship. Because you are working locally with each one, you shouldn't have to worry about prefixing them with table names or aliases.
$posts = Post::with(array(
'user' => function($query) use ($region)
{
// User constraints here
$query->where('status', 'A');
$query->where('deleted', '0');
},
'lookingfors' => function($query)
{
// lookingfors constraints here
$query->where('name', 'somelookingforname');
},
'playstyles' => function($query)
{
// playstles constraints here
$query->where('name', 'someplaystylesname');
},
'postcomments' => function($query)
{
// Some postcomments constraints here
$query->where('name', 'somepostcommentsname');
}))
->orderBy('id', 'DESC')
->paginate(10);
Looking at your attempt at left joins though, I don't know if these relationships are correct. My example assumes all the relationships can relate directly back to the user table. Otherwise, you will need to change the keys on the array to match how the relationships should be.
For example, if trying to relate to the playstyles table, but the relationship between playstyles and posts doesn't exist because you need to go through the users table first, you would just change 'playstyles' to 'users.playstyles'.
If you post your migrations, I could help you further.
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
I have three tables as follows
user(username address)
profile(fname,lname,mobile)
details(performance,activity)
I want all information from the above three tables in one query
i.e. I want to make join of three tables for one common id field
I have the following query which retrives only two table fields
#details=User.find(:all,:joins => :profile,:select => "*")
How to do it for all three tables ???
Here's how this query would look:
#details = User.select('*').joins(:profile, :details).all
I'm not convinced this is actually a good way to do anything, but it should work.
This worked for me:
value_variable = 'hello world'
Member.joins(:person => [:workplace => [:business]]).where("businesses.name LIKE :value", value: "%#{value_variable}%")
NOTE: Tested on rails 3.2, 4.x, 5.x
I have a database table called Event which in CakePHP has its relationships coded to like so:
var $belongsTo = array('Sport');
var $hasOne = array('Result', 'Point', 'Writeup', 'Timetable', 'Photo');
Now am doing a query and only want to pull out Sport, Point, and Timetable
Which would result in me retrieving Sports, Events, Points, and Timetable.
Reason for not pulling everything is due the results having 17000+ rows.
Is there a way to only select those tables using:
$this->Event->find('all');
I have had a look at the API but can't see how its done.
You should set recursive to -1 in your app_model and only pull the things you require. never use recursive of 2 and http://book.cakephp.org/view/1323/Containable is awesome.
just $this->Event->find('all', array('contain' => array()));
if you do the trick of recursive as -1 in app_model, this is not needed, if would just be find('all') like you have