Get aggregated information as part of an Eloquent many-to-many relationship - laravel

I am new to Laravel and need some help with what might be a basic question.
Race horses (can) have many owners. Owners (can) have many race horses. However, owners can buy and sell shares in race horses. At any point in time it must be possible to determine the share holding of each race horse.
My pivot table is defined as follows:
Schema::create('horse_owner', function (Blueprint $table) {
$table->id();
$table->foreignId ('horse_id');
$table->foreignId ('owner_id');
$table->date ('effective'); // the date when the transaction occurred
$table->integer ('shares')->default (1); // the number of shares + or -
$table->timestamps();
});
My Horse model includes a withPivot for effective and shares and I can show these in the Horses view. However, where an owner buys and sells shares, I would like the resultant number of shares to be shown.
The SQL query to find the current owners of a horse on a certain date looks like this:
SELECT `owners`.*, SUM(`horse_owner`.`shares`) AS `share_total`
FROM `owners`
INNER JOIN `horse_owner` ON `owners`.`id` = `horse_owner`.`owner_id`
WHERE `horse_owner`.`horse_id` = ? -- some horse
AND `horse_owner`.`effective` <= ? -- some date
GROUP BY horse_owner.owner_id;
While seeing the individual "transactions" is a necessary function, this share total will be mostly more useful than the individual transactions (e.g. summary reports, billing) and so being able to extract it easily for one or more horses or owners will be useful.
How does one set this up in Laravel and are there any best practice suggestions?
EDIT:
Based on other solutions, I've attempted the following:
return $this->belongsToMany (Owner::class)
->selectRaw ("`owners`.*, SUM(`horse_owner`.`shares`) AS `share_total`")
->withTimestamps()
->groupBy ("horse_owner.owner_id");
However, this results in an error because the query still includes "contains nonaggregated column horse_owner.id which is not functionally dependent on columns in GROUP BY clause".

Related

Laravel models, database and pivot tables question

Hello I am working with Laravel,
I have to create two simple models, let's say Stores and Books.
Stores can have one or multiple Books and Books can belong to many Stores.
Of course I will use a many to many relationship, with a pivot table.
Books the can have different prices depending the store.
I think a separate table can only complicate things, in my mind the pivot table associating books and stores should have a price column, but pivot tables only contains store_id and book_id.
Should I create a book_prices and associate it with books and to stores? What is the best approach?
You are free and able to set other attributes on your pivot table. You can read more about it in the docs.
https://laravel.com/docs/9.x/eloquent-relationships#retrieving-intermediate-table-columns
You have to define the relationship accordingly, the following should clarify how this works. In this example you use the many-to-many relationship and add the price column to every retrieved pivot model.
public function books()
{
return $this->belongsToMany(Book::class)
->withPivot('price')
}
For example, you are able to access the pivot column in a loop like this
foreach ($shop->books as $book)
{
echo $book->pivot->price;
}
You can define additional columns for your pivot table in the migration for the pivot table, and then when defining the relationship use withPivot to define the additional columns so they come through in the model:
return $this->belongsToMany(Book::class)->withPivot('price');
(Adapted from the Laravel documentation, see https://laravel.com/docs/9.x/eloquent-relationships#retrieving-intermediate-table-columns)
Depends on the complexity of your case, but yes, you have two options for it. Let's say that the pivot table is called as book_store:
Directly adds price column to book_store. This is obviously the simpler option. The drawbacks are:
The history of the price changes isn't logged. You'll have to create another table for logging if you want to keep this history information.
Changes made to price will directly change the price of the related book_store record. Meaning that a price is being updated "live" e.g users cannot update the price now but "publish" it some time later just like this example in the doc.
Create a new, different table to store the price. This may seems relatively more complex, but it may also be more future-proof.
Basically, you get 2 things that you miss in the first option above.
Don't think too much about book_store being a pivot table. One way to see it is like this: book_store IS a pivot table from books and stores tables viewpoints, but it's also just a normal SQL table which could relate to any other tables using any kind of relationships.
If you want to implement this, make sure to create a primary-key in the book_store table.
Alast, it all depends on what you need. Feel free to ask if you need more insight about this. I hope this helps.

How can I create groups of users in a waiting room automatically?

We are trying to enable collaborative chats in an educational application. The general idea is that the users (students) will start an exercise, this will make them join an specific waiting room. And somehow the system should be able to decide it has enough students to create a group of n students (depending on the exercise) according to a given strategy and will send a message to those students to join a chatroom with a generated ID so that they can collaborate.
Right now we are totally blank and cannot decide on how to make the server decide wether to try and create groups or to wait for more students. Our stack is in Spring Boot, Redis and Postgress. Our initial idea was to add the students into a waiting room in Redis and launch a Spring event every time a student joined the waitlist. However, we understand that approach might generate many race conditions, which should be avoided.
Create an exercise_students table which has a SERIAL column on it, call it arrival_order or something. Another column for group_id. As students sign in for the exercise, insert them into this table. By nature SERIAL is atomically auto-incremented, so you avoid race conditions. Regularly query the table for students with no group_id (I assume you have a exercise_group table of some sort that defines how many students are part of a group). When the count reaches n, update them with the group_id and create a new group in the exercise_group table for the next group.
Relational databases are pretty good at this sort of thing. Atomic updating of state is pretty straightforward stuff.

Count number times an observation appears connected to another column. DAX

I'm working in a data set that tracks ownership of firms in a database. I have three databases. One with all the firms, one with all unique owners and one database with the relations between the owners and the firms. Each owner can own multiple firms. I want a calculated column that for each firm shows how many firms the specific owner or owners has. I have no idea how to do it.
It is something like; for each firm, search how many times the owner or owners appears in the relationship database and sum those to give a number for each firm.
After all data is in data model and relationships are created, calculated column in table Firms should look like this:
CountFirmsOfOwner = COUNTROWS(FILTER(Firms, Firms[OwnerID] = EARLIER(Firms[OwnerID])))

Laravel eloquent for non-related tables

I have tables as seen here
An Athlete has many test days (e.g. one for each year), an Institution has teams e.g. Redham High School Rugby U14 Level. The team table is composed of the Institution, a sport and a level. Sport and Level are maintained in Codelist, which is just a table to maintain entities that are not big enough to be a table. Almost like replacing the hard coding of droplists.
Question 1: I don't see that the Codelist is related to the Team table, since Code list store unrelated info as well, like Country names, province names etc. Example of Codelist data below. Should they be related?
Question 2:
When creating a Test day record, I need the user to select for which team this athlete is getting tested for. I'm getting the Team records, but how do I now access the sport and level names from the Codelist?
I can't do $team->sport->name as they're not related tables.
Below is my current code. How do I get user friendly values for sportcode and levelcode e.g. Rugby U14.
$teams = Team::whereHas('Institution', function($query){
$query->whereClient_id(1);
})->get();
I can with $teams loop get the sportcode and level code and now write separate queries to fetch the names from the codelist. This would send 3 collections back to the view and doesn't seem the eloquent way. Advice appreciated.

Datamodel for a MVC learning project

I am trying to learn Microsoft MVC 2, and have in that case found a small project I wanted to deploy it on.
My idea was to simulate a restaurant where you can order a table.
Basics:
A user can only reserve a full table,
so I don't have the trouble of
merging people on different tables.
A person can order a table for a
certain amount of hours.
My question was how I could make the data model the smartest way. I thought of just having a my database like this:
Table
{
Id,
TableName
}
Reservations
{
Id
TableId
ReservedFrom
ReservedTo
UserId
}
User
{
UserId
UserName
...
}
By doing it this way I would have to program a lot of the logic in e.g. the business layer, to support which tables are occupied at what time, instead of having the data model handle it.
Therefore do you guys have a better way to do this?
A database constraint that doesn't allow two reservations for a table to overlap using a function that counts the number of reservations for the table whose start datetime or end datetime is between the datetimes of the row being inserted. The constraint would ensure that the count is 1 (the row just inserted).
Also, you should have your user interface block times where all of the tables available are reserved. Essentially, you'd get all the reservations for the day and for each hour block count the number of reservations that span that block -- if the count is equal to the number of tables, then the UI doesn't allow that block to be chosen. This spans your business/UI layers.
There's no way to know how long a person will take to eat so you cannot assume the ReservedTo time is accurate. I would build up timeslots in a seperate table. That way a simple unique constraint could be used.
TimeSlot { id, StartTime, Duration }
Additionally I would dump the user table and just put in a name.
Reservation { id, tableId, date, timeSlotId, Name }
put the unique constraint on { tableId, date, timeSlotId }
This could be expanded out to allow different durations for different tables but that is outside tke scope of a learning project I think.

Resources