I have Service entity with title, tags and description fields. When searching service with QueryBuilder how can I get results sorted by field priority. For example, when I search term php I want get services with php in their title at the top of the list, then services with php in their tags and services with search term in their description as last.
This is part of my Querybuilder:
$qb = $this->createQueryBuilder('service');
$qb->leftJoin('service.tags', 'tag');
$conditions = array($conditions[] = $qb->expr()->eq('service.enabled', true));
$conditions[] = $qb->expr()->eq('service.category', $categoryId);
$term = '%' . $term . '%';
$conditions[] = $qb->expr()->orX(
$qb->expr()->like('service.title', "'$term'"),
$qb->expr()->like('service.description', "'$term'"),
$qb->expr()->like('tag.name', "'$term'")
);
$conditions = call_user_func_array(array($qb->expr(), 'andX'), $conditions);
$qb->where($conditions);
The best way to do this would be to perform a series of UNION statements and then weed out the duplicates at the same time giving weight.
(Unchecked pseudo-SQL to give you the idea):
SELECT id,title,tag,SUM(weight) score
FROM (
SELECT id,title,tag, 100 as weight FROM service WHERE title LIKE '%foo%'
UNION ALL
SELECT id,title,tag, 10 as weight FROM service WHERE tags LIKE '%foo%'
UNION ALL
SELECT id,title,tag, 1 as weight FROM service WHERE description LIKE '%foo%'
) t
GROUP BY id
ORDER BY score DESC /* This sort by probably won't work; might need to do it a different way, but you get the idea */
You can use native query for this. Ex.:
$em = $this->get('doctrine')->getManager();
$sql = "
select *
from service s
where
s.title like '%xxx%'
or s.tags like '%xxx%'
or s.description like '%xxx%'
order by
s.title like '%xxx%' desc,
s.tags like '%xxx%' desc,
s.description like '%xxx%' desc
";
$rsm = new \Doctrine\ORM\Query\ResultSetMappingBuilder($em);
$rsm->addRootEntityFromClassMetadata('\You\Entity\Service\Class', 's');
$query = $em->createNativeQuery($sql, $rsm);
$data = $query->getResult();
dump($data);
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/native-sql.html how to use sql insted of dql in orm
I would not try get the desired results in only one query. It will be too complicated and you'll spent to much time trying get what you want.
First, I assume you'd like to get a sorted Array of Service entities like this:
/** #var Service[] $results **/
$results = $this->getDoctrine()->getManager()->getRepository('YourBundle:YourServiceRepo')->findSerivesByGivenSerchCriteria($yourConditions);
so you can simply iterate (foreach) through it
If it's the case, concider following:
create three separate methods whitin your service-repository to get
services with search-criteria in their title - first priority
services with search-criteria in their tags - second priority
services with serach-criteria in their desc - third priority
and in your findSerivesByGivenSearchCriteria()method you call all three of them and could combine the found results in any order i'd like
for Example:
public function findSerivesByGivenSerchCriteria( $searchTirm ) {
$foundServicesByTitle = $this->findServicesBySearchCriteriaInTitle( $searachTirm );
$foundServicesByTag = $this->findServicesBySearchCriteriaInTags( $searchTirm );
$foundServicesByDesc = $this->findServicesBySearchCriteriaInDesc( $searchTirm );
// Hier you can combine found results in any order
$results = [];
if( false === empty($foundServicesByTitle ) )
{
// for example with array_merge ...
// NOTE: If you choose array_merge() you have to make sure your $foundServicesByTitle, $foundServicesByTag, $foundServicesByDesc have unique array-indexes
// And you get Results like
// array( 'unique_id_1' => ServiceEntity, 'unique_id_2' => ServiceEntity ... )
$results = array_merge($results, $foundServicesByTitle);
}
// .. do the same with other two
return $results;
}
To get the "uniqness" try to combine Doctrine's INDEX BY and HIDDEN
INDEX BY -> http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#using-index-by
INDEX BY in QueryBuilder -> https://stackoverflow.com/a/15120793/348193
HIDDEN -> https://michelsalib.com/2012/03/04/doctrine2-ninja-with-the-hidden-keyword/
Why I wouldn't choose the UNION way?
Since you work with symfony and doctrine UNION isn't the best way. See How to write UNION in Doctrine 2.0
As you can see, UNION isn't supported by Doctrine and you have to use NativeQuery (which could be frustrating with ResultSetMappingBuilder and corect mapping and turning raw-results in to desired entities)
Related
Have two SQLite tables:
sh_item
id
title
..
and
sh_paramvalues
id
paramid
itemid
value
Have some query that I need to modify
//$get[0] - search query from user
$this->q = "SELECT *
FROM sh_item
WHERE title LIKE '%".$get[0]."%'
OR descr LIKE '%".$get[0]."%'
LIMIT '".$this->page->start."','".$this->page->on1page."'";
I need some query that could get value ( LIKE '%".$get[0]."%') from sh_paramvalues where paramid='some my value' and itemid = id which use sh_item in base query.
Maybe I need to use join but I am not good at that.
Try:
//$get[0] - search query from user
$this->q = "SELECT *
FROM sh_item si
INNER JOIN sh_paramvalues sp
ON si.id = sp.itemid
WHERE (si.title LIKE '%".$get[0]."%'
OR si.descr LIKE '%".$get[0]."%'
OR sp.value LIKE '%".$get[0]."%')
AND sp.paramid = 3
LIMIT '".$this->page->start."','".$this->page->on1page."'";
I need to get the percentage of each possible values in the field column, over the total value of my table.
I found two way to get my result in SQL:
SELECT m.field, sum(m.value) * 100 / t.total
FROM my_table AS m
CROSS JOIN (
SELECT SUM(value) AS total FROM
WHERE year = 2000) t
WHERE m.year = 2000
GROUP BY m.field, t.total
And
SELECT m.field, sum(m.value) * 100 / (SELECT SUM(value) AS total FROM WHERE year = 2000)
FROM my_table AS m
WHERE m.year = 2000
GROUP BY m.field
But both are nested queries, and I don't know how to prepare statments with the Doctrine's QueryBuilder into a nested queries.
Is there a way to do it?
I have been trying to do so using querybuilder and DQL with no success. As it seems, DQL doesn't allow operations with subqueries in SELECT. What I've achieved so far:
$subQuery = $em->createQueryBuilder('m')
->select("SUM(m.value)")
->where("m.year = 2000")
->getDQL();
The following query works though doesn't calculate the percentage:
$query = $em->createQueryBuilder('f')
->select("f.field")
->addSelect(sprintf('(%s) AS total', $subQuery))
->addSelect('(SUM(f.value)*100) AS percentage')
->where("f.year = 2000")
->groupBy("f.field")
->getQuery()
->getResult();
However, if you try to add the division in the select in order to get the percentage and you use the subquery, it simply doesn't work. Looks like the construction it's not allowed in DQL. I've tried with an alias and with the subquery directly and neither of them worked.
Doesn't work:
$query = $em->createQueryBuilder('f')
->select("f.field")
->addSelect(sprintf('(%s) AS total', $subQuery))
->addSelect('(SUM(f.value)*100)/total AS percentage')
->where("f.year = 2000")
->groupBy("f.field")
->getQuery()
->getResult();
Doesn't work either:
$query = $em->createQueryBuilder('f')
->select("f.field")
->addSelect(sprintf('(SUM(f.value)*100)/(%s) AS percentage', $subQuery))
->where("f.year = 2000")
->groupBy("f.field")
->getQuery()
->getResult();
I'd suggest using SQL directly (Doctrine allows it). Using native sql queries and mapping the results would do the trick. There is no disadvantage in doing so.
Documentation
If you find a way of doing it using queryBuilder or DQL, please let me know.
Hope it helps.
yeah! the solution is:
$qs = $this
->createQueryBuilder('h');
$d = $qs ->select($qs->expr()->count('h'));
$e = $d->getQuery()->getScalarResult();
$qs->addSelect('(COUNT(h.id)*100 / :t) AS percentage')->setParameter('t', $e);
$qs->addGroupBy(sprintf('h.%s', $type));
return $qs->getQuery()->getResult();
I implemented my project in Yii. I wrote query for single table like query with condition.
I want to write query to call from the two different table. I've tables are recipe and ingredient table. Recipe table I've more than 7 fields. recipe_id, cuisinename, course_id, type etc. another table ingredient. id, ingredient_name.
$result="SELECT * FROM recipe WHERE name LIKE '%$name%' AND `cuisinename` LIKE
'$cuisine1%' AND course_id LIKE '%$course1%' AND `type` LIKE '%$type1%'
AND `calorie_count` LIKE '$calorie1%' ORDER BY recipe_id DESC";
I wrote this condition. my search function is working very well. But I want to display from the other table i.e. ingredient table also.
could I write like query??
Sounds like you need a JOIN. However I would imagine Recipe and Ingredients would have a many to many relationship in your database. You'd need to first make an associative table for them and then use an INNER JOIN to link up the 3 tables.
Something like this:
SELECT /* you stuff */
FROM recipe_ingredients ri
INNER JOIN recipe r ON ri.recipe_id = r.id
INNER JOIN ingredients i ON ri.ingredient_id = i.id
WHERE /* do you like stuff here */
Further to my comment, you could try this;
/* first query */
$query = "SELECT * FROM recipe WHERE name LIKE '%$name%' AND `cuisinename` LIKE '$cuisine1%' AND course_id LIKE '%$course1%' AND `type` LIKE '%$type1%' AND `calorie_count` LIKE '$calorie1%' ORDER BY recipe_id DESC";
$result = $mysqli->query($query);
/* numeric array */
$ret1 = $result->fetch_array(MYSQLI_NUM);
/* second query */
$query = "Whatever is in your second query?"; //add your second query here..
$result = $mysqli->query($query);
/* numeric array */
$ret2 = $result->fetch_array(MYSQLI_NUM);
/*final result set */
$result = array_merge((array)$ret1, (array)$ret2);
If you are using Yii, use query builder http://www.yiiframework.com/doc/guide/1.1/en/database.query-builder
Query will look like
Yii::app()->db->createCommand()
->select('*, i.ingridient_name')
->from('recipe t')
->join('ingredients i', 'i.recipe_id=t.id')
->where('name LIKE :name AND `cuisinename` LIKE
:cuisine1 AND course_id LIKE :course1 AND `type` LIKE :type1
AND `calorie_count` LIKE :calorie1', array(
':name'=>'%'.$name.'%',
':cuisine1'=>'%'.$cuisine1.'%',
))
->order('recipe_id DESC')
->queryAll();
I'm trying to filter some car parts depending on the categories they are related to.
A part can have many categories (in the code they are called tags), so I chose the HABTM relation with a join table.
Filtering works so far, but only with an OR condition with cake using the SQL command IN.
But I'm trying to filter only the parts that have all the selected categories, so I need to use an AND condition on the category array.
Here's the extracted code from the controller:
$this->Part->bindModel(array('hasOne' => array('PartsTagsJoin')));
$params['conditions'] = array('AND' => array('PartsTagsJoin.tag_id' => $selectedCats));
$params['group'] = array('Part.id');
$parts = $this->Part->find('all',$params);
$this->set('parts',$parts);
$selectedCats is an array like this: array(1,2,3,4,5);
The SQL output is:
'SELECT `Part`.`id`, `Part`.`name`, `Part`.`image`, `Part`.`image_dir`, `Part`.`time_created`, `Part`.`time_edited`, `Part`.`user_id`, `Part`.`editor_id`, `Part`.`notice`, `User`.`id`, `User`.`username`, `User`.`password`, `User`.`usergroup_id`, `User`.`realname`, `PartsTagsJoin`.`id`, `PartsTagsJoin`.`part_id`, `PartsTagsJoin`.`tag_id`
FROM `c33rdfloor`.`parts` AS `Part`
LEFT JOIN `c33rdfloor`.`users` AS `User` ON (`Part`.`user_id` = `User`.`id`)
LEFT JOIN `c33rdfloor`.`parts_tags_join` AS `PartsTagsJoin` ON (`PartsTagsJoin`.`part_id` = `Part`.`id`)
WHERE `PartsTagsJoin`.`tag_id` IN (1, 4, 8, 24)'
How can I filter the parts that have every id that is committed through the $selectedCats Array.
Thank you in advance for your help.
I've got it working thanks to this blog post:
http://nuts-and-bolts-of-cakephp.com/2008/08/06/habtm-and-join-trickery-with-cakephp/
It seems to be a little tricky to filter entries with all selected tags:
The key in achieving an AND relation is to get the count of the selected cats and match it with the ones of the query inside the group parameter.
This line did it:
$params['group'] = array('Part.id','Part.name HAVING COUNT(*) = '.$numCount);
In the End the code looked like this (for people interested in a solution):
// Unbinds the old hasAndBelongsToMany relation and adds a new relation for the output
$this->Part->unbindModel(array('hasAndBelongsToMany'=>array('PartsTag')));
$this->Part->bindModel(array('hasOne'=>array(
'PartsTagsJoin'=>array(
'foreignKey'=>false,
'type'=>'INNER',
'conditions'=>array('PartsTagsJoin.part_id = Part.id')
),
'PartsTag'=>array(
'foreignKey'=>false,
'type'=>'INNER',
'conditions'=>array(
'PartsTag.id = PartsTagsJoin.tag_id',
'PartsTag.id'=>$selectedCats
)))));
$numCount = count($selectedCats); // count of the selected tags
// groups the entries to the ones that got at least the count of the selected tags -> here the 'AND' magic happens
$params['group'] = array('Part.id','Part.name HAVING COUNT(*) = '.$numCount);
$parts = $this->Part->find('all', $params); // sends the query with the params
$this->set('tagCategories', $categories);
$this->set('selectedCats', $selectedCats);
$this->Part->recursive = 4;
$this->set('parts',$parts);
Hey, I think I'm going about this the wrong way, but have tried many ways, none of which give me the desired results.
Basically I have one search field which is to check multiple tables. My issue is that when, say, an item doesn't have a related person, it won't return a result for that item. Other than that it is fine. I realize the problem is in the WHERE, demanding that it find only records that match in the other table, but I'm not sure how to rectify this.
Thanks!
$q = "SELECT DISTINCT item.*
FROM item, item_people, people
WHERE item.record_id = item_people.item_id AND people.record_id = item_people.people_id
AND
item.live = '1'
AND
(concat_ws(' ',people.name_first,people.name) LIKE '%$search_param%' OR
item.name_title LIKE '%$search_param%' OR
item.city = '$search_param' OR
item.category LIKE '%$search_param%' OR
item.on_lists LIKE '%$search_param%')
$limit";
You would need an OUTER JOIN to return items without related people.
SELECT DISTINCT item.* /*But don't use *!
*/
FROM item
LEFT OUTER JOIN item_people
ON item.record_id = item_people.item_id
LEFT OUTER JOIN people
ON people.record_id = item_people.people_id
WHERE item.live = '1'
AND
(
concat_ws(' ',people.name_first,people.name) LIKE '%$search_param%'
OR item.name_title LIKE '%$search_param%'
OR item.city = '$search_param'
OR item.category LIKE '%$search_param%'
OR item.on_lists LIKE '%$search_param%'
)
$limit