I have two tables in the database, namely "user" and "user_user" which links users to users (many to many)
in user_user there are two columns "user_id_0" and "user_id_1"
I need to get all users where the current logged in user id is equal to either
user_id_0 OR user_id_1.
I can achieve this simply with sql:
select * from user u
inner join user_user uu
on uu. user_id_0 = u.id
or uu.user_id_1 = u.id
[where u.id = 1]
In Yii2 I'm stuck with something like this:
($this would be of type User)
$this->hasMany(User::className(), ['id' => 'user_id_0'])
->viaTable('user_user', ['user_id_0' => 'id'],
function($query) {
$query->orOnCondition(['user_id_1' => 'id']);
});
Now this can't work as hasMany() links to only one foreign key. How do I do something like the SQL above? To join two foreign keys with 'or'.
I this possible or would you suggest a different database design? The goal here is really to enable users have "friends" or "connections" to other users like you would in a social network.
I'm not sure if this works as you expect. I didn't try it out.
IMO the query should look like this (assuming MySQL):
SET #id = 1; // or whatever
SELECT user.*
FROM user
INNER JOIN user_user
ON user.id = user_user.id1 OR user.id = user_user.id2
WHERE user.id != #id AND (user_user.id1 = #id OR user_user.id2 = #id)
GROUP BY user.id;
This could be realized in class User like this:
public function getConnectedPersons() {
return self::find()
->select('user.*')
->innerJoin('user_user', ['or',
'user.id=user_user.user_id_0',
'user.id=user_user.user_id_1'
]
)->where(['and',
['user.id' => $this->id],
['or',
['user_user.user_id_0' => $this->id],
['user_user.user_id_1' => $this->id]
]
]
)->groupBy('user.id');
}
This returns an ActiveQuery object. Append ->all() if you want to get the user array directly.
I hope this works. It is untested. But you may get the idea and give me feedback if anything is wrong.
This might not be the best solution. It doesn't use viaTable() which should also work somehow.
Related
I'm using luman and Database Query Builder to fetch full user info from database.
First, Please Take a lock at my database structure:
I have a table called users and a series of other tables that are related to user groups (Ex: secretaries, patients, doctors and admins) which stores additional information about the users.
Also To determine user access, I have a level column on user table which can have one of this value as enum: 'admin', 'doctor', 'secretary', 'patient'.
So, I want to get this information using one query by join and select.
My training code is something like this:
$userInfo = User::where("userID", $userID)
->limit(1)
->join('[GROUP_TABLE_NAME]', function ($join) {
$join->on('user.userID', '=', '[GROUP_TABLE_NAME]' .'.'.
'[GROUP_NAME]' . 'ID');
})
->get();
The GROUP_NAME comes from level column on user table and the GROUP_TABLE_NAME can be built based on the GROUP_NAME value(Ex: ['secretary' => 'secretaries' , 'patient' => 'patients' , ...]).
Any idea to handle the join structure using laravel query builder?
First you should be aware of the fact that this code architecture is not convenient and not easy to understand for other developers.
SQL
You can achieve your goal by using union and join.
Just convert this query for laravel builder or use it directly with DB::statement: select users.*, infos.info from users left join (( select secretaries.* from secretaries ) UNION (select doctors.* from doctors)) infos ON users.id = infos.user_id where users.id=?.
But
The easiest way to do it is to fetch info in two queries, both indexed and fast: user from users by primary key and then info by indexed field user_id in it. Create Doctorinfo, Admininfo models and correspondent migrations. So user class can be smth like this:
public function getInfo() {
switch($this->level) {
'doctor':
return $this->doctorinfo;
...
}
}
private function doctorinfo() {
$this->hasOne('App\Doctorinfo');
}
Builder
You can use left join to join all sub tables as well. The following builder selects info column.
User::where("userID", $userID)->limit(1)
->leftJoin('patients', 'users.id', '=', 'patients.user_id')
->leftJoin('doctors', 'users.id', '=', 'doctors.user_id')
->leftJoin('admins', 'users.id', '=', 'admins.user_id')
->select('users.*', DB::raw('IF(users.level="admin", admins.info, (IF users.level="doctors", doctors.info, patients.info))'))
I'm working with L5 and elequent
My table structure is..
users
id
other field
auctions
id
other field
lots
id
auction_id
other field
lot_user
lot_id
user_id
I want to find auctions for a user.
How can i do this?
$user = User::find($id);
$auctions = $user->auctions();
I have got an idea to do this with eloquent..
$auctions = $user->lots()
->join('auctions','auctions.id','=','lots.id')
->select(['auctions.*'])
->get();
I'm not sure Eloquent is going to be very efficient here, but you could do something like :
In your User(s) class, you need to define a many-many relationship like :
public function lots()
{
return $this->belongsToMany('App\Lot');
}
In your Lot(s) class, you need to define the inverse of a one-to-many relationship like:
public function auctions()
{
return $this->belongsTo('App\Auction')
}
Then, to get Lots for a user, you'd do something like :
$user->lots();
To get auctions, you'd need to loop over lots and call $lot->auctions() for each one, and then filter by id to get the unique auctions.
This is a case where it would probably be easier to use the DB facade to built a query instead of just trying to use Eloquent.
About DB facade.
Raw query will looks like this:
SELECT * FROM auctions AS a
INNER JOIN lots AS l ON (l.auction_id = a.id)
INNER JOIN lot_user AS lu ON (lu.lot_id = l.id AND lu.user_id = $findUserId)
GROUP BY a.id
And using query-builder you can do it like this:
DB::table('auctions')
->join('lots', 'lots.auction_id', '=', 'auctions.id')
->join('lot_user', function ($join) {
$join->on('lot_user.lot_id', '=', 'lots.id')
->where('lot_user.user_id', '=', $findUserId);
})
->groupBy('auctions.id')
->get();
I have a User and Role entity, with a Many-to-Many relation. In my role table I have a column named visible that stores a boolean value. If a user has a role (at least one amongst others) which is not visible I want to exclude him from the result set.
I can get the related roles in my query and just iterate through them and find out that way, but what I really would like to do is just have my query only return the users with all roles that are visible instead of filtering the query afterwards.
Something like :
public function scopeVisible($query)
{
$query->whereHas('roles', function($q){
// and here i want to find that thing out
})
}
Lets first identify the tables we're working with
user
id - integer
name - string
role
id - integer
visible - bool in the form of SMALLINT 0 or 1
user_role
user_id - integer
role_id - integer
We'll solve it in plain old SQL first. Like this:
SELECT * FROM user
INNER JOIN user_role ON user_role.user_id = user.id
INNER JOIN role ON role.id = user_role.role_id
GROUP BY user.id
HAVING COUNT(user_role.user_id) = SUM(role.visible);
The key here is the HAVING statement. We do a count on the amount of roles a user has and then we do a sum on the visible column. If all roles are visible then the amount of relations a user has to roles will equal the amount of visible roles.
Now to convert this into Laravel speak. In order to do this we'll have to use Laravel's query builder.
$visibleUsers = DB::table('user')
->join('user_role', 'user_role.user_id', '=', 'user.id')
->join('role', 'role.id', '=', 'user_role.role_id')
->groupBy('user.id')
->havingRaw('COUNT(`user_role`.`user_id`) = SUM(`role`.`visible`)')
->get();
I'm not familiar with whereHas but I would try something like this
public function scopeVisible($query)
{
return $query->whereHas('roles', function($q) {
$q->where('visible', '=', true);
});
}
And then use it: $usersVisible = User::visible()->get();
I'm using the Yii framework and have come across an issue whilst building a Friend/Connection style system. I have a standard users table and to store friends I have a friends table with the following layout (the appropriate FKs etc have been set up):
id | userId | friendId | and some other fields...
As the "friend" is mutual - so, if user A is a friend with user B, then user B is a friend with user A - therefore there is only ever one entry per "friendship".
What I'm trying to do is use Yii's relational query to eventually return items which belong to friends. Currently I have the following relation in the User model...
'friends' => array(self::HAS_MANY, 'UserFriend', 'userId'),
However that only returns 'friends' if the user's ID is present in the userId field. If the user's ID is in the friendId field then the "friendship" won't be recognised.
Is there a way to query using "OR" - for example: if user's ID == userId OR user's ID == friendId then return that entry?
In addition, is there a way to get Yii to return the other ID, so if user's ID == userId it will return friendId, else if user's ID == friendId it will return userId?
I'm eventually trying to do something like:
$userModel = User::model()->findByPk(Yii::app()->user->id); // user's model
$userFriends = $userModel->with(items)->friends; // return friends with their items
Hopefully I haven't confused too many! Sorry for the poor explanation
Thanks :)
Follow the instructions here on creating a database command object, and then you can query with an OR condition as follows:
$command->where(array('OR', 'userID = :userID', 'userID = :friendID'), array(':userID' => $userID, ':friendID' => $friendID));
Do you want get all friends of current user of your app?
You can use CDbCriteria and method find(). Something like this:
$criteria = new CDbCriteria;
$criteria->addCondition('id = :userid');
$criteria->addCondition('friendid = :userid', 'OR'); // OR - the operator to join different conditions. Defaults to 'AND'.
$criteria->params = array(':userid' => Yii::app()->user->id);
$userFriends = UserFriend::model()->find($criteria);
But I'm not sure in your User and UserFriend models structure and mutual relations. Have you some relation from UserFriend to User? If so, than you can use something like $userFriends->user0 after code above.
CDbCriteria details
find() method details
I have the following query I want to build using CakePHP. How should I go about this?
SELECT
`Artist`.`id`,
CONCAT_WS(' ', `Person`.`first_name`, `Person`.`last_name`, `Person`.`post_nominal_letters`) AS `name`,
`Portfolio`.`count`
FROM
`people` as `Person`,
`artists` as `Artist`
LEFT OUTER JOIN
(SELECT
`Product`.`artist_id`,
COUNT(DISTINCT `Product`.`id`) AS `count`
FROM
`product_availabilities` AS `ProductAvailability`,
`products` AS `Product`
LEFT OUTER JOIN
`order_details` AS `OrderDetail`
ON
`Product`.`id` = `OrderDetail`.`product_id`
LEFT OUTER JOIN
`orders` AS `Order`
ON
`Order`.`id` = `OrderDetail`.`order_id`
WHERE
`ProductAvailability`.`id` = `Product`.`product_availability_id`
AND
`Product`.`online` = true
AND
(`ProductAvailability`.`name` = 'For sale')
OR
((`ProductAvailability`.`name` = 'Sold') AND (DATEDIFF(now(),`Order`.`order_date`) <= 30))
GROUP BY
`Product`.`artist_id`)
AS
`Portfolio`
ON
`Artist`.`id` = `Portfolio`.`artist_id`
WHERE
`Artist`.`person_id` = `Person`.`id`
AND
`Artist`.`online` = true
GROUP BY
`Artist`.`id`
ORDER BY
`Person`.`last_name`, `Person`.`first_name`;
I think that the model is Artist and It has a belongsTo Relationship with , then you could use the CakePHP ORM on this way and hasMany with Portfolio
first you mus distroy the relation between Artist and Portfolio
$this->Artist->unbindModel(array('hasMany'=>array('Portfolio')));
and then Build the relations
$this->Artist->bindModel(array('hasOne'=>array('Portfolio')));
Finally you must create the other relationsships
$this->Artist->bindModel(array(
'belongsTo'=>array(
'Product'=>array(
'clasName'=>'Product',
'foreignKey'=> false,
'conditions'=>'Product.id = Artist.product_id'
),
'ProductAvaibility'=>array(
'clasName'=>'ProductAvaibility',
'foreignKey'=> false,
'conditions'=>'ProductAvaibility.id = Product.product_avaibility_id'
),
'OrderDetail'=>array(
'clasName'=>'OrderDetail',
'foreignKey'=> false,
'conditions'=>'Product.id = OrderDetail.product_id'
),
'Order'=>array(
'clasName'=>'Order',
'foreignKey'=> false,
'conditions'=>'Order.id = OrderDetail.order_id'
),
)
));
Now, when the relationships are done, you could do your find
$this->Artist->find('all', array(
'conditions'=>array(
'Artist.online'=>true
),
'group'=>array(
'Artist.id'
),
'order'=>array(
'Person.last_name',
'Person.first_name',
)
))
I hope it could be useful for you
You should place it in your model. If you haven't read yet, I suggest you to read about skinny controllers and fat models.
class YourModel extends AppModel {
public function getArtistsAndPortfolioCounts() {
return $this->query("SELECT ... ");
}
}
So in your controller:
class ArtistsControllre extends AppController {
public function yourAction() {
debug($this->YourModel->getArtistsAndPortfolioCounts());
}
}
Do not try to build that query using ORM. It will be a disaster in slow motion.
Instead you should try to do that request as "close to metal" as possible. I am not familiar with CakePHP's API ( as i tend to avoid it like black plague ), but you should be able to create a Model which is not related to ActiveRecord, and most likely have access to anything similar to PDO wrapper. Use that to execute the query and map results to variables.
That said, you might want to examine your query ( you seem to have some compulsive quoting sickness ). One of improvements you could do would be stop using id columns in tables.
This whole setup could really benefit from creation of another column in Artists table : portfolio_size. Which you can update each time it should be changed. Yes , it would de-normalize your table, but it also will make this query trivial and blazing fast, with minor costs elsewhere.
As for names of columns, if table Artists has a id, then keep it in column artist_id , and if Products has a foreign key referring to Artists.artist_id then name it too artist_id. Same thing should have same name all over your database. This would additionally let you use in SQL USING statement. Here is a small example :
SELECT
Users.name
Users.email
FROM Users
LEFT JOIN GroupUsers USING (user_id)
LEFT JOIN Groups USING (group_id)
WHERE Groups.name = 'wheel'
I assume that this query does not need explanation as it is a simple many-to-many relationship between users and groups.
You could use the query method, but that method is treated as a last resort, when find or any of the other convenience methods don't suffice.
But it's also possible to manually specify joins in the Cake find method, see the cookbook. I'm not entirely sure, but I believe nested joins are also supported. CONCAT_WS could just be called, with the relevant fields, in the field property of the find method.