How to compare array values with one to many relations in laravel - mysql

I have an input array of submaterials. $submaterials = [1, 2,3]
I am using lumen framework connected with mysql. I have a one to many relation with material and submaterial table. That's a material can have many submaterials.
Here my requirement is to get materials which have same submaterials in input. I only need to get materials which have exact same number of submaterials and same submaterials in input array
This is the query i have added
$submaterials_array = [12,13];
$otherrequests = for ($i = 0; $i < sizeof($submaterials_array); $i++)
{
$id = $submaterials_array[$i];
Offerrequest::whereHas('submaterialprops', function ($q) use ($id) {
$q->where('submaterial_id', $id);
});
}
$otherrequests = $otherrequests->get();
I have tried the query and I got a list of all offerrequests with submaterial id='12,13'. But I want to get only rows which have exact these submaterials(one to many relation). That means it should not return rows with submaterial 12,13,14 or 12,13,14,15,16.

Assuming you have a material model and your one to many relationship defined on the material model is called submaterials and that $submaterials is the array of ids. You could try using https://laravel.com/docs/9.x/eloquent-relationships#querying-relationship-existence
There may be other ways to do this but you could dynamically build the query by looping through the submaterials and adding a whereHas for every submaterial.
$materialQuery = Material::has('submaterials', '=', count($submaterials));
foreach($submaterials as $submaterial){
$materialQuery->whereHas('submaterials', function (Builder $query) use ($submaterial){
$query->where('id',$submaterial);
});
}
$material = $materialQuery->get();

Related

Laravel Query builder where() for when Product has to have multiple tags (with product_tags pivot table many-to-many)

I am new to Laravel and I got a complicated query to build. I managed to sort it except for when a user asks for multiple tags (tags = 1, 2, 3). Product shown has to have all tags that the user asks (not only one or two but all of them).
I have the query in SQL (this example is two tags, I would switch it to different numbers based on how many tags are passed):
SELECT m.*
FROM meals m
WHERE EXISTS (
SELECT 1
FROM meals_tags t
WHERE m.id = t.meals_id AND
t.tags_id IN (227,25)
HAVING COUNT(1) = 2
);
This one works perfectly, but I have an issue when translating it to an Eloquent query builder where method.
I have another where method included in the same query so I want to attach this one as well.
I have tried this:
DB::table('meals')
->select('id')
->where(function ($query) use ($parameters) {
if (isset($parameters['tags'])) {
$array = explode(',', $parameters['tags']);
$query->select(DB::raw(1))
->from('meals_tags')
->where('meals.id', '=', 'meals_tags.meals_id')
->whereIn('meals_tags.tags_id', $array)
->having(DB::raw('COUNT(1)'), '=', count($parameters['tags']));
}
});
But I can't find a way. New to Laravel and PHP.
Let's say I have table meals and tags with meals_tags to connect them (many to many).
$paramaters are comming from GET (...?tags=1,2,3&lang=en&another=something...), they are an array of key-value pairs (['tags' => '1,2,3', 'lang' => 'en'])
$parameters['tags'] is of type string with comma separated numbers (positive integers greater than 0) so that I have the option to explode it if needed somewhere.
Assuming that you have defined belongsToMany (Many-to-Many) meal_tags relationship on the Meal model, you can try
Meal::select('id')
->when(
$request->has('tags'),
function($query) use ($request) {
$requestedTagIds = explode(',', $request->tags);
return $query->whereHas(
'meal_tags',
fn ($query) => $query->whereIn('tags_id', $requestedTagIds),
'=',
count($requestedTagIds)
);
}
)
->get();

compact(): Undefined variable: users

