I have structured a categories table to store Categories as well as Sub Categories.
My table Structure is:
catID (int)
catType (varchar 4)
parentID (int)
catName (varchar 50)
sOrder (int)
How to do following subquery with Eloquent.
SELECT c.*,
(
SELECT COUNT(*) FROM categories AS sc WHERE sc.parentID = c.catID AND sc.catType = "Sub"
) AS totalSubCategories
FROM categories AS c
WHERE c.catType = 'Root'
I have tried following but it wont work:
$rows = CategoryModel::from('categories as c')
->where('c.catType', '=', 'Root')
->paginate(20)
->select(DB::raw('c.*, (SELECT COUNT(*) FROM categories as sc WHERE sc.parentID = c.catID AND sc.catType = "Sub") AS totalCategories'));
I am getting following Error
ErrorException (E_UNKNOWN)
call_user_func_array() expects parameter 1 to be a valid callback, class 'Illuminate\Support\Collection' does not have a method 'select'
You can add an Eloquent relationship on the same table, and filter it to the right catType:
public function children() {
return $this->hasMany('Model', 'parentID')->where('catType', 'Sub');
}
This makes the item's children available as $item->children. You can get a count like so:
$count = $item->children()->count();
Related
I want to pick questions randomly but category dependent. For example, if the test is given with 10 questions and the total category is 5, the test flow should take 2 questions randomly from each category. Is there a way to select it through random and eloquent relations?
and the question table
+-------+-------+-------+-------+
| id | category_id |.......|
+-------+-------+-------+-------+
already I am using random eloquent but the probability of getting questions from each category is low
public getRandomQuestions($limit)
{
$this->inRandomOrder()->limit($limit)->get()
}
and I'm clueless when it's coming to relations.
You can also use the inRandomOrder and groupBy method together to select random questions from each category.
$questions = Question::with('category')->inRandomOrder()->groupBy('category_id')->limit(2)->get();
This will give you 2 random questions from each category.
You can also use subquery to select questions with certain number of random questions per category
$questions = Question::with('category')
->whereIn('category_id', function($query) use ($limit) {
$query->select('category_id')
->from('questions')
->groupBy('category_id')
->inRandomOrder()
->limit($limit)
})->get();
the query to get 1 random question for each category:
SELECT *
FROM
(SELECT *,
#position := IF(#current_cate=category_id, #position + 1, 1) AS POSITION,
#current_cate := category_id
FROM
(SELECT q.*
FROM category c
INNER JOIN question q ON c.id = q.category_id
ORDER BY RAND()) temp
ORDER BY category_id) temp1
WHERE POSITION <= 2
ORDER BY category_id;
explanation:
since you want the question to be take randomly we need order by rand(), note: inRandomOrder also uses order by rand() under the hood
to be able to get 2 questions for each category, we need a variable (#position) to mark the order of question
laravel implementation:
public getRandomQuestions($limit)
{
$questions = DB::select("SELECT *
FROM
(SELECT *,
#position := IF(#current_cate=category_id, #position + 1, 1) AS POSITION,
#current_cate := category_id
FROM
(SELECT q.*
FROM category c
INNER JOIN question q ON c.id = q.category_id
ORDER BY RAND()) temp
ORDER BY category_id) temp1
WHERE POSITION <= 2
ORDER BY category_id");
return Question::hydrate($questions->toArray());
}
If you're using PHP >= 7.2 the use shuffle()
public function getRandomQuestions($limit) {
return Question::limit($limit)->groupBy('category_id')->get()->shuffle();
}
or else
public function getRandomQuestions($limit) {
return Question::inRandomOrder()->limit($limit)->groupBy('category_id')->get();
}
the trick is you need to use groupBy() clause for this
Trying to join 4 - 5 tables at once as of wanted to grab multiple data which is stored in 5 tables on in 5th table i am trying to count total number of players that have been joined the tournament this is working fine but the main problem which I am facing here is when there is not data in the main table it still return me 1 row with all fields as null except total players showing 0 as it shown not return any rows can anyone help em out below is my query
$getTournamentData = $this->db->select('tournament.*, tournament_meta.meta_title, tournament_meta.meta_description, categories.title AS category, categories.slug AS category_slug, games.game_name, games.slug AS game_slug, count(tournament_players.id) AS total_players');
$getTournamentData = $this->db->join('categories', 'categories.id = tournament.category_id');
$getTournamentData = $this->db->join('games', 'games.game_id = tournament.game_id');
$getTournamentData = $this->db->join('tournament_meta', 'tournament_meta.post_id = tournament.id');
$getTournamentData = $this->db->join('tournament_players', 'tournament_players.tournamentID = tournament.id', 'left');
$dataCond['created_by'] = $this->session->userdata('user_id');
if($id != null) {
$dataCond['tournament.id'] = $id;
}
$getTournamentData = $this->db->where($dataCond);
$getTournamentData = $this->db->get('tournament');
so in return total_players are showing 0 and rest all is null because no data is insterted in the table yet show it should not return any data from the database
You're mixing an aggregate function (count()) with plain column names in your SELECT clause and that is giving unexpected results.
See: Why can't you mix Aggregate values and Non-Aggregate values in a single SELECT?
You can fix this by adding a GROUP BY clause with all of the column names from the SELECT clause, except for the column name that has the count() on it. Be sure to fully type out all of the column names for the tournament.* in the GROUP BY, so use tournament.id, tournament.category_id, tournament.game_id etc instead:
SELECT tournament.*, tournament_meta.meta_title, tournament_meta.meta_description, categories.title AS category, categories.slug AS category_slug, games.game_name, games.slug AS game_slug, count(tournament_players.id) AS total_players
FROM tournament
JOIN categories ON categories.id = tournament.category_id
JOIN games ON games.game_id = tournament.game_id
JOIN tournament_meta ON tournament_meta.post_id = tournament.id
JOIN tournament_players ON tournament_players.tournamentID = tournament.id
GROUP BY
tournament.id, tournament.category_id, tournament.game_id,
-- add other tournament colums here --
tournament_meta.meta_title, tournament_meta.meta_description, categories.title, categories.slug, games.game_name, games.slug
In CodeIgniter (3) this would translate to:
$this->db->select('tournament.*, tournament_meta.meta_title, tournament_meta.meta_description, categories.title AS category, categories.slug AS category_slug, games.game_name, games.slug AS game_slug, count(tournament_players.id) AS total_players');
$this->db->from('tournament');
$this->db->join('categories', 'categories.id = tournament.category_id');
$this->db->join('games', 'games.game_id = tournament.game_id');
$this->db->join('tournament_meta', 'tournament_meta.post_id = tournament.id');
$this->db->join('tournament_players', 'tournament_players.tournamentID = tournament.id');
$this->db->group_by('tournament.id, tournament.category_id, tournament.game_id,
/* add other tournament columns here */
tournament_meta.meta_title, tournament_meta.meta_description, categories.title, categories.slug, games.game_name, games.slug');
Alternatively you can use a subselect, in which case you can remove the join to the tournament_players table:
SELECT tournament.*, tournament_meta.meta_title, tournament_meta.meta_description, categories.title AS category, categories.slug AS category_slug, games.game_name, games.slug AS game_slug, (
SELECT count(id)
FROM tournament_players
WHERE tournament_players.tournamentID = tournament.id) AS total_players
FROM tournament
JOIN categories ON categories.id = tournament.category_id
JOIN games ON games.game_id = tournament.game_id
JOIN tournament_meta ON tournament_meta.post_id = tournament.id
Use with $this->db->query() in CodeIgniter.
I haven't tested these queries obviously, so there may be errors.
Hopefully this'll help you get started.
I have two tables: categories and videos, I then have a pivot table for these as it's a belongsToMany relationship.
What I'm trying to do is get all of the videos where there isn't a single instance of the video being in one of many categories.
e.g.
Video 1 is in category 1, 2 and 3.
Video 2 is in category 1 and 3.
Video 3 is in category 1.
I want to get the video which is NOT in category 2 or 3, meaning this will return Video 3.
What I've tried so far, which doesn't give the intended result, this is because another row is still found for Video 1 and 2, as they are in Category 1:
Video::whereHas('categories', function($query) {
$query->whereNotIn('category_id', [2,3]);
})->take(25)->get();
The query populated from this is:
select * from `videos` where exists (select * from `categories` inner join
`category_video` on `categories`.`id` = `category_video`.`category_id` where
`videos`.`id` = `category_video`.`video_id` and `category_id` != ? and
`category_id` != ? and `categories`.`deleted_at` is null) and `videos`.`deleted_at`
is null order by `created_at` desc limit 25
You can use Eloquent's whereDoesntHave() constraint to get what you need:
// get all Videos that don't belong to category 2 and 3
Video::whereDoesntHave('categories', function($query) {
$query->whereIn('id', [2, 3]);
})->get();
I'm trying to display a list of books from a table (books) but I want to display the name of the book in green if the user connected added the book in his collection. The collections are in the table collection. So I need to retrieve all the informations from the books table but I need to differentiate the books owned by the user.
I tried something like this :
SELECT * FROM books LEFT JOIN collection ON books.ID = collection.ID_book AND collection.ID_member = :ID_member WHERE books.ID_author = :ID_author
:ID_member is the ID in session of the member logged in and ID_author is the name of the author I want to display the list. But with that query, I cannot retrieve the ID of books that the user doesn't own. Do you have an idea to retrieve these ID too ?
I thought of a second query inside the while() but that would means a query PER book...
EDIT (more informations) :
I have a table books with all the informations concerning the books.
Then, I have a table collection with these columns : ID, ID_member, ID_book
With my query, if I want to display all books from... let's say Stephen King, I have something like this :
book || ID of the book || book owned by the user
1 || 1 || 0
2 || 2 || 0
3 || || 1
4 || 4 || 0
5 || || 1
6 || 6 || 0
The ID I want to display is the collection.ID_book because if I display ID, it will show collection.ID
So as you can see when the user own the book I can't have the ID of the book... but I can display the other informations (like the book title) because the other informations are taken from the books table and the ID from the collection table... Too bad that it's not possible to chose the table in the
I understand that the problem is that the ID column exists in both tables. One solution may be to duplicate the ID column, like ID2. Then it will work because ID2 doesn't exist in the collection table. But that is maybe too much...
Thank you for your help.
Regards.
It is very easy to set columns up with aliases:
SELECT books.ID AS book_id, collections.id AS collection_id
FROM books
LEFT JOIN collection ON books.ID = collection.ID_book AND collection.ID_member = :ID_member
WHERE books.ID_author = :ID_author
After you have done this your column names will be book_id and collection_id and since they are no longer duplicate names (with one overwriting the other) you can access each of them.
Then if you know that you want all the columns from books but only the idcolumn from collection you can do something like this:
SELECT books.*, collections.id AS collection_id
FROM books
LEFT JOIN collection ON books.ID = collection.ID_book AND collection.ID_member = :ID_member
WHERE books.ID_author = :ID_author
I think your query should be doing what you want. I suspect that the problem may be the select * and multiple columns with the same name. Does this query do what you want?
SELECT b.*,
(case when c.ID_book is not null then 'GREEN' else 'NOT OWNED' end) as color
FROM books b LEFT JOIN
collection c
ON b.ID = c.ID_book AND c.ID_member = :ID_member
WHERE b.ID_author = :ID_author;
I think you can solve this problem by changing your query slightly, maybe give this a try.
SELECT * FROM books
LEFT JOIN collection ON books.ID = collection.ID_book
WHERE books.ID_author = :ID_author OR collection.ID_member = :ID_member
When joining on the condition of the user id, you're only going to have those rows available in the result set. By moving that condition to the WHERE clause using the OR operator, you can have both conditions met.
NOTE: Try not to use SELECT *.
SELECT * FROM books LEFT JOIN collection
ON books.ID = collection.ID_book
INNER JOIN member ON collection.ID_member = member.:ID_member WHERE books.ID_author = :ID_author
would you try that sir.. I'm not sure though. I'm assuming that the ID_member fields came from a member table.. and the ID_member to collection table is a foreign key..
I have problem with innerJoin.
Two tables range and product:
table range
id
parent_id
category_id
table product
id
range_id
the query must join range.id with range2.chidren only level 1 and range, range2 with product
ex:
range.id = product.range_id or range2.id = product.range_id:
I want something like :
INNER JOIN product p1_ ON p0_.id = p1_.range_id or p4_.id = p1_.range_id
with doctrine when I write :
->innerJoin('r.products', 'p', Expr\Join::WITH, 'r.id = p.range or rp.id = p.range ')
I got :
INNER JOIN product p1_ ON p0_.id = p1_.range_id AND (p0_.id = p1_.range_id OR p4_.id = p1_.range_id)
someone have solution