Yii2 Delete Query Using Relations - php

I have defined a relation in Yii2 in which every website can have multiple feeds:
public function getFeeds() {
return $this->hasMany(Feed::className(), ['website_id' => 'id']);
}
Now consider following query:
$ids = [2, 3];
$feeds = Website::find()->where(['user_id' => 1])->with(['feeds' => function($query) use ($ids) {
$query->where(['id' => $ids);
}])->all();
The corresponding raw SQL queries are:
SELECT * FROM `website` WHERE `user_id`= 1 // returns 4, 5, 6
SELECT * FROM `feed` WHERE (`id` IN (2, 3)) AND (`website_id` IN (4, 5, 6))
My questions is, can I have delete version of this query using Active Record? Something like this:
SELECT * FROM `website` WHERE `user_id`= 1
DELETE FROM `feed` WHERE (`id` IN (2, 3)) AND (`website_id` IN (4, 5, 6))
Any help would be appreciated.

You may do as this:
$query = Website::find()
->select('id')
->where(['user_id' => 1]);
Feed::deleteAll([
'id' => [2, 3],
'website_id' => $query
]);

In this case could be better a CreateCommand
for do this you should bild raw query using join eg:
select *
from `feed`
join `website` on `website`.id = `feed`.`website_id`
WHERE website`.`user_id`= 1
AND `feed`.id IN (2, 3)
or for delete
delete
from `feed`
join `website` on `website`.id = `feed`.`website_id`
WHERE website`.`user_id`= 1
AND `feed`.id IN (2, 3)
then for Yii2
$sql = "delete
from `feed`
join `website` on `website`.id = `feed`.`website_id`
WHERE website`.`user_id`= 1
AND `feed`.id IN (2, 3);"
Yii::$app->db->createCommand($sql)->execute();

Related

Not able to find correct data with whereHas and having count

I have users, conversations, conversation_user and messages table:
Before making a new conversation with $data array of user id's, I am trying to find existing one:
$data = [2,3]; // without auth()->id() -> 1 => total of 3 users per conversation
auth()->user()->conversations()->has('messages')->whereHas('users', function ($query) use ($data) {
$query->whereIn('user_id', $data);
})->whereHas('users', function ($query) use ($data) {
$query->groupBy('conversation_id', 'conversation_user.id')
->havingRaw('count(conversation_id) = ' . count($data) + 1); // total count is 3
})->first()
Now first whereHas returns even if I have conversation between auth()->id() and ID-2, because user_id 2 is in (2,3).. So it would retrieve the wrong conversation, where I need to count for users per conversation as well.
The second whereHas is for counting however if I use $query->groupBy('conversation_id') I get mysql SQL_MODE error for grouping, meaning I need to add $query->groupBy('conversation_id', 'conversation_user.id') as well, but with all that I get no record from database even if there are some.
What am I missing here?
[Updated with generated sql]
select * from `conversations`
inner join `conversation_user` on `conversations`.`id` = `conversation_user`.`conversation_id`
where `conversation_user`.`user_id` = 1 and exists (
select * from `conversation_messages`
where `conversations`.`id` = `conversation_messages`.`conversation_id`
and `conversation_messages`.`deleted_at` is null
) and exists (
select * from `users`
inner join `conversation_user` on `users`.`id` = `conversation_user`.`user_id`
where `conversations`.`id` = `conversation_user`.`conversation_id`
and `user_id` in (2, 3) and `users`.`deleted_at` is null
) and exists (
select * from `users`
inner join `conversation_user` on `users`.`id` = `conversation_user`.`user_id`
where `conversations`.`id` = `conversation_user`.`conversation_id`
and `users`.`deleted_at` is null
group by `conversation_id`, `conversation_user`.`id`
having count(conversation_id) = 3
) and `conversations`.`deleted_at` is null
[Update with table structures]
users -> id, name, email
conversations -> id, slug, subject
conversation_user -> id, user_id, conversation_id
messages -> id, conversation_id, user_id, body
[Another update]
Seems like this works also, in case someone need:
auth()->user()->conversations()->has('messages')->whereHas('users', function ($query) use ($data) {
$query->whereIn('user_id', $data);
})->whereDoesntHave('users', function ($query) use ($data) {
$query->whereNotIn('user_id', $data);
})->first()
I think this is the SQL you want -
SELECT c.*
FROM conversation_user cu
JOIN conversations c
ON cu.conversation_id = c.id
WHERE cu.user_id IN (1, 2, 3)
AND NOT EXISTS (
SELECT *
FROM conversation_user
WHERE conversation_id = cu.conversation_id
AND user_id NOT IN (1, 2, 3)
)
GROUP BY cu.conversation_id
HAVING COUNT(DISTINCT cu.user_id) = 3
Not sure if this is correct as I am not a Laravel user -
$data = [1, 2, 3];
$conv = DB::table('conversation_user cu')
->select('c.*')
->join('conversations c', 'cu.conversation_id', '=', 'c.id')
->whereIn('cu.user_id', $data)
->whereNotExists(function($query) use ($data) {
$query->select(DB::raw(1))
->from('conversation_user')
->whereColumn('conversation_id', 'cu.conversation_id')
->whereNotIn('user_id', $data);
})
->groupBy('cu.conversation_id')
->havingRaw('COUNT(DISTINCT cu.user_id) = ?', count($data))
->get();

Laravel hasmanythrough model places row in wrong parent when primary key number is 'skipped' or when another student is added inbetween the keys

This is about these 4 tables with the following primary keys.
Courses: CursusNaam varchar
Exercises: OpdrachtID int
Exercise progress: OpdrachtVoortgangID int
Students: LeerlingID int
One course can have many exercises (so exercises has the foreign key CursusNaam).
The progress of the exercises per student is stored in the exercise progress table (so it has the foreign
key OpdrachtID) and of course the exercise progress is linked to a student (so it has the foreign key LeerlingID.
What i tried to do is to get and display the latest exercise finished by the student. I did this with an hasManyThrough relation in the course Model.
This works fine when there's only one student in the table exercise progress. However, when i add one more row with a different student in the Exercise progress table, it displays the last OpdrachtVoortgangID from a course to the wrong course!
Exercise progress: This works with one student, it shows right:
Image of view where it shows right
INSERT INTO `opdrachtvoortgang` (`OpdrachtVoortgangID`, `LeerlingID`, `OpdrachtID`, `IsKlaar`, `Beoordeling`) VALUES
(1, 1, 1, b'1', 'Voldoende'),
(2, 1, 2, b'1', 'Onvoldoende'),
(3, 1, 3, b'1', 'Voldoende'),
(4, 1, 4, b'0', 'onvoldoende'),
(5, 1, 5, b'0', 'Voldoende'),
(6, 1, 6, b'0', 'Voldoende');
Exercise progress wrong #1:Adding another student causes OpdrachtVoortgangID:2 (the last one of the course it belongs to) to display to the wrong course:
Image of view where OpdrachtID 2 moved to wrong course
INSERT INTO `opdrachtvoortgang` (`OpdrachtVoortgangID`, `LeerlingID`, `OpdrachtID`, `IsKlaar`, `Beoordeling`) VALUES
(1, 1, 1, b'1', 'Voldoende'),
-- Different student with id 2 messes it up!
(2, 2, 2, b'1', 'Onvoldoende'),
(3, 1, 2, b'1', 'Onvoldoende'),
(4, 1, 3, b'1', 'Voldoende'),
(5, 1, 4, b'0', 'onvoldoende'),
(6, 1, 5, b'0', 'Voldoende'),
(7, 1, 6, b'0', 'Voldoende');
Exercise progress wrong #2: I just found out 'skipping' a primary key number has the same effect as having the progress of another student in between.
(1, 1, 1, b'1', 'Voldoende'),
(3, 1, 2, b'1', 'Onvoldoende'),
(4, 1, 3, b'1', 'Onvoldoende'),
(5, 1, 4, b'1', 'Voldoende'),
(6, 1, 5, b'0', 'onvoldoende'),
(7, 1, 6, b'0', 'Voldoende'),
Exercise table:
INSERT INTO `opdrachten` (`OpdrachtID`, `Opdracht`, `CursusNaam`, `Deadline`) VALUES
(1, '1', 'Laravel-essentials 1', '2021-02-10 13:52:26'),
(2, '2', 'Laravel-essentials 1', '2021-02-20 13:52:26'),
(3, '1', 'ASPAdvanced', NULL),
(4, '2', 'ASPAdvanced', NULL),
(5, '3', 'ASPAdvanced', NULL),
(6, '3B', 'ASPAdvanced', '2021-03-25 23:42:02');
Here's the course controller:
$cursussen = Cursus::get();
return view('home')
->with(compact('cursussen'))
This is how i display it in the view:
#foreach ($cursussen as $cursus)
$cursus->getVoortgang()
#endforeach
Here's the Course model:
//get the latest finished exercise by a student
function getVoortgang()
{
return $this->hasManyThrough('App\Models\OpdrachtVoortgang', 'App\Models\Opdrachten', 'CursusNaam', 'OpdrachtVoortGangID', 'CursusNaam', 'OpdrachtID')
->where('LeerlingID', 1) //get progress from student with ID 1
->where('IsKlaar', 1) //only exercises that are finished
->whereNotNull('IsKlaar' )
->orderByDesc('OpdrachtID')
->get(); //get instead of first to show that that the progress row got displayed next to the wrong course.
}
Thanks a lot for having a look.
Edit: This is the output of dd($cursus->getVoortgang())
get() returns a collection of objects every time. That collection may have 0 or more objects in it, depending on the results of the query.
first() calls get() under the hood, but instead of returning the collection of results, it returns the first entry in the collection (if there is one).
Which method you use depends on what you need. Do you need the collection of all the results (use get()), or do you just want the first result in the collection (use first())?
I think you want to return the first result in the collection
You have to try this
function getVoortgang()
{
return $this->hasManyThrough('App\Models\OpdrachtVoortgang', 'App\Models\Opdrachten', 'CursusNaam', 'OpdrachtVoortGangID', 'CursusNaam', 'OpdrachtID')
->where('LeerlingID', 1) //get progress from student with ID 1
->where('IsKlaar', 1) //only exercises that are finished
->whereNotNull('IsKlaar' )
->orderByDesc('OpdrachtID')
->first(); //get instead of first to show that that the progress row got displayed next to the wrong course.
}
or
function getVoortgang()
{
return $this->hasManyThrough('App\Models\OpdrachtVoortgang', 'App\Models\Opdrachten', 'CursusNaam', 'OpdrachtVoortGangID', 'CursusNaam', 'OpdrachtID')
->where('LeerlingID', 1) //get progress from student with ID 1
->where('IsKlaar', 1) //only exercises that are finished
->whereNotNull('IsKlaar' )
->orderByDesc('OpdrachtID')
->get()
->first();
}
Use get()->first() it will return only one record, if not find, it will return null.
and you have to order by the id I don't know the id column of your Cursus whatever it is put in in the orderby class
$cursussen = Cursus::orderby('id','desc')->get();
return view('home',compact('cursussen'));
I've still no idea to what caused this, but by using the 'through' table and adding joins to it solved the weird problem.
function getOpdracht()
{
return $this->hasMany('App\Models\Opdrachten', 'CursusNaam');
}
function getOpdrachtLaatstAfgemaakt()
{
return $this->getOpdracht()
->join('opdrachtvoortgang', 'opdrachten.OpdrachtID', '=', 'opdrachtvoortgang.OpdrachtID')
->where('opdrachtvoortgang.IsKlaar', '1')
->where('opdrachtvoortgang.LeerlingID', '1')
->orderByDesc('opdrachtvoortgangID')
->first();
}
Now it no longer shows next to the wrong course in the case there's a progress id number in between missing or when there's another student in between.

Eloquent/SQL - Get the row number of a query (rank)

I would like to know the rank based on my DB structure:
I have a model Post that belongs to a model called Edition (also one Edition has many Post).
One Post has many Like.
I would like to know the rank of a Post based on the Like count inside a particular Edition.
The code:
// Define the id of an edition
$edition_id = 53;
// The id of the post to retrieve rank
$post_id = 132;
// Compose the query by select all posts, of interested edition ...
$query = App\Models\Post::where('edition_id', $edition_id)
// ... with like count (this produce an additional field named likes_count) ...
->withCount('likes')
// ... in descendig order by this count.
->orderBy('likes_count', 'desc');
// By execute it I can get right results.
$query->get();
For people who are not familiar with Laravel Eloquent ORM, I report the sql query executed from code above:
select `posts`.*, (select count(*) from `likes` where `posts`.`id` = `likes`.`post_id` and `likes`.`deleted_at` is null) as `likes_count`
from `posts`
where `edition_id` = '53' and `posts`.`deleted_at` is null
order by `likes_count` desc
I report the query results:
=> Illuminate\Database\Eloquent\Collection {#994
all: [
App\Models\Post {#993
id: 135,
likes_count: 4,
},
App\Models\Post {#1075
id: 134,
likes_count: 3,
},
App\Models\Post {#1091
id: 133,
likes_count: 2,
},
App\Models\Post {#997
id: 132,
likes_count: 1,
},
App\Models\Post {#1038
id: 131,
likes_count: 0,
},
],
}
How can I get the row position of the record with a certain id from the results of the composed query?
For example, how to retrieve the rank result of the record with id = 132?
You can use a subquery:
$query = Post::where('edition_id', $edition_id)
->withCount('likes')
->selectRaw('#rank := #rank + 1 rank')
->from(DB::raw('`posts`, (SELECT #rank := 0) vars'))
->orderBy('likes_count', 'desc');
$posts = Post::fromSub($query, 'posts')->paginate();
$post = Post::fromSub($query, 'posts')->find($post_id);
Workaround for Laravel < 5.6:
Builder::macro('fromSub', function($query, $as) {
$this->bindings = array_merge(
array_slice($this->bindings, 0, 1),
['from' => $query->getBindings()],
array_slice($this->bindings, 1)
);
$sql = '('.$query->toSql().') as '.$this->grammar->wrap($as);
return $this->from(DB::raw($sql));
});
You can use the search() method to find the key:
The search method searches the collection for the given value and returns its key if found. If the item is not found, false is returned.
$id = 132;
$key = $query->search(function($post,$id) {
return $post->id === $id;
});

How to compare and get data From DB Json value in Laravel 5.5

I am new in laravel. I want to compare and get data according to date if date matches the current date on grater then current date and pagination set according to get result.
'[\"28-09-2018\"]'
'[\"27-08-2018\",\"15-10-2018\",\"23-10-2018\"]'
'[\"27-11-2018\",\"15-12-2018\",\"23-12-2018\"]'
This is my SQL:
INSERT INTO `events` (`id`, `title`, `organizer_name`, `email`, `number`, `alternate_number`, `event_category`, `event_dates`, `start_time`, `end_time`, `venue`, `long`, `lat`, `entry_fee`, `description`, `is_paid`, `is_activity`, `is_active`, `deleted_at`, `created_at`, `updated_at`) VALUES
(1, 'Test', 'Demo', 'msworldqueen#gmail.com', '9818043775', NULL, '12', '[\"28-09-2018\"]', '22:45:00', '23:00:00', 'Rajasthan 313001, India', '73.7124790', '24.5854450', '{\"Normal\":\"1000\"}', 'asdfasdfasdf', 1, '0', 1, NULL, '2018-08-27 00:31:37', '2018-08-27 00:31:37'),
(2, 'teststestse', 'sadfdsfa', 'admin#ngf.com', '999999999', '99999999', '0', '[\"27-08-2018\",\"15-10-2018\",\"23-10-2018\"]', '13:09:00', '12:09:00', 'slougia tastour 9014, Testour, Tunisia', '9.4422664', '36.5499000', NULL, 'test', 0, '0', 1, NULL, '2018-09-15 04:57:21', '2018-09-15 04:58:16');
(3, 'tesede,o', 'sadfdsfa', 'admin#ngf.com', '999999999', '99999999', '0', '[\"27-11-2018\",\"15-12-2018\",\"23-12-2018\"]', '13:09:00', '12:09:00', 'slougia tastour 9014, Testour, Tunisia', '9.4422664', '36.5499000', NULL, 'test', 0, '0', 1, NULL, '2018-09-15 04:57:21', '2018-09-15 04:58:16');
And I Try
$curdate = date('d-m-Y');
$events = Event::where('event_dates', '!=', '')
->where(function ($query) use ($curdate) {
$query->whereRaw(
'JSON_CONTAINS(event_dates, '>=', $curdate)'
);
/*$query->whereRaw(
'JSON_CONTAINS(event_dates, \'["' . $curdate . '"]\')'
);*/
/*foreach (event_dates as $id) {
$query->orWhereRaw(
'JSON_CONTAINS(event_dates, \'["' . event_dates . '"]\')'
);
}*/
return $query;
})->paginate(3);
I think kerbholz comment is the most elegant solution.
What I thinked was:
First transform events collection in array:
$events = Event::where('event_dates', '!=', '')->get()->toArray()
Tranform json in array
$arrayOfDates = json_decode($event_dates, false);
Now you have 2 arrays to play with.
Hope it helps.

Laravel Eloquent Query Not Working

$demos = Demo::whereIn('store_id', [1,2,4], function($query){
$query->where('status', 1);
})->paginate(10);
I know that this thats not working, But how can I work with this logic..?
[Select * from 'demos' where store_id in 1,2,4 and status = 1 ]
If I understand correctly you need something like this.
$demos = Demo::whereIn('store_id', [1, 2, 4])->where('status', 1)->paginate(10);
Chained "where" Eloquent methods == "AND" database query conditions.
Sometimes, it is better not use ORM`ish approach.
They say, old plain SQL is your best friend. So.
$data = \DB::select(
\DB::raw("SELECT * FROM `demos` WHERE `store_id` IN (:id1, :id2, :id3)"), array( ':id1' => 1, ':id2' => 2, ':id3' => 3 )
);
Or, if you have unknown count of store_id entries, you could:
$ids = array(1, 2, 3, 4, ...); // looks like unknown ammount of entries
$data = \DB::select(
\DB::raw("SELECT * FROM `demos` WHERE `store_id` IN (".rtrim(str_repeat('?,', count($ids)),',').")"), $ids
);

Categories