here i am trying to get all the project->id, which match with team->project_id and relation is working but my using variable $users showing undefined. What could be the problem??
here is the controller code
public function index()
{
if (Auth::user()->role->id == 1) {
$projects = Project::all();
$teams = Team::all();
foreach ($teams as $team) {
$users = User::whereIn('id', $team->members)->get();
}
return view('project.index', compact('projects', 'users'));
}
}
You are not initializing the users out of the condition, so if there are no teams, there will be no users:
Try this instead:
$users = collect();
foreach ($teams as $team) {
$users = User::whereIn('id', $team->members)->get();
}
return view('project.index', compact('projects', 'users'));
But keep in mind that your loop will override the users each time, so you will just get the users from the last team only :D
Your problem is in your loop.
There are some blurry parts in your code.
1) In every loop you over-write your $users variable: I will assume that you need an array so try something like:
$users[] = User::whereIn('id', $team->members)->get();
*small note make sure that the column members in the Team table actually holds the ids.
2) The usage of compact() is correct but in case that your $user is empty then you will be trying to get an undefined property from it. In addition with the fact that you over-write your variable if the last itteration is empty then your $user is empty so you just return an empty array in your FrontEnd.
3) Using whereIn means that your column has more than 1 Ids. That's generally a bad practise. You should use a pivot table or store each record in different rows.
4) Predefine your array. If there are no teams, the foreach loop will never be accessed leaving you with a php error of undefined $users.

Laravel Eloquent Limit in Relation that has Sub Relation

I have hasmany relation from category to contents, and I want limitation 4 content for each category.
I would like to limit the result of relation contents that has sub relation to languages
My Code
Category::with(['contents.languages' => function($query){
$query->limit(4);
}])
->get();
But I see in log the limit works on languages relation, not contents, that I wanted is limit on contents
take() and limit() functions will not work with eager loading if you retrieve parent model more than one using get().
So you have to do another way,
$categories = Category::with('contents')->get();
After retrieving $categories, you can do foreach loop like below,
$contents = [];
foreach($categories as $category){
$category->limitedContents = $category->contents()->with('languages')->limit(4);
}
And by doing this you will get 4 contents per category in all categories with limitedContents.
Note: Here I used name as 'limitedContents' because you have already defined contents relationship.
This question is basically something akin to Get top n records for each group of grouped results
As far as I can see there's not much choice but to perform N+1 queries. You can achieve this by doing:
$categories = Category::get();
$categories->each(function ($category) {
$category->load([ 'contents' => function ($q) {
return $q->limit(4);
}, 'contents.languages']);
});
Can we do better? I doubt it doubtful, though I am open to ideas. While we can optimise this to send a less queries to the database, the database internally will still need to compute the N+1 queries.
This also works
foreach(Category::with('contents')->get() as $category)
{
foreach($category->contents->take(4) as $content)
{
$languages = $content->with('languages')->get();
foreach($languages as $language)
{
//your code
}
}
}
You should try this:
Category::with(['contents.languages'])->limit(4)->get();
You could set additional method in Category model and make a call with that one:
public function contentsTake4()
{
return $this->hasMany('App\Content')->limit(4);
}
Then you would call it in controller as :
Category::with('contentsTake4.products')->get();
Try this
Category::with([
'contents' => function($q) { $q->limit(4); },
'contents.languages'
])->get();

mySQL query for building dynamically populated model

I have some code below which demonstrates a hard-coded example of what I would like to accomplish dynamically.
At a high level, I wish to do something like select * from view_data_$app_state and then get all of the data from that views table into my mustache templates dynamically.
The code I currently must use to group multiple rows of data for a specific column along with the views data is:
<?php
error_reporting(E_ALL);
class Example {
function __construct(){
try {
$this->db = new PDO('mysql:host=localhost;dbname=Example', 'root','drowssap');
}
catch (PDOException $e) {
print($e->getMessage());
die();
}
}
function __destruct(){
$this->db = null;
}
function string_to_array($links_string){
return explode(",", $links_string);
}
function get_view_data(){
$q = $this->db->prepare('select *, GROUP_CONCAT(`links`) as "links" from `view_data_global` ');
$q->execute();
$result = $q->fetchAll(PDO::FETCH_ASSOC);
return $result;
}
}
$Example = new Example();
$result = $Example->get_view_data();
$result[0]["links"] = $Example->string_to_array($result[0]["links"]);
echo json_encode($result);
This gives me the perfect object while
GROUP_CONCAT seems to be doing the trick this way, however I MUST know the column name that will contain multiple rows before writing the query. I am trying to figure out an approach for this and wish to make a custom query + code example that will transform cols with multiple rows of null null and not empty data into an array like above - but return the data.. again like the code above.
Below is an output of the actual data:
[{"id":"1","title":"This is the title test","links":["main","about","store"]}];
How can I replicate this process dynamically on each view table?
Thank you so much SO!
You can use PDOStatement::fetch to retrieve your results, with fetch_style set to PDO::FETCH_ASSOC (some other values will also provide the same information). In this case, the result set will be array indexed by column name. You can access this information with foreach (array_expression as $key => $value)
See the documentation for additional information.

