I have three models related through hasMany relationship: Course/Lesson/Article - A Course hasMany Lessons which hasMany Articles.
I have an int column in my articles table named pos (short for position) and I want to order the articles by it.
I am using the following query in my CoursesController, but it's not sorting the articles by this pos attribute:
Code:
public function show(Course $course, Lesson $lessons, Article $articles)
{
$articles = $lesson->articles()->orderBy('pos', 'asc')->get();
return view('users.courses.show', compact('course', 'lessons', 'articles'));
}
I'm using a foreach loop in blade:
#foreach($lesson->articles as $article)
{{ $article->title }}
#endforeach
Any help would be appreciated!
Laravel debugbar shows the following result:
select * from articles where slug = 'this-is-article-one' limit 1
13.27ms\vendor\laravel\framework\src\Illuminate\Routing\ImplicitRouteBinding.php:35
select * from articles where lesson_id = 1 and pos < 2 order by pos desc limit 1 660μs\app\Http\Controllers\Users\ArticlesController.php:55
select * from articles where lesson_id = 1 and pos > 2 order by pos asc limit 1 520μs \app\Http\Controllers\Users\ArticlesController.php:59
select * from courses where courses.id = 2 limit 1 610μs view::users.articles.show:7
select * from lessons where lessons.id = 1 limit 1 530μs
view::users.articles.show:8
select * from articles where articles.lesson_id = 1 and articles.lesson_id is not null
When you call $lesson->articles again in the show view, basically you make a new DB call. To get the correct values, use the variable $articles:
#foreach($articles as $article)
{{ $article->title }}
#endforeach
If you want to continue using your lesson object in your view, use sortBy:
#foreach($lesson->articles->sortBy('pos') as $article)
{{ $article->title }}
#endforeach
I will suggest to extend you relationship with orderBy()
<?php
public function articles(){
return $this->hasMany('App\Articles')->orderBy('pos','asc');
}
Related
I want to be able to order my articles based on their rating which I solved using the following query:
return Article::where('private', 0)
->whereIn('movie_id', $movie)
->with(['user', 'movie', 'ratings'])
->withCount(['ratings as average' => function ($query) {
$query->select(\DB::raw('coalesce(avg(value), 0)'));
}])
->orderByDesc('average')
->paginate(10);
The problem is however if an article only has 1 or 2 ratings/votes and they are all positive ratings then the article will rank with other top articles that have many more ratings/votes which gives inaccurate results. How would I limit it only to articles with let's say more than 5 votes? Is there any other other better solution?
If ratings is a relation hasMany, you can simply use :
return Article::where('private', 0)
->has('ratings', '>=', 5)
...
To only get articles with 5 or more ratings.
You can grab all articles too in your query, with number of ratings :
return Article::where('private', 0)
->withCount('ratings')
...
And you can use a simple condition in your loop to show ratings only for Articles with 5 or more ratings :
#foreach ($articles as $article)
#if ($article->count_ratings >= 5)
// show rating
#endif
#endforeach
I have the following 3 tables:
Movie
- id
Series
- id
- status_id
- movie_id
Status
- id
- order_number
this is the code on the controller:
$movie = Movie::where('slug', $slug)->with(['series'])->first();
this is the code for view:
#foreach ($movie->series as $series)
{{ $series->name}}
#endforeach
how to sort $movie->series based on status->order_number? if it can be written on the model, so every order is only written once the controller?
is there a code that I can use for example like this:
$movie->series->sortBy('status.order_number');
Yes, but you will need to join status with series:
$movie = Movie::where('slug', $slug)->with([
'series' => function ($query) {
// Subquery on `series` table
return $query
// select whatever you'll need
->select('series.*')
// join with status
->join('status', 'series.status_id', '=', 'status.id')
// order by order number
->orderBy('status.order_number')
// * you can drop this if you select all the fields you need and use those
->with('status');
},
])->first();
Edit this ^ method will sort on SQL level, but you could also do this with collections:
#foreach ($movie->series->sortBy('status.order_number') as $series)
{{ $series->name}}
#endforeach
In that case also add .status to your with to avoid n + 1 problem: ->with(['series.status'])
The reason your attempt didn't work is because ->sortBy(..) doesn't mutate the collection, it just returns a new sorted one. This would work:
$movie->series = $movie->series->sortBy('status.order_number');
hello I have two tables one is categories another is ads_listings. In categories table there are 4 columns id, parent_category_id, category_slug, category_title. Here parent_category_id 0 indicates main category and rest of the thing indicates sub category. user post store in ads_listings table. Now I want to find post with specific categories like this
vehicles(3)
cars(2)
motorbike(1)
cycle(0)
here the problem is my code found only subcategories which have posts. my controller code is
public function countListingsByCategories()
{
return DB::table("ads_listings")
->select("categories.category_title",DB::raw("COUNT(ads_listings.category_id) as num_listings"))
->join("categories", "categories.id","=","ads_listings.category_id")
->groupBy("ads_listings.category_id")
->get();
}
views code is:
<ul class="row catelist">
#if(isset($categoriesNumListings) && count($categoriesNumListings))
#foreach($categoriesNumListings as $categoriesNumListings)
<li class="col-md-12">{{ $categoriesNumListings->category_title }} <span>({{ $categoriesNumListings->num_listings }}Listings )</span></li>
#endforeach
#endif
</ul>
my Category table is:
ads_listing table is:
I think the problem is that you are counting ad_listings when you primarily want to count categories and get the number of ad_listings, I would do something like:
Second shot :)
public function countListingsByCategories()
{
return DB::table("categories")
->select("categories.category_title",
DB::raw("categories.title,
CASE
WHEN (categories.parent_id = 0)
THEN (SELECT count(ads_listings.category_id)
FROM ads_listings
WHERE ads_listings.category_id in
(SELECT subcategory.id
FROM categories subcategory
WHERE subcategory.parent_id = categories.id))
ELSE (SELECT COUNT(ads_listings.category_id)
FROM ads_listings
WHERE ads_listings.category_id = categories.id)
END as numberOfPosts,
CASE
WHEN (categories.parent_id = 0)
THEN (select 'Category')
ELSE (select 'subCategory')
END as type
FROM categories"))
->get();
}
I have a table with products
products
------------------------------------------------
id product_code purchase_type quantity
1 106 new 1
2 107 renew 3
and a pricelist for products
price_list
----------------------------------------------------------------------
id product_code purchase_type minimum maximum unit_price
1 106 new 1 25 20
2 106 new 26 50 16
3 106 new 51 100 14
When I display the products on a page, I need to display the price with the formula
getPrice(product_code, purchase_type, quantity) for every product
Using
foreach(Product::all() as $product){
{{ $product->product_code }}
{{ $product->purchase_type }}
{{ $product->quantity }}
{{ PriceList::getPrice($product->product_code, $product->purchase_type, $product->quantity) }} - this one is the n+1 problem
}
PriceList::getPrice() is something like:
public static function getPrice($productCode, $purchaseType, $quantity){
return PriceList::where('product_code', $productCode)
->where('purchase_type', $purchaseType)
->where('minimum', '>=', $quantity)
->where('maximum', '<=', $quantity)
->get()->first()->unit_price;
}
I can't seem to find a more efficient way.
And when I display over 1000 products, then it gets really slow.
I can't seem to find a way to use eager loading.
It is not a good idea to call model from inside loop to get data.
Instead of doing that, follow below way.
You should define relationship between Product and PriceList tables.
In Model Product.php
public function price_list(){
return $this->hasMany('PriceList','product_code');
}
In Model PriceList.php
public function product(){
return $this->belongsTo('Product','product_code');
}
In Controller File
$products = Product::with('price_list')->get();
return View::make('your_view')->with('products', $products);
In View File (file must have extention .blade.php to use below syntax)
#foreach($products as $product)
{{ $product }}
#endforeach
Note:- Print $products in controller to check results.
I found a way with mysql,
SELECT
`products`.`id` AS `product_id`,
`products`.`product_code` AS `product_product_code`,
`products`.`purchase_type` AS `product_purchase_type`,
`products`.`quantity` AS `product_quantity`,
`price_list`.`product_code` AS `price_list_product_code`,
`price_list`.`purchase_type` AS `price_list_purchase_type`,
`price_list`.`minimum` AS `price_list_minimum`,
`price_list`.`maximum` AS `price_list_maximum`,
`price_list`.`unit_price` AS `price_list_unit_price`
FROM
`products`
LEFT JOIN `price_list` ON `products`.`product_code` = `price_list`.`product_code`
AND `products`.`purchase_type` = price_list.purchase_type
AND `products`.`quantity` >= price_list.minimum
AND `products`.`quantity` <= price_list.maximum
ORDER BY
`products`.`id` ASC
http://sqlfiddle.com/#!9/5d6ac/2
I've got 4 tables:
My relationships should work like this:
Items can only have one size, color and category.
This should be working but it's really not. The query generated returns wrong results.
Here are my model files:
<?php
class Shop_Item extends Eloquent
{
public static $table = 'items';
public static $timestamps = false;
public function scategory() {
return $this->has_one('Shop_Category','id');
}
public function ssize() {
return $this->has_one('Shop_Size','id');
}
public function scolor() {
return $this->has_one('Shop_Color','id');
}
}
The rest of the model files for the remaining tables are the same (except table name and model name).
<?php
class Shop_Category extends Eloquent
{
public static $table = 'item_categories';
public static $timestamps = false;
}
So when I try to access the extra values (size->name, color->name, category->name), I get wrong results.
I have two test records in my database:
Item 1 and Item 2 with different color, size and category.
Item 1 is blue and have the size of M,
Item 2 is green and have the size of XL, but not in the returned query. Which shows me, that Item 2 is red and have the size of S.
Controller:
<?php
class Admin_Shop_Controller extends Base_Controller {
public function action_index() {
$items = Shop_item::order_by('name')->paginate(10,array('id','name','price','sex','visible','color','size','category'));
return View::make('admin.shop.index')->with('items', $items);
}
View:
#forelse($items->results as $i)
{{ $i->name }}
{{ $i->price }}
{{ $i->sex }}
{{ $i->scategory->name }}
{{ $i->scolor->name }}
{{ $i->ssize->name }}
Edit
Delete
#empty
There are no items in the shop.
#endforelse
Queries generated:
0.23ms
SELECT COUNT(`id`) AS `aggregate` FROM `items`
0.28ms
SELECT `id`, `name`, `price`, `sex`, `visible`, `color`, `size`, `category` FROM `items` ORDER BY `name` ASC LIMIT 10 OFFSET 0
0.25ms
SELECT * FROM `item_categories` WHERE `id` IN ('1', '2')
0.21ms
SELECT * FROM `item_sizes` WHERE `id` IN ('1', '2')
0.36ms
SELECT * FROM `item_colors` WHERE `id` IN ('1', '2')
Note that in the view if I access these values from the other table like this:
{{ Shop_Color::find($i->color)->name }}
It gets me the right result, but I really don't want to query the database n+3 times because of this. Any suggestions what am I doing wrong?
Edit: Still no luck. :( I've done the changes you listed, experimented with them but this thing still not working. Current error is :
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'id' in 'where clause'
SQL: SELECT * FROM `item_colors` WHERE `id` IN (?)
Bindings: array (
0 => 0,
)
I don't know why it looks for an id, I've changed all references to the child tables and renamed columns appropriately. :(
Number of queries !== Performance. In your case, you're doing nearly-atomic queries (the where id in are all done vs. primary indices, which will make them fast - as fast as a JOIN if you exclude the returning-result process time). If you nevertheless want to solve the n+1 query problem, Eloquent has a solution for this: eager-loading.
Instead of this:
Shop_item::order_by('name')
Use this:
Shop_item::with(array("scategory", "ssize", "scolor"))->order_by('name')
You should see only one query by using this. The doc for this feature is at: http://laravel.com/docs/database/eloquent#eager
There are quite a few things going on here.
I would name your reference columns thing_id... so that's color_id, size_id and category_id. This will allow you to set up relationships named 'color', 'size' and 'category', instead of 'sthing'.
You need belongs_to() instead of has_one(). Laravel assumes that the ID will be like thing_id, so if you've updated as 1 above then you can update your references like $this->belongs_to('Shop_Size'). If not, then you should use the reference column here, like $this->belongs_to('Shop_Size', 'size').
When you use Eloquent models it's best not to restrict the columns - you might have logic in your model that depends on them all being there.
You can use eager loading to improve the queries, but the way Eloquent works it will still need a query per relationship. Have a look at this line for action_index()
$items = Shop_Item::with(array('color', 'size', 'category'))
->order_by('name')->paginate(10);
After all of the edits above you will be able to write code like this in your view...
#forelse ($items->results as $item)
<p>Color: {{ $item->color->name }}</p>
#else
<p class="no-results">There are no items in the shop.</p>
#endforelse
Most of this is covered in the Eloquent docs, particularly Relationships and Eager Loading.