Check if relation exists in database via pivot table - mysql

situation
User can belong to multiple organizations, linked via a pivot table called employees
Models at play: User, Employee & Organizations
Relevant database columns:
users
- id
employees
- user_id
- organization_id
organizations
- id
goal
An efficient way to check if user 1 and user 2 share at least one organization_id in the employees table
usecase
Api endpoint /api/v1/user/# returns additional metadata regarding the user.
Using a policy, it checks if the current user and the user id from the url are the same, or that they are both employee in at least one organization, the organization_id is not known at this stage, all that matters is that it matches.
example A
user A (1) is employee of organization foo (1)
user B (2) is employee of organization bar (2)
employee table thus has the following records:
+-----------------+---------+
| organization_id | user_id |
+-----------------+---------+
| 1 | 1 |
| 2 | 2 |
+-----------------+---------+
in this example the query should return a false result, since there is no shared organization_id between user A and B
example B
user A (1) is employee of organization foo (1)
user A (1) is employee of organization foobar (3)
user B (2) is employee of organization bar (2)
user B (2) is employee of organization foobar (3)
employee table thus has the following records:
+-----------------+---------+
| organization_id | user_id |
+-----------------+---------+
| 1 | 1 |
| 2 | 2 |
| 3 | 1 |
| 3 | 2 |
+-----------------+---------+
in this example the query should return a true result, since there is a shared organization_id between user A and B
policy code
/**
* Determine whether the user can view the model.
*
* #param \App\User $user
* #param \App\User $model
* #return mixed
*/
public function view(User $user, User $model)
{
if ($user->is($model)) {
return true;
} else {
// check if users share at least one organization
}
}
code that works but does not look efficient
foreach ($user->organizations()->with('users')->get() as $organization) {
if ($organization->users->where('id', $model->id)->first()) {
return true;
}
}
return false;
experimental code with joins instead of something done with laravel models
\Illuminate\Support\Facades\DB::table('employees as auth_employee')
->join('employees as other_employee', 'other_employee.organization_id', '=', 'auth_employee.organization_id')
// ->join('organizations', 'organizations.id', '=', 'organizations.id')
->where('auth_employee.id', 1)
->where('other_employee.id', 2)
->get()
requested solution
An efficient query to get a (castable to) boolean result result wether or not 2 users share at least one organization_id on the employees table, 'bonus points' for using the laravel models / query builder.
footer
Thanks for reading, here is a potato: 🥔

Assuming you have a users relationship set up in your Organization model, you could use the whereHas method:
$user->organizations()->whereHas('users', function ($query) use($model) {
$query->where('users.id', $model->id);
})->exists();

As a raw query, I would probably use EXISTS here, but since you would need to port any query to Laravel/PHP code, I might suggest using a self-join:
SELECT DISTINCT
e1.user_id, e2.user_id
FROM employees e1
INNER JOIN employees e2
ON e1.organization_id = e2.organization_id AND e1.user_id = 2
WHERE
e1.user_id = 1;
This would just return the user_id pair of values (1, 2). If you wanted a query to return all pairs of distinct users sharing at least one organization, you could rewrite this query to this:
SELECT DISTINCT
e1.user_id, e2.user_id
FROM employees e1
INNER JOIN employees e2
ON e1.organization_id = e2.organization_id AND e1.user_id <> e2.user_id;

The most readable example I can think of would be to put something like this in the user model:
public function isColleagueWith(User $user): bool
{
return $this->organizations->intersectByKey($user->organizations)->count() > 0;
}
Usage is easy to read and understand:
$userA->isColleagueWith($userB);
If you wanted to use less DB queries, you could query the pivot table directly instead. Here you an get all organizations that employ the two users and check if the list contains any duplicate organization ids.
use Illuminate\Database\Eloquent\Relations\Pivot;
class Employees extends Pivot
{
public function areColleagues(int $userIdA, int $userIdB): bool
{
$employments = $this->where('user_id', $userIdA)
->orWhere('user_id', $userIdB)
->get('organization_id');
return $employments->count() > $employments->unique()->count();
}
}
Usage:
Employees::areColleagues($userIdA, $userIdB);

To check if users 1 and 2 share one or more organizations:
SELECT EXISTS (
SELECT 1 FROM employees AS a
JOIN employees AS b USING(organization_id)
WHERE a.user_id = 1
AND b.user_id = 2;
Have both of these indexes:
(user_id, organization_id)
(organization_id, user_id)

Related

Laravel MySql get attendance for players for match

Currently I am designing an app for Football Club which organises friendly match every weekend.
The system opens entry for players on Wednesday morning till Thursday 6PM. So players give their availability if they are available for match on that weekend or not and club organises accordingly.
I have 2 tables matches which saves match information like following.
id | date | entry_open_time | entry_close_time | active | deleted | created | modified
Another table is availabilities table which saves players availability
id | match_id | player_id | is_available | deleted | created | modified
How can I calculate who is top of the leaderboard by attendance percentage for the year?
Currently I do the following.
Run 2 queries, one to get total number of matches organised in current year and second get total attendance count per user
Then I calculate percentage of attendance for each user looping through collection.
Then sort the collection.
Is there a better way to do this? May be get the results directly using mysql query?
Total Match count query
Match::where('active', true)->count();
Attendance by user
User::active()->withCount([
'availabilities' => function ($query) {
$query->available();
},
])->orderBy('availabilities_count')->get()
Then I calculate the percentage for each user in collection
$availabilities->map(function ($item) use ($matchCount) {
if ($item['availabilities_count'] && $matchCount) {
$percentage = ($item['availabilities_count'] * 100) / $matchCount;
$item['attendance'] = round($percentage,2);
} else {
$item['attendance'] = 0;
}
return $item;
});
After that I sort the collection by attendance
$availability->sortBy('attendance');
Here is the sample SqlFiddle
How can I get same result with mysql query?
Thank you
I believe in Mysql you can achieve your expected results as
select `u`.*,
ac.availabilities_count,
mc.match_count,
round((ac.availabilities_count/ mc.match_count) * 100,2) attendance
from `users` u
join (
select user_id, count(*) availabilities_count
from `availabilities`
where `is_available` = 1
group by user_id
) ac on `u`.`id` = `ac`.`user_id`
join (
select count(*) match_count
from matches
-- where active = 1
) mc
order by `first_name`, `last_name`
DEMO

Sorting a table from values out of the table but with relation

In my database, each user can submit 4 types of rates for each course.
I have a table named course and this table has a column named id.
I have another table named rate_type to record 4 types of the rate that user can submit
-------------
| id | type |
-------------
I have another table named rate to record to record user's rate submission with the following structure
----------------------------
| id | user_id | course_id |
----------------------------
And I have another table named rate_value to record my user's rate value for each course and for each rate type with following structure:
----------------------------------
| id | rate_id | value | type_id |
----------------------------------
the course_id in rate has relation with id in course table.
user_id's relation in rate is not important.
rate_id in rate_value has relation with id in rate table.
type_id in rate_value has relation with id in rate_type table.(But it is not important because rate type isn't inmportant for the course, juse sum of the rates are important).
now I want to sort the course table from highest rate to lowest but the values of rate are in rate table and each course have many records in rate table and for sorting courses, I should sum all of the rate's for that specific course in the rate_value table and sort the courses by rates sum of each course.
$query = Course::find()
->alias("t")
->where(["t.deleted" => 0])
->joinWith([
"rate"
]);
What should I add to $query to complete this query as defined.
This the raw sql query I want to run with yii2 Model
SELECT t.id, t.title, SUM(rate_value.value) FROM `course` `t`
LEFT JOIN rate
ON rate.course_id = t.id
LEFT JOIN rate_value
ON rate_value.rate_id = rate.id
GROUP BY t.id
ORDER BY SUM(rate_value.value) DESC
UPDATE1:
This query is working correctly but when I join somthing else to this query, this will return a larger number for SUM
Assuming that you have models relations declared as:
class Course extends ActiveRecord {
public static function tableName() {
return 'course';
}
public function getRates(){
return $this->hasOne(RateValue::class, ['rate_id' => 'id'])
->viaTable('rate', ['course_id' => 'id']);
}
}
class RateValue extends ActiveRecord {
public static function tableName() {
return 'rate_value';
}
}
You can build such query by:
$result = Course::find()
->select([
't.id',
't.title',
'sum' => 'SUM(rate_value.value)'
])
->alias('t')
->joinWith(['rates'])
->groupBy(['t.id'])
->orderBy(new \yii\db\Expression('SUM(rate_value.value) DESC'))
->asArray()
->all();
Displaying results:
foreach ($result as $row) {
echo "{$row['title']} - {$row['sum']}\n";
}

selecting all rows where a column has an specific ID

So I have a badly designed database (I think) which I can't change. It's a twitter like app where the users can follow each other. Every user has it`s row in the table, and in that table there is a column named 'following' which represents all USERID's that a user is following. In that column there is a list of USERID's separated with a coma. So lets say the user with the ID 1 is following users 2 and 3 and the user with ID 2 is following user 1 the table would look like this, user 3 is not following anybody.
USERID | username | following
-------------------------------------------
1 | some user | 2,3
2 | test1 | 1
3 | test2 |
Question is how do I show all the users which user 1 is following?
EDIT 1
The code that did not work from 491243, posting here, maybe I missed something in php
$USERID = $_GET['userid'];//this has a value, so not the problem here
$sql_select = "SELECT B.USERID FROM users A INNER JOIN users B ON FIND_IN_SET(B.USERID, B.following) > 0 WHERE B.USERID = '$USERID'";
$result_select = mysqli_query($link,$sql_select);
while($record = mysqli_fetch_array($result_select))
{
$following = $record['USERID'];
var_dump($following); //result is nothing, not even NULL
}
EDIT 2
Just for sanity check I did this:
$sql_select = "SELECT USERID FROM users WHERE USERID = '1'";
$result_select = mysqli_query($link,$sql_select);
while($record = mysqli_fetch_array($result_select))
{
$following = $record['USERID'];
var_dump($following); //result is 1, like it`s supposed to be
}
Is it possible my PHP code is wrong for the query in the replays?
Your table schema is in bad shape. you should normalize it properly. But to answer you question, you can still get the result you want using JOIN and FIND_IN_SET
SELECT b.userid, b.username
FROM tableName a
INNER JOIN tableName b
ON FIND_IN_SET(b.userID, a.following) > 0
WHERE a.userID = 1
SQLFiddle Demo
My preferred design would be
User Table
UserID (PK)
UserName
Following Table
UserID (FK) - also a PK with FollowID
FollowID (FK)
You might be looking for FIND_IN_SET()
SELECT userid, username
FROM tableName
WHERE FIND_IN_SET('1', following);
SAMPLE FIDDLE

MYSQL - How to check if a user is following another user, potentially through a subselect?

I'm fetching a list of activities (activities) and using a left join to grab the user data (users) who created the activity. Within my application users have the ability to follow one another.
This is my current query, which grabs all activities not posted by yourself ($user_id)
SELECT
activities.id, activities.user_id, users.id, users.name
FROM
activities
LEFT JOIN
users on activities.user_id = users.id
WHERE
users.id != $user_id
Aside from the activities + users tables, I have a another table in my application called followers:
followers
id | user_id_1 | user_id_2 | followed_back
1 1 3 1
2 2 3 0
3 3 1 1
I need to check whether you ($user_id) have followed a particular user joined to each activity and perhaps call this new field "user_followed" which represents a true/false/null value?
For example, I'm user_id = 1. Based on the above table, this means I have followed user_id 3. When an activity is fetched and user_id 3 is joined / responsible, the new field "user_followed" would be true.
Therefore, I think I'd need to incorporate another SELECT query, checking if the user is being followed:
(SELECT
*
FROM
followers
WHERE
user_id_1 = $user_id AND user_id_2 = users.id
)
I'm just largely unsure of how to incorporate this into my initial query and create a new field representing yes or no. Any help would be much appreciated!

Optimise Linq-to-Sql mapping with one to many lookup

I'm having problems optimising data lookup with the following data structure:
Order
-----
Id
Customer
Date
... etc
OrderStatus
------
Id
OrderId
Date
UpdatedBy
StatusTypeId
...etc
This is causing me a headache on the Order List page, which basically shows a list of Orders. Each Order Summary in the list shows a bunch of fields from Order and the current OrderStatus, i.e. the OrderStatus with the latest Date which is linked to the Order.
Order List
-------------------------------------------------------
Order Id | Customer | Order Date | CurrentStatus |
-------------------------------------------------------
1 | Someone | 1.10.2010 | Completed |
-------------------------------------------------------
2 | Someone else | 12.10.2010 | In Progress |
-------------------------------------------------------
3 | Whoever | 17.10.2010 | On Hold |
-------------------------------------------------------
Now, say I want to list all orders from this year. My Repository fetches the Order objects
var orders = _repository.GetAllOrdersSinceDate(dt);
and now I end up with something like
foreach (Order order in orders)
{
OrderSummary summary = new OrderSummary();
summary.Customer = order.Customer;
summary.Date = order.Date;
// ...etc
// problem here!!
summary.OrderStatus = order.OrderStatus
.OrderByDescending(s => status.Date).First();
}
So what I end up with is a SELECT statement on Order and then a further SELECT statement on OrderStatus for each Order returned.
So to show the summary of all records for this year is requiring around 20,000 individual SQL queries and taking many minutes to load.
Is there any neat way to fix this problem?
I'm considering re-writing the database to hold the current OrderStatus in the Order table, so I end up with something like
Order
-----
Id
Customer
Date
CurrentStatusTypeId
CurrentStatusDate
CurrentStatusUpdatedBy
...etc
OrderStatusHistory
------
Id
OrderId
Date
UpdatedBy
StatusTypeId
...etc
which is the only way I can see to solve the problem but seems a pretty nasty solution.
Whats the best way forward here?
Please don't denormalize your database model to solve your problem. This will only make things worse. You can fix this by writing a service method that returns a list of data transfer objects (DTO) instead of the LINQ to SQL entities. For instance, the service method might look like this:
public OrderSummary[] GetOrderSummariesSinceDate(DateTime d)
{
return (
from order in this.context.Orders
where order.Date >= d
let lastStatus = (
from status in order.OrderStatusses
orderby status.Date descending
select status).First()
select new OrderSummary
{
OrderId = order.Id,
CustomerName = order.Customer.Name,
Date = order.Date,
OrderStatus = lastStatus.StatusType.Name
}).ToArray();
}
Note the following:
This code will execute as a single SQL query in the database.
This method will return an object that contains just the data that the client needs, but nothing more. No Customer object, no OrderStatus object.
By calling ToArray we ensure that the database is queried at this point and it is not deferred.
These three points ensure that the performance is maximized and allows the service layer to stay in control over what is executed to the database.
I hope this helps.
You can create a DataLoadOptions object as follows:
DataContext db = new DataContext
DataLoadOptions ds = new DataLoadOptions();
ds.LoadWith<OrderStatus>(c => c.Orders);
db.LoadOptions = ds;
Then when you run your query it should prefetch the OrderStatus table