Yii JSON with relations

How I can return object with all relations(ans sub objects relations?).
Now I use EJsonBehavior but it returns only first level relations, not sub related objects.
My source code:
$order = Order::model()->findByPk($_GET['id']);
echo $order->toJSON();
Yii::app()->end();
The eager loading approach retrieves the related AR instances together with the main AR instance(s). This is accomplished by using the with() method together with one of the find or findAll methods in AR. For example,
$posts=Post::model()->with('author')->findAll();
The above code will return an array of Post instances. Unlike the lazy approach, the author property in each Post instance is already populated with the related User instance before we access the property. Instead of executing a join query for each post, the eager loading approach brings back all posts together with their authors in a single join query!
We can specify multiple relationship names in the with() method and the eager loading approach will bring them back all in one shot. For example, the following code will bring back posts together with their authors and categories:
$posts=Post::model()->with('author','categories')->findAll();
We can also do nested eager loading. Instead of a list of relationship names, we pass in a hierarchical representation of relationship names to the with() method, like the following,
$posts=Post::model()->with(
'author.profile',
'author.posts',
'categories')->findAll();
The above example will bring back all posts together with their author and categories. It will also bring back each author's profile and posts.
Eager loading may also be executed by specifying the CDbCriteria::with property, like the following:
$criteria=new CDbCriteria;
$criteria->with=array(
'author.profile',
'author.posts',
'categories',
);
$posts=Post::model()->findAll($criteria);
or
$posts=Post::model()->findAll(array(
'with'=>array(
'author.profile',
'author.posts',
'categories',
)
);
I found the solution for that. you can use $row->attributes to create data
$magazines = Magazines::model()->with('articles')->findAll();
$arr = array();
$i = 0;
foreach($magazines as $mag)
{
$arr[$i] = $mag->attributes;
$arr[$i]['articles']=array();
$j=0;
foreach($mag->articles as $article){
$arr[$i]['articles'][$j]=$article->attributes;
$j++;
}
$i++;
}
print CJSON::encode(array(
'code' => 1001,
'magazines' => $arr,
));
This is the best piece of code I found after a long time search to meet this requirement.
This will work like Charm.
protected function renderJson($o) {
//header('Content-type: application/json');
// if it's an array, call getAttributesDeep for each record
if (is_array($o)) {
$data = array();
foreach ($o as $record) {
array_push($data, $this->getAttributes($record));
}
echo CJSON::encode($data);
} else {
// otherwise just do it on the passed-in object
echo CJSON::encode($this->getAttributes($o));
}
// this just prevents any other Yii code from being output
foreach (Yii::app()->log->routes as $route) {
if ($route instanceof CWebLogRoute) {
$route->enabled = false; // disable any weblogroutes
}
}
Yii::app()->end();
}
protected function getAttributes($o) {
// get the attributes and relations
$data = $o->attributes;
$relations = $o->relations();
foreach (array_keys($relations) as $r) {
// for each relation, if it has the data and it isn't nul/
if ($o->hasRelated($r) && $o->getRelated($r) != null) {
// add this to the attributes structure, recursively calling
// this function to get any of the child's relations
$data[$r] = $this->getAttributes($o->getRelated($r));
}
}
return $data;
}