This question already has answers here:
count relation of relation in laravel
(3 answers)
Closed 3 years ago.
I am trying to count the amount of rows in a a relationship which is a via a relationship on the main table. I have 3 tables in total..
Surveys:
| id |
Questions:
| id | survey_id |
Responses:
| id | question_id |
I am trying to get the total count in the responses table from the surveys table. I have all of the relationships set up but I cannot seem to get the total count.
I have tried to do ->withCount('questions.responses') but this results in a 500 error. I know i can loop through but didn't know if there was a more efficient way to do it within Eloquent.
You can achieve it using a Has Many Through relationship.
In your model:
/* Survey.php */
public function responses()
{
return $this->hasManyThrough(Response::class, Question::class);
}
Then in your controller:
/* SurveyController.php */
public function index()
{
// Get your surveys with your desired attribute:
$surveys = Survey::withCount('responses')->get();
return view('my_cool_view')->withSurveys($surveys);
}
Related
I am writing a query in Laravel 8 using Eloquent. There is a many-to-many relation between subjects and streams and their pivot table is courses.
subjects:
id
name
streams
id
name
courses
id
subject_id
stream_id
I have to fetch subjects with specific stream.
I have written the following query.
$this->subjects = Subject::has('streams',$this->stream)->get();
I am having problem in this.
courses table :
id
stream_id
subject_id
1
1
1
2
1
2
3
1
3
4
1
4
5
2
1
6
2
2
7
3
1
This is the sample data. If I want to fetch subject where stream id is 1 then it is only fetching subject with ids 3 and 4 and not fetching subjects with id 1 and 2. Can anyone explain me why it is skipping subject ids with 1 and 2 ?
PLease anyone can help me with this.
Thank you in advance . :)
Edited
class Subject
{
public function streams()
{
return $this->belongsToMany(Stream::class, 'courses',
'subject_id', 'stream_id');
}
}
class Stream
{
public function subjects()
{
return $this->belongsToMany(Subject::class, 'courses',
'stream_id', 'subject_id');
}
}
For fetching models having a specific related model you can use whereHas() method. https://laravel.com/docs/8.x/eloquent-relationships#querying-relationship-existence
Assuming that $this->stream is a Stream model, you can use a closure for whereHas method:
$stream = $this->stream;
$this->subjects = Subject::whereHas('streams', function($query) use ($stream){
$query->where('streams.id', $stream->id);
})->get();
Edit: since $this->stream is an id you should do
$stream = $this->stream;
$this->subjects = Subject::whereHas('streams', function($query) use ($stream){
$query->where('streams.id', $stream);
})->get();
You should specify the table name where you are referencing the id to avoid ambiguity.
$this->subjects = Subject::whereHas('streams', function($query) use ($stream){
$query->where('stream.id', $stream->id);
})->get();
I've an admin side in my laravel project where admin can add Ads along with impression and hours. For e.g if i add a add with impression = 2 and hours = 2. So, once the user have viewed this ad for 2 times then user will not be shown that Ad for the next 2 hours.
Here is the structure of admin ads table:
--------------------------------------
id | image |impressions| hours |
1 | image.png| 2 | 6 |
--------------------------------------
And there's ads_impression_log where i store the log of users who have viewed an ad
---------------------------------------------
id | user_id | ad_id | impression_datetime |
1 | 1 | 1 | 2020-07-28 23:22:45 |
---------------------------------------------
How can i create a laravel query so, that i get specific ad impression count and if impression count is within the hours show the user next ad.
This is my first question here. So, please ignore if there's some mistake :)
Ad Model
public function users(){
return $this->belongsToMany(App\Add, 'ads_impression_log', 'ad_id', 'user_id')
->withPivot('impression_datetime');
}
User Model
public function ads(){
return $this->belongsToMany(App\Add, 'ads_impression_log', , 'user_id', 'ad_id')
->withPivot('impression_datetime');
}
Impression count of a given user($user_id) for a given ad.($user_id)
We'll use the collection method filter here.
$user = User::with('ads')->find($user_id);
//
$ads = $user->ads->filter(function($value, $key){
return $value->id === $ad_id;
});
Now you can get the count of those impressions.
$count = $ads->count();
Impressions required for that ad.
$ads->first()->impression;
You can do the comparison accordingly.
And to get the impression_date column from the pivot table you could do the following.
foreach($ads as $adImp){
$adImp->pivot->impression_date;
}
I'm developing a reddit-like site where votes are stored per-user (instead of per-post). Here's my relevant schema:
content
id | author_id | title | text
---|-----------|-------------|---
1 | 1 (adam) | First Post | This is a test post by adam
vote: All the votes ever voted by anyone on any post
id | voter_id | content_id | category_id
---|-------------|------------------|------------
1 | 1 (adam) | 1 ("First Post") | 1 (upvote)
2 | 2 (bob) | 1 ("First Post") | 1 (upvote)
vote_count: Current tally ("count") of total votes received by a post by all users
id | content_id | category_id | count
---|------------------|--------------|-------
1 | 1 ("First Post") | 1 (upvote) | 2
I've defined a voteCount relation in Objection.js model for the content table:
class Content extends Model {
static tableName = 'content';
static relationMappings = {
voteCount: {
relation: Model.HasManyRelation,
modelClass: VoteCount,
join: {
from: 'content.id',
to: 'vote_count.content_id'
}
}
}
}
But I recently (learned and) decided that I don't need to keep (and update) a separate vote_count table, when in fact I can just query the vote table and essentially get the same table as a result:
SELECT content_id
, category_id
, COUNT(*) AS count
FROM vote
GROUP
BY content_id
, category_id
So now I wanna get rid of the vote_count table entirely.
But it seems that would break my voteCount relation since there won't be a VoteCount model (not shown here but it's the corresponding the model for the vote_count table) no more either. (Right?)
How do I keep voteCount relation while getting rid of vote_count table (and thus VoteCount model with it)?
Is there a way to somehow specify in the relation that instead of looking at a concrete table, it should look at the result of a query? Or is it possible to define a model class for the same?
My underlying database in PostgreSQL if that helps.
Thanks to #Belayer. Views were exactly the solution to this problem.
Objection.js supports using views (instead of table) in a Model class, so all I had to do was create a view based on the above query.
I'm also using Knex's migration strategy to create/version my database, and although it doesn't (yet) support creating views out of the box, I found you can just use raw queries:
module.exports.up = async function(knex) {
await knex.raw(`
CREATE OR REPLACE VIEW "vote_count" AS (
SELECT content_id
, category_id
, COUNT(*) AS count
FROM vote
GROUP
BY content_id
, category_id
)
`);
};
module.exports.down = async function(knex) {
await knex.raw('DROP VIEW "vote_count";');
};
The above migration step replaces my table vote_count for the equivalent view, and the Objection.js Model class for it (VoteCount) worked as usual without needing any change, and so did the relation voteCount on the Content class.
This is my first time using a polymorphic relationship.
I am creating a LMS where a Assignment can be allocated to a individual user or a team so reading the Laravel docs it seems that the Polymorphic Relationship will be a good way to go about it.
I have created 4 tables.
Users:
| id | username | password | created_at | updated_at |
Teams: | id | friendly_name | slug |
Team User: | id | user_id | team_id |
Assignment Allocation:
| id | assignment_id | assignmentable_type | assignmentable_id | created_at | updated_at
So when the assignment_allocations has data in it looks like this...
| id | assignment_id | assignmentable_type | assignmentable_id |
| 1 | 1 | App\Models\User | 1 |
However I get this error:
SQL: select * from users where users.assignmentable_id = 1 and users.assignmentable_id is not null and users.assignmentable_type = App\Models\User
SO obviously I have done something wrong, however I cannot for the life of me figure out what I've done wrong.
This is my functions that relate to this:
AssignmentAllocation.php
public function assignmentable()
{
return $this->morphTo();
}
User.php
public function assignments()
{
return $this->morphMany('App\Models\User', 'assignmentable');
}
Team.php
public function assignments()
{
return $this->morphMany('App\Model\Team', 'assignmentable');
}
Any help is greatly appreciated.
There are 2 potential answers depending on your data structure, which isn't clear from your question.
User/Team to Assignment is a one-to-many relationship (each Assignment has one User/Team, and each User/Team has many Assignments
Many-to-many relationship where each User/Team has many Assignments and each Assignment has many Users/Teams
One to many
In a one-to-many you wouldn't need an Assignment table and an AssignmentAllocation table, so I am assuming your AssignmentAllocation is your Assignment model. If not then you need to put the assignmentable_type and assignmentable_id columns on the assignments table instead, and use Assignment.php instead of AssignmentAllocation.php.
AssignmentAllocation.php
public function assignmentable()
{
return $this->morphTo();
}
User.php
public function assignments()
{
return $this->morphMany('App\Models\AssignmentAllocation', 'assignmentable');
}
Team.php
public function assignments()
{
return $this->morphMany('App\Models\AssignmentAllocation', 'assignmentable');
}
Your error is because Laravel is searching the users table for the match (you have morphMany('User'..), when it should be searching the AssignmentAllocation table. So just switch them out.
1) The morphMany acts like a hasMany, so you're saying:
Each User hasMany Assignments, and each Team hasMany Assignments. The first part of the morphMany says "search the AssignmentAllocation table", and the second part says "search for assignmentable_id and assignmentable_type being equal to this instance of this model".
2) morphTo acts like belongsTo so you're saying:
Each Assignment belongsTo an assignmentable.
Many to many
However if AssignmentAllocation is a many-to-many pivot table and each Assignment has many Users or Teams, and each User/Team has many Assignments, then you need a morphToMany/morphedByMany pair.
User.php
public function assignments()
{
return $this->morphToMany('App\Models\Assignment', 'assignmentable');
}
Team.php
public function assignments()
{
return $this->morphToMany('App\Models\Assignment', 'assignmentable');
}
Assignment.php model NOTE: not the AssignmentAllocation model
public function users()
{
return $this->morphedByMany('App\Models\User', 'assignmentable');
}
public function teams()
{
return $this->morphedByMany('App\Models\Team', 'assignmentable');
}
You should rename the assignment_allocation table to assignmentables, or add the required third and fourth arguments to the morph functions. I prefer to keep the table names consistent.
I have four tables
create table entities{
integer id;
string name;
}
create table users{
integer id;//fk to entities
string email;
}
create table groups{
integer id;//fk to entities
}
create table group_members{
integer group_id; //fk to group
integer entity_id;//fk to entity
}
I want to make a query that returns all groups where a user belongs, directly or indirectly. The obvious solution is to make a recursion at the application level. I’m wondering what changes can I make to my data model to decrease the database access and as a result have a better performance.
In Oracle:
SELECT group_id
FROM group_members
START WITH
entity_id = :user_id
CONNECT BY
entity_id = PRIOR group_id
In SQL Server:
WITH q AS
(
SELECT group_id, entity_id
FROM group_members
WHERE entity_id = #user_id
UNION ALL
SELECT gm.group_id, gm.entity_id
FROM group_members gm
JOIN q
ON gm.entity_id = q.group_id
)
SELECT group_id
FROM q
In PostgreSQL 8.4:
WITH RECURSIVE
q AS
(
SELECT group_id, entity_id
FROM group_members
WHERE entity_id = #user_id
UNION ALL
SELECT gm.group_id, gm.entity_id
FROM group_members gm
JOIN q
ON gm.entity_id = q.group_id
)
SELECT group_id
FROM q
In PostgreSQL 8.3 and below:
CREATE OR REPLACE FUNCTION fn_group_members(INT)
RETURNS SETOF group_members
AS
$$
SELECT group_members
FROM group_members
WHERE entity_id = $1
UNION ALL
SELECT fn_group_members(group_members.group_id)
FROM group_members
WHERE entity_id = $1;
$$
LANGUAGE 'sql';
SELECT group_id
FROM group_members(:myuser) gm
There are ways of avoiding recursion in tree hierarchy queries (in opposition to what people have said here).
The one I've used most is Nested Sets.
As with all life and technical decisions, however, there are trade offs to be made. Nested Sets are often slower to update but much faster to query. There are clever and complicated ways of improving the speed of updating the hierarchy, but there's another trade-off; performance vs code complexity.
A simple example of a nested set...
Tree View:
-Electronics
|
|-Televisions
| |
| |-Tube
| |-LCD
| |-Plasma
|
|-Portable Electronics
|
|-MP3 Players
| |
| |-Flash
|
|-CD Players
|-2 Way Radios
Nested Set Representation
+-------------+----------------------+-----+-----+
| category_id | name | lft | rgt |
+-------------+----------------------+-----+-----+
| 1 | ELECTRONICS | 1 | 20 |
| 2 | TELEVISIONS | 2 | 9 |
| 3 | TUBE | 3 | 4 |
| 4 | LCD | 5 | 6 |
| 5 | PLASMA | 7 | 8 |
| 6 | PORTABLE ELECTRONICS | 10 | 19 |
| 7 | MP3 PLAYERS | 11 | 14 |
| 8 | FLASH | 12 | 13 |
| 9 | CD PLAYERS | 15 | 16 |
| 10 | 2 WAY RADIOS | 17 | 18 |
+-------------+----------------------+-----+-----+
You'll want to read the article I linked to understand this fully, but I'll try to give a short explanation.
An item is a member of another item if (the child's "lft" (Left) value is greater than the parent's "ltf" value) AND (the child's "rgt" value is less than the parent's "rgt" value)
"Flash" is therfore a member of "MP3 PLAYERS", "Portable Electronics" and "Electronics"
Or, conversley, the members of "Portable Electronics" are:
- MP3 Players
- Flash
- CD Players
- 2 Way Radios
Joe Celko has an entire book on "Trees and Hierarchies in SQL". There are more options than you think, but lots of trade off's to make.
Note: Never say something can't be done, some mofo will turn up to show you that in can.
Can you clarify the difference between an entity and a user? Otherwise, your tables look OK. You are making an assumption that there is a many-to-many relationship between groups and entities.
In any case, with standard SQL use this query:
SELECT name, group_id
FROM entities JOIN group_members ON entities.id = group_members.entity_id;
This will give you a list of names and group_ids, one pair per line. If an entity is a member of multiple groups, the entity will be listed several times.
If you're wondering why there's no JOIN to the groups table, it's because there's no data from the groups table that isn't already in the group_members table. If you included, say, a group name in the groups table, and you wanted that group name to be shown, then you'd have to join with groups, too.
Some SQL variants have commands related to reporting. They would allow you to list multiple groups on the same line as a single entity. But it's not standard and wouldn't work across all platforms.
If you want a truly theoretically infinite level of nesting, then recursion is the only option, which precludes any sane version of SQL. If you're willing to limit it, then there are a number of other options.
Check out this question.
You can do the following:
Use the START WITH / CONNECT BY PRIOR constructs.
Create a PL/SQL function.
I don't think there is a need for recursion here as the solution posted by barry-brown seems adequate. If you need a group to be able to be a member of a group, then the tree traversal method offered by Dems works well. Inserts, deletes and updates are pretty straightforward with this scheme, and retrieving the entire hierarchy is accomplished with a single select.
I would suggest including a parent_id field in your group_members table (assuming that is the point at which your recursive relationship occurs). In a navigation editor I've created a nodes table like so:
tbl_nodes
----------
node_id
parent_id
left
right
level
...
My editor creates hierarchically-related objects from a C# node class
class node {
public int NodeID { get; set; }
public Node Parent { get; set; }
public int Left { get; set; }
public int Right { get; set; }
public Dictionary<int,Node> Nodes { get; set; }
public int Level {
get {
return (Parent!=null) ? Parent.Level+1 : 1;
}
}
}
The Nodes property contains a list of child nodes. When the business layer loads the hierarchy, it rectifies the parent/child relationships. When the nav editor saves, I recursively set the left and right property values, then save to the database. That lets me get the data out in the correct order meaning I can set parent/child references during retrieval instead of having to make a second pass. Also means that anything else that needs to display the hierarchy ( say, a report) can easily get the node list out in the correct order.
Without a parent_id field, you can retrieve a breadcrumb trail to the current node with
select n1.*
from nodes n1, nodes n2
where d1.lft <= d2.lft and d1.rgt >= d2.rgt
and d2.id = #id
order by lft;
where #id is the id of the node you're interested in.
Pretty obvious stuff, really, but it applies to items such as nested group membership that might not be obvious, and as others have said eliminates the need to slow recursive SQL.