I have 2 tables; user and user_info with their models User and UserInfo. user holds id, expire and user_info_id, while user_info holds id and username.
I'd like to get all users where expire is greater or less than $now or null, and I'd like to sort the list by username. This last part is where my problem lies.
What I currenty have;
$usersQ = User::where ('expire', '>', $now)->orWhereNull ('expire');
$usersCount = $usersQ->count ();
$users = $usersQ->get ();
I have tried;
$usersQ = User::with ('user_info')->join ('user_info', 'user.user_info_id', 'user_info.id')->where ('expire', '>', $now)->orWhereNull ('expire')->orderBy ('username');
Unfortunately, this didn't work (SQL syntax error).
Note: I do actually need User as my main entity rather than UserInfo because I need other information from User in my View.
Basically;
SELECT user.*, user_info.username, user_info.fname, user_info.lname
FROM user
INNER JOIN user_info ON user.user_info_id = user_info.id
ORDER BY user_info.username ASC;
And then I'd be able to get access to everything from user and those 3 fields from user_info. How on earth do I do that with Laravel?
$users = User::with('user_info')->where('expire', '>', $now)
->orWhereNull('expire')->get();
$usercount = $users->count();
Eloquent Relationships
Eager Loading
Ah, damn. Seems I got the order wrong.
$usersQ = User::join ('user_info', 'user_info.id', '=', 'user.user_info_id')->orderBy ($order)->where ('expire', '>', $now)->orWhereNull ('expire');
$usersCount = $usersQ->count ();
$users = $usersQ->get ();
This is working just fine, except for the bug in Laravel (yes, it's a bug in my opinion) that overwrites the value of any field with a duplicate name. Would be great if anyone knew a fix for that... Joins are pretty much useless if your id gets overwritten.
Related
Can someone identify why this multi-table join is not accepted? When I bring in the third table, it then fails with invalid table alias. I am not seeing what is wrong:
This works (two table):
select
a.ri as `R_ID`
,oc3.name as `RET`
,a.rch as `RC`
from dev.sl a join dev.codes oc3
on (a.pk_business = oc3.pk_business
and a.pk_data_source = oc3.pk_data_source
and a.pk_frequency = oc3.pk_frequency
and oc3.pk_data_state = '123'
and oc3.code = a.ri and oc3.codeset = 'xyz')
Then add a third table and it fails:
(Three table):
select
a.ri as `R_ID`
,oc3.name as `RET`
,a.rch as `RC`
from dev.sl a join dev.codes oc3
on (a.pk_business = oc3.pk_business
and a.pk_data_source = oc3.pk_data_source
and a.pk_frequency = oc3.pk_frequency
and oc3.pk_data_state = '123'
and oc3.code = a.ri and oc3.codeset = 'xyz') join dev.items b
on (b.pk_business = a.pk_business
and b.pk_data_source = a.pk_data_source
and b.pk_frequency = a.pk_frequency
and b.pk_data_state = '123'
and a.ii = b.item_id
and a.cc = b.country_code)
SemanticException [Error 10009]: Line 1:2920 Invalid table alias 'a':
I have an update - it seems that this was caused by having one table created as an updatable table (TBLPROPERTIES ('transactional'='true')), and one without, and with my session settings of:
SET hive.txn.manager=org.apache.hadoop.hive.ql.lockmgr.DbTxnManager;
SET hive.support.concurrency=true;
SET hive.enforce.bucketing=true;
SET hive.exec.dynamic.partition.mode=nonstrict;
This caused the problem. On another session without the settings AND repointing to an identical table "a" created as a non-ACID type table, the multi-table join worked fine. I don't know enough about HIVE to know why - I suspect that a transactional and non-transactional table cannot be joined in the same "transaction" (select statement).
One more update - It may not be due to the transactional table. With additional testing, I now also see it happens with non-transactional tables as well. It seems that the three table join works when I execute it from a putty session directly on the server, but when I use SQL Developer, it will produce the aforementioned error. It appears to be an issue with SQL Developer, but why still is unknown.
I have a many-to-many relationship so I have three tables (administrators, offices and the intermediate table):
I need to do a very simple innerjoin of the three tables to show lastnames and their offices with Active Record. I tried but I couldn't.
This is what I tried:
$admins = Admins::find()
->joinWith(Oficinas::tableName())
->all();
echo '<pre>';
print_r($admins);
echo '</pre>';
die();
Also I would like to know how to show the SQL query so it can help me to find a solution.
You need to specify the relation name for joinWith() rather than table names. Since there isn't any info on the relation names I will use simple innerJoin using table names as per your requirements to display the last name and the office name for the admins.
Admins::find()
->alias('a')
->select('a.lastnameadm, o.nombreofi')
->innerJoin('admin_oficinas ao','ao.idadm = a.idadm')
->innerJoin('oficinas o','o.idofi = ao.idofi')
->all();
Try this:
TABLE_NAME_1::find()
->join('inner join',
'table_name_2',
'table_name_2.column = table_name_1.column'
);
->join('inner join',
'table_name_3',
'table_name_3.column = table_name_2.column'
)->all();
but if you can also use Via like the following example:
public function getClassschedule()
{
return $this->hasOne(TABLE_NAME_2::className(), ['table_name_1.column' => 'table_name_2.column'])
->via('tableName3');
}
when excuded, the results should be obtained like the previous example. so there's no need to repeated relations. full documentation can be seen here :
https://www.yiiframework.com/doc/guide/2.0/en/db-active-record
I have this, but it loads each and every ebay row individually, generating thousands of SQL statements:
$products = \app\models\Product::find()
->joinWith('ebay', false, 'inner join')
->indexBy(function($row){return $row->ebay->epid;})
->all();
I tried this, but it gave an error: 'Getting unknown property: app\models\Product::ebay.epid'
$products = \app\models\Product::find()
->joinWith('ebay', false, 'inner join')
->indexBy('ebay.epid')
->all();
Setting eager loading = true doesn't help either. It still loads each row individually then loads them again at the end.
How can I efficiently join a table in Yii and index by a value in the joined table?
You won't be able to do it with indexBy. However, ArrayHelper::index can index an array on a related model field. So here's how it can be done:
$products = \app\models\Product::find()
->with('ebay')
->all();
ArrayHelper::index($products, 'ebay.epid');
The code will run two queries, one to get all products, one to get all related ebay products. Then the array will be indexed with no DB queries at all.
I ended up doing it manually for a subset of the ids and it only uses 2 queries. I'd still be interested in the indexBy though.
$products = Product::find()->joinWith('ebay', true, 'inner join')->where(['ebay.epid' => $productIds])->all();
$ebayProducts = array();
foreach ($products as $p) {
$ebayProducts[$p->ebay->epid] = $p;
}
If you want index by relation recods via joinWith() or with() results you can use following:
->with(['relationName' => function($q) {
$q->indexBy('field_name');
}])
I'm trying to replicate a join like so using the laravel query builder:
LEFT JOIN content_userdata
ON content_id = content.id
AND user_id = $user_id
I have discovered that I can do additional "ons" using the following function in my model which extends Eloquent
public function scopeJoinUserData($query, $user_id)
{
return $query->leftJoin('content_userdata', function($join)
{
$join->on('content_userdata_content_id', '=', 'content.content_id')->on('content_userdata_user_id', '=', 10);
});
}
But this creates two problems. Firstly I cannot get the $user_id variable into the function and secondly even if I hardcode it for testing purposes as I have done above (to int "10") Laravel encases it in ` meaning that it is interpreted as a column name when it shouldn't be, like so:
left join `content_userdata`
on `content_id` = `content`.`id`
and `user_id` = `10`
So now I have two problems.
I cannot get the $user_id into the join function when using query scopes
Even if I could I cannot send a variable to the join since it always interprets it as a column name
Why would I want to do this?
I realise one response may be to place it in a where. However I am trying to do it this way as the join may not necessarily return any results (hence the left join), since the content_userdata table contains things like a users rating for a piece of content. If I use a where then results with nothing in the content_userdata table will not be returned, where as if I can put it in the join then they will be returned due to the left join.
Is there anyway to achieve this in Laravel and if not what are the alternatives, obviously completely changing ditching Laravel is over the top but the only alternative I can think of is to get the userdata in a separate query.
You need to pass the variable to the closure using the use keyword - which imports the variable into scope. Example:
public function scopeJoinUserData($query, $user_id)
{
return $query->leftJoin('content_userdata', function($join) use ($user_id)
{
$join->on('content_userdata_content_id', '=', 'content.content_id')
->on('content_userdata_user_id', '=', DB::raw($user_id));
});
}
This is a PHP syntax related issue and not a Laravel limitation!
In the accepted answer, just adding quotes around the DB::raw part of the query will not fully protect it from sql injection. Just pass some quotes in your user_id and see. To parameterize you can do something like this:
public function scopeJoinUserData($query, $user_id)
{
return $query->leftJoin('content_userdata', function($join)
{
$join->on('content_userdata_content_id', '=', 'content.content_id')
->on('content_userdata_user_id', '=', DB::raw('?'));
}
->setBindings(array_merge($query->getBindings(),array($user_id)));
}
Notice in this example that you don't have to pass the variable into the closure. Alternatively you could try and write this part completely raw.
UPDATE: Taylor added joinWhere, leftJoinWhere... if you have a function join just use ->where and ->orWhere from within the Closure.
I managed to fix this myself, there's a note at the bottom of why it's not completely optimal but here's how to do it anyway:
public function scopeJoinUserData($query, $user_id)
{
return $query->leftJoin('content_userdata', function($join) use ($user_id)
{
$join->on('content_userdata_content_id', '=', 'content.content_id')->on('content_userdata_user_id', '=', DB::raw('"'.$user_id.'"'));
});
}
Note the use of "use ($user_id)" as suggested by #Half Crazed.
DB::raw() is used to wrap $user_id in quotes even though it's an integer and not a string. This will stop Laravel automatically using ` which makes it MySQL interpret it as a column name.
Performance: One thing to note is that MySQL queries can be considerably faster when using an integer rather than a string and will interpret it as a string if it's wrapped in quotes. I'm not worrying about that for now, but I figured I should mention it if others are using this as a solution.
Why dont you just use relationships? That is the whole point of an ORM like Eloquent?
Something like this;
class User extends Eloquent {
public function userdata()
{
return $this->hasOne('Userdata');
}
}
$result= User::find(1)->userdata();
edit to show you can do whatever you want with relationships
Option 1:
$place = new Place;
$array = $place->with(array('users' => function($query)
{
$query->where('user_id', $user_id);
}))->get();
var_dump($array->toArray());
or Option 2:
$place = new Place;
$array = $place->with('users')->where('user_id', $user_id)->get();
var_dump($array->toArray());
Both give different results - but you get the idea
Your first problem: You should use PHP syntax for closure as the answer of Half.
About your second problem, I think the part AND user_id = $user_id of the query does not belong to a JOIN clause but a WHERE clause because it just depends on one table, not both in this joining relationship. I think you should use a subquery like this:
public function scopeJoinUserData($query, $user_id)
{
return $query->leftJoin(\DB:raw("(SELECT * FROM content_userdata WHERE user_id = {$user_id}) AS t"), function($join)
{
$join->on('t.content_id', '=', 'content.content_id');
});
}
However, as you see, let be sure that the $user_id variable is safe because we use \DB:raw method.
In my Seeder, I do the below, the order of which they land in the DB seems to be different now and then! I need the order to be consistent because I refer to the IDs using static variables stored. Why is the order different? Can I make it consistent? Thank you.
List<BadgeGroup> BGs = new List<BadgeGroup>();
BadgeGroup Unclassified = new BadgeGroup() { Description = "Unclassified" };
BGs.Add(Unclassified);
BadgeGroup NumVotesOnPost = new BadgeGroup() { Description = "Number of votes on a post" };
BGs.Add(NumVotesOnPost);
foreach (BadgeGroup BG in BGs)
db.BadgeGroups.AddOrUpdate(BG);
db.SaveChanges();
When you save a list of objects, EF doesn't guarantee that it'll be saved in that order. After you save, you can iterate through the objects to get their ids.