Search item in MySQL tree with many to many association - php

We are using Doctrine 2 for database access with tree extension.
First table is tree table with locations of European Union created from this source.
Location table:
id
name
parent
lft
rgt
lvl
root
companies
There is also company table which holds list of companies with many to many relation to location table. These locations are locations where given company acts. It can be country (level 0), some region (level 1, 2, 3, 4) or just some city, town or vilage (level 5).
Company table:
id
name
locations
Than we have got one search form for companies with location select.
Now I need to create DQL which will select all countries in given location (nested).
So when I choose for example whole country, it should select all companies in this country, in regions in this country and also in cities or towns in this country.
Thank you.
Edit:
I added "auto-adding" (maybe temporary) of parent locations into companies. It will just walk through all parents and add them. So when company acts in city and user choose whole country or region, this company will be displayed.
Unfortunately there is still one problem. If some company acts in whole country and user choose city in this country, company will not be displayed.

Searching is not so expensive and people will search in just few locations in one search query.
So the first thing I do, is to load ids of all searched locations and also ids of all their parents.
Getting parents' ids: BaseTreeFacade::getPathIds
public function getPathIds($entity)
{
$ids = $this->getPathQueryBuilder($entity)
->select('node.id')
->getQuery()
->getResult(AbstractQuery::HYDRATE_SCALAR);
return array_map('current', $ids);
}
this method returns simple array with ids. Now I have to join ids from search query with parents' ids.
Expanding ids: Helpers::expandTreeValues
public static function expandTreeValues(BaseTreeFacade $facade, array $values)
{
$result = [];
foreach ($values as $id) {
$entity = $facade->findOneById($id);
$result[] = [$id];
$result[] = $facade->getPathIds($entity);
}
$result = call_user_func_array('array_merge', $result);
$result = array_unique($result, SORT_NUMERIC);
return $result;
}
Next step is creating where clause for searching in locations and theirs children.
Creating where clause: Helpers::prepareWhereClauseForChildren
public static function prepareWhereClauseForChildren(BaseTreeFacade $facade, array $values)
{
$where = [];
$parameters = [];
foreach ($values as $id) {
$entity = $facade->findOneById($id);
$where[] = "(l.lvl > :level$id AND l.id > :left$id AND l.id < :right$id)";
$parameters["left$id"] = $entity->lft;
$parameters["right$id"] = $entity->rgt;
$parameters["level$id"] = $entity->lvl;
}
$where = implode(' OR ', $where);
return (object) [
'where' => $where === '' ? null : $where,
'parameters' => count($parameters) === 0 ? null : $parameters,
];
}
There I can search in all sub-locations because of left and right branches of tree. Last step is to update original QueryBuilder with where clause created in previous step.
Finalizing QueryBuilder: Helpers::updateWithTreeEntity
public static function updateWithTreeEntity(QueryBuilder $qb, array $values, $addWhere = null, array $addParameters = null)
{
if ($addParameters === null) {
$addParameters = [];
}
$addParameters['locations'] = $values;
$appendQuery = $addWhere === null ? '' : " OR $addWhere";
$qb->andWhere("l.id IN (:locations)$appendQuery");
foreach ($addParameters as $key => $value) {
$qb->setParameter($key, $value);
}
}
Usage example:
public function createQueryBuilderForSearchingInLocations(array $locations)
{
$gb = $this->createQueryBuilderSomehow();
$facade = $this->getLocationsFacadeSomehow();
$locations = Helpers::expandTreeValues($facade, $locations);
$where = Helpers::prepareWhereClauseForChildren($facade, $locations);
Helpers::updateWithTreeEntity($qb, $locations, $where->where, $where->parameters);
return $qb;
}

Related

Symfony query ids from ManyToMany relation table for every entity item

I have two entities Article and Tag with a Many to Many relation. For a bulk edit I query some attributes from every Articles. In that query I also want to query all assigned Tag-IDs from every Article.
I read something about the function GROUP_CONCAT() but Doctrine doesn't support that function yet.
My Doctrine statement is currently like that:
$this->getEntityManager()->createQuery('SELECT e.articleId, e.articleTitle, t.tagId FROM App\Entity\Article e LEFT JOIN e.tags t');
It would be best to fetch those assigned Tag-IDs as an array.
So I extended Doctrine with GROUP_CONCAT function from github.com/beberlei/DoctrineExtensions and use following code to get all IDs as an array:
$query = $this->getEntityManager()->createQuery('SELECT e.articleId, e.articleTitle, GROUP_CONCAT(t.tagId) AS tagIds FROM App\Entity\Article e LEFT JOIN e.tags t GROUP BY e.articleId');
$articleArray = $query->execute();
for ($i = 0; $i < count($articleArray); $i++) {
if (strpos($articleArray[$i]['tagIds'], ',')) {
$arr = array_map('intval', explode(',', $articleArray[$i]['tagIds']));
$articleArray[$i]['tagIds'] = ($arr) ? $arr : [];
}
elseif (!is_null($articleArray[$i]['tagIds'])) $articleArray[$i]['tagIds'] = [(int) $articleArray[$i]['tagIds']];
else continue;
}

Laravel table joins

I'm trying to do some table joins and having some trouble.i need to display
the customer’s complete name and title, item description, quantity ordered, for each item ordered after April 2000. i made a SQL script that works but i need to use Laravel ORM.
SELECT `first_name`,`last_name`,o.order_id,`quantity`,`description`,`date_placed`
FROM `customer` c
JOIN `order` o
ON c.`customer_id` = o. `customer_id`
JOIN `orderline` ol
ON o.`order_id` = ol. `order_id`
JOIN `item` i
ON ol.`item_id` = i. `item_id`
WHERE `date_placed` > '2000-4-00';
I created 2 models for the tables "Customer", "Order"
here is my Customer model
public function orders(){
return $this->hasMany('App\Order','customer_id');
}
here is my order model
public function orderline(){
return $this->hasMany('App\Orderline','order_id');
}
right now i am able to get some of my data but i dont feel like this is a good way to go about
$customer = Customer::all();
foreach($customer as $input){
$item = Customer::find($input->customer_id)->orders;
$name = $input->title . ' ' . $input->first_name . ' ' . $input->last_name;
$datePlaced = null;
$orderID = null;
foreach($item as $value){
$orderID = $value->order_id;
$datePlaced = $value->date_placed;
$order = Order::find($value->order_id)->orderline;
}
if anyone could point me in the right direction that would be great.
It looks like you want to get all Customers with their Orders and OrderLines?
Customer::with(['order' => function ($query) {
$query->where('date_placed', '>=', '2000-04-01')->with('orderline');
}])->get();
If you want to limit the columns on the relationships, you can...
Customer::with(['order' => function ($query) {
$query->select(/* columns */)->where('date_placed', '>=', '2000-04-01')
->with(['orderline' => function ($query) {
$query->select(/* columns here */);
}]);
}])->get();
Just make sure if you specify the columns in the relationships, that you're selecting all of the foreign keys or related columns for each relationship.

Getting null array PHP

i have a database named "products" which has a column "categories". This table contain four category of products namely electronic,Decoration,clothes and vehicle. My target to show these category with their count ie:if there are four products belongs to category electronic, then output should be like this :electronic=4 and so on
My code
public function category()
{
$arrayCategorys = ['electronic','Decoration','clothes','vehicle'];
$data = [];
foreach($arrayCategorys as $arrayCategory)
{
$sql = "SELECT count(id) FROM products WHERE categories='$arrayCategory'";
$records = \DB::select($sql);
$data = array_merge_recursive($data, [
"{$arrayCategory}" =>isset($records[0]->count),
]);
$data=array_filter($data);
dd($data);
}
}
I want show output like this
'electronic'=>'4',
'Decoration'=>'2',
'clothes'=>'2',
'vehicle'=>'1' according to data in database
but iam getting nothing ,[]
You can GROUP BY your categories like this way when you COUNT
SELECT categories,COUNT(*)
FROM products
GROUP BY categories;
For Idea: http://www.w3resource.com/mysql/aggregate-functions-and-grouping/aggregate-functions-and-grouping-count-with-group-by.php
EDIT: Though i am not familiar with laravel5 syntax but this may work for you
$result = DB::table('products')
->select('products.categories',
DB::raw('count(products.id) as category_count')
)
->orderBy('products.id', 'asc')
->groupBy('products.categories')
->get();
You used isset($records[0]->count) but the column name for the count will be count(id). Name the count as count like this "SELECT count(id) AS count FROM products WHERE categories='$arrayCategory'". And you wont be able to get the count just by checking if it is set. Remove the isset and just use $records[0]->count. The code should look like:
public function category()
{
$arrayCategorys = ['electronic','Decoration','clothes','vehicle'];
$data = [];
foreach($arrayCategorys as $arrayCategory)
{
$sql = "SELECT count(id) AS count FROM products WHERE categories='$arrayCategory'";
$records = \DB::select($sql);
$data = array_merge_recursive($data, [
"{$arrayCategory}" =>$records[0]->count,
]);
$data=array_filter($data);
dd($data);
}
}

Select posts based on selected categories

I have following scenario:
user selects couple categories
user should see posts which belongs to these categories
post belong to one or more categories
I setup the database like this:
users -> category_user <- categories
posts -> categoriy_post <- categories
I managed to accomplish this, but I had to find all ids from these tables to find relevant posts. I need to make it simpler because this approach is blocking some other actions I need to do. This is my code:
$categoryIds = Auth::user()->categories;
$ids = array();
$t = array_filter((array)$categoryIds);
if(!empty($t)){
foreach ($categoryIds as $key => $value) {
$ids[] = $value->id;
}
}else{
return View::make("main")
->with("posts", null)
->with("message", trans("front.noposts"))->with("option", "Latest");
}
$t = array_filter((array)$ids);
if(!empty($t)){
$p = DB::table("category_post")->whereIn("category_id", $ids)->get();
}else{
return View::make("main")
->with("posts", null)
->with("message", trans("front.noposts"))->with("option", "Latest");
}
$postsIds = array();
foreach ($p as $key => $value) {
$postsIds[] = $value->post_id;
}
$t = array_filter((array)$postsIds);
if(!empty($t)){
$postIds = array_unique($postsIds);
$posts = Post::whereIn("id", $postsIds)
->where("published", "=", "1")
->where("approved", "=", "1")
->where("user_id", "!=", Auth::user()->id)
->orderBy("created_at", "desc")
->take(Config::get("settings.num_posts_per_page"))
->get();
return View::make("main")
->with("posts", $posts)->with("option", "Latest");
}else{
return View::make("main")
->with("posts", null)
->with("message", trans("front.noposts"))->with("option", "Latest");
}
How to do this properly without this bunch code?
Yes, there is Eloquent way:
$userCategories = Auth::user()->categories()
->select('categories.id as id') // required to use lists on belongsToMany
->lists('id');
if (empty($userCategories)) // no categories, do what you need
$posts = Post::whereHas('categories', function ($q) use ($userCategories) {
$q->whereIn('categories.id', $userCategories);
})->
... // your constraints here
->get();
if (empty($posts)) {} // no posts
return View::make() ... // with posts
Or even better with this clever trick:
$posts = null;
Auth::user()->load(['categories.posts' => function ($q) use (&$posts) {
$posts = $q->get();
}]);
if (empty($posts)) // no posts
return View... // with posts
Obviously, you can write joins, or even raw sql ;)
You can take those categories directly from the database from user records:
SELECT ...
FROM posts AS p
INNER JOIN category_post AS cp ON cp.id_post = p.id
INNER JOIN categories AS c on c.id = cp.id_category
INNER JOIN category_user AS cu ON cu.id_category = c.id
WHERE cu.id_user = 123 AND p.published = 1 AND ...
Joins in Laravel can be achieved, see the documentation: laravel.com/docs/queries#joins Maybe there is also an Eloquent way, I don't know, try searching :-)

Codeigniter 2.1 - MySQL JOIN

I have two tables:
MENU
->id_menu
->title
->page_id
->order
PAGES
->id_page
->title
->page
->slug
This is select function:
public function get_all_menu()
{
return $this->db
->select('menu.*, pages.id_page, pages.title AS page_title')
->from($this->table)
->join('pages','id_page = page_id')
->order_by($this->order_by)
->get()
->result_array();
}
Here is my problem - item in the menu can be connected with page, but also it can be solo (without connection to page). This means page_id in MENU table can be 0. If page_id is 0, I am not getting that row from above query. How can I get all items in the menu (those which are connected and those which are not connected with page )?
You need add the join type in ->join()
like
public function get_all_menu()
{
return $this->db
->select('menu.*, pages.id_page, pages.title AS page_title')
->from($this->table)
->join('pages','id_page = page_id','left')
->order_by($this->order_by)
->get()
->result_array();
}
See reference manual Active Record
As requested from comments:
You need to use "LEFT JOIN" to retrieve partial or incomplete values from the tables.
In this case, the regular join is looking the matches between the two tables. Since there's no id 0 in one of the tables, the match can't be retrieved and the row won't be selected.
Using a LEFT JOIN (or RIGHT JOIN when appropriate, based on which one is the "incomplete" table) if the match isn't retrieved, the outcome will contain also the rows which aren't matched, with all NULLs in the other table's values
From Your Controller call this method in Model Say your Model Name is general_model
$value=('menu.*, pages.id_page, pages.title AS page_title');
$joins = array
(
array
(
'table' => 'pages',
'condition' => 'menu.id_page = pages.page_id',
'jointype' => 'leftouter'
),
);
$where_condition=array('menu.Id '=>'Enable','restaurant.Id'=>$rest_id);
$data['detail'] = $this->general_model->get_joins('menu',$value,$joins,$where_condition,'menu.Id','asc')->result();
here $where_condition is a where condition you want to pass if u want to remove you can remove it from function.,And Last two parameters are orderby
Simply use this function in your Model For Join
public function get_joins($table,$value,$joins,$where,$order_by,$order)
{
$this->db->select($value);
if (is_array($joins) && count($joins) > 0)
{
foreach($joins as $k => $v)
{
$this->db->join($v['table'], $v['condition'], $v['jointype']);
}
}
$this->db->order_by($order_by,$order);
$this->db->where($where);
return $this->db->get($table);
}

Categories