MySql Query build using Kohana's ORM - mysql

I'm having a hard time to translate the following query into kohana's ORM.
So, if I do the following works fine:
$query = DB::query(Database::SELECT, 'SELECT id_book, MATCH(title, author, isbn) AGAINST (:str) AS score FROM tab_books WHERE status = 1 AND MATCH(title, author, isbn) AGAINST (:str) HAVING score > '.$score.' ORDER BY score DESC LIMIT 100');
However, I need to use an specific class model. So far I have:
$books = new Model_Book();
$books = $books->where('status', '=', 1);
$books = $books->where(DB::expr('MATCH(`title`,`author`,`isbn`)'), 'AGAINST', DB::expr("(:str)"))->param(':str', $search_terms);
Which works fine, except for the fact that I'm unable to use the score value. I need the score because since I changed the table engine to InnoDB, the 2nd query is returning a lot of results.
ORM here: https://github.com/kohana/orm/blob/3.3/master/classes/Kohana/ORM.php
Thank you for your time.

So, you don't use query builder, but ORM object finding.
In first case you take result array on second array of objects.
Trust me, you don't want use list objects. (It's extremely slow)
$sq = DB::expr('MATCH(title, author, isbn) AGAINST (:str) AS score')
->param(":str", $search_terms);
$wq = DB::expr('MATCH(title, author, isbn)');
$query = DB::select('id_book', $sq)
->from('tab_books') // OR ->from($this->_table_name) for model method
->where('status','=',1) ->where($wq, 'AGAINST ', $search_terms)
->order_by('score', desc)->limit(100) //->offset(0)
->having('score', '>', $score);
$result = $query->execute()->as_array();
for query test:
die($query->compile(Database::instance()));
OT: Use
$books = ORM::factory('Book')->full_text($search_terms, $score);
instead $books = new Model_Book();

Related

laravel hydrateRaw/fromQuery and eager loading with pagination

I currently found out that you can hydrate an Raw sql query.
I have following query:
DB::table(DB::raw('(SELECT *, Y(location) AS longitude, X(location) AS latitude FROM meetings WHERE MBRCONTAINS(#quadrat, location)) AS sub'))
->select(DB::raw('(FLOOR(SQRT(POW((#ibk_breite - sub.latitude) * 111, 2) + POW((#ibk_laenge - sub.longitude) * 111 * ABS(COS(RADIANS(#ibk_breite))),2)))) AS distance, sub.*, latitude, longitude'));
which I hydrate as following
$meetings = Meeting::fromQuery($query->toSql());
In the blade view i need to get some additional data from different tables, for example:
$meeting->user
which references to the User Model. But if I'm not complety wrong that would result to a n+1 problem in a for each loop, because I'm not eager loading it?! So is it possible to eager load the required models as you would normally do with
->with('user', 'books', 'etc...')
??
Also is it possible to paginate it like $meetings = $query->paginate(5); and do $meetings->withPath('home');
EDIT:
Found a solution:
// Do your query stuff
// Get count before the query because it won't work with skip and take parameter
$count = $query->count();
$query->skip($skip);
$query->take($meetingsPerPage);
$meetings = Meeting::fromQuery($query->toSql());
$meetings->load('user', 'interest.image', 'img_thumbnail');
$meetings = new LengthAwarePaginator($meetings, $count, $meetingsPerPage);
$meetings->load acts as ->with().
As last step you need to create a paginator. IMPORTANT: Use query->count() before you set skip() and/or take() Otherwise it will not work.
Original answer from laracasts. Theres also another possibily stated that didn't work for me.
You can use setCollection() function with LengthAwarePaginator object (found in Illuminate/Pagination/AbstractPaginator.php). Use load() for eager loading.
$users = DB::table('users')->paginate();
$users->setCollection(User::hydrate($users->items())->load(['category']));
My solution:
// Do your query stuff
// Get count before the query because it won't work with skip and take parameter
$count = $query->count();
$query->skip($skip);
$query->take($meetingsPerPage);
$meetings = Meeting::fromQuery($query->toSql());
$meetings->load('user', 'interest.image', 'img_thumbnail');
$meetings = new LengthAwarePaginator($meetings, $count, $meetingsPerPage);
$meetings->load acts as ->with(). As last step you need to create a paginator. IMPORTANT: Use query->count() before you set skip() and/or take() Otherwise it will not work.
Original answer from laracasts. Theres also another possibily stated that didn't work for me.

How to retrieve items with hibernate clustered by to_days + COUNT(*)

I am pretty new to hibernate again, so this might be a noobish question ;).
Without to_days, but clustered by timestamp it works like this:
CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class);
Root<Session> sessionRoot = query.from(Session.class);
query.multiselect(
sessionRoot.get("time").alias("time"),
criteriaBuilder.count(sessionRoot).alias("count")
);
query.groupBy(sessionRoot.get("time"));
List<Tuple> results = this.executeQuery(query);
So I recieve:
time|count
13721938721|1
13721938722|2
13721938723|3
13721938724|4
13721938725|2
13721938726|1
13721938727|4
But this are all sessioncounts for each millisecond, but I need those clustered by day and not by timestamp: thus I use to_days in plain mysql.
In mysql I perform this query:
SELECT TO_DAYS(`time`) AS `days`, COUNT(*) as `count` FROM sessions WHERE 1 GROUP BY `days`
This gives me:
days|count
777594|123
777595|60
777596|61
777597|74
But I have no idea, yet: how to achieve the same thing with javax.persistence.criteria.CriteriaBuilder and CriteriaQuery in hibernate?
I dont know how to do it with criteriaBuilder, but i do know how in Hibernate 4 criteria api:
query.setProjection(
Projections.sqlProjection(
"TO_DAYS(time) as days",
new String[]{"days"},
new Type[]{StandardBasicTypes.INTEGER}
)
);
sqlProjection allows you to cast or convert data types, but careful, using a projection will only retrieve the fileds you specify in it, and the resulting list will come up like this:
List<Object[]> results = this.executeQuery(query);
But you can make hibernate do a alias match with the properties using a result transformer:
query.setResultTransformer(new AliasToBeanResultTransformer(Session.class));
and the list comes out like it normally does:
List<Session.class> results = this.executeQuery(query);
Sorry i could not provide a criteriaBuilder solution, but i hope this gets you in the right track.
After some investigation, it turned out, that HQL does not support TO_DAYS. Since I want to make it possible for MySQL and other databases, this is my final solution:
Query q = entityManager.createQuery("SELECT concat(day(e.time), '-', month(e.time), '-', year(e.time)) AS days, COUNT(*) FROM Event e GROUP BY concat(day(e.time), '-', month(e.time), '-', year(e.time))");
The result is:
3-5-2012|980
4-5-2012|200
10-6-2012|123
12-6-2012|144
13-11-2012|500
So afterwards I convert all ugly date strings into proper milliseconds in java and have the data, which I need.

DQL innerJoin query equivalent

I have the code below working correctly on phpMyAdmin:
select testers.department from testers
inner join request_details
on testers.id = request_details.test_id
where request_details.request_id = '12345'
I tried converting it to DQL as below:
$query = Doctrine_Query::create()
->select('t.department')
->from('testers t, request_details r')
->innerJoin('t.id r')
->where('t.id = r.tester_id')
->andWhere('r.request_id = ?', 12345);
However , a var_dump() on the variable holding the query result returns NULL.
Which Doctrine version are you using, because in Doctrine2 you should be using the QueryBuilder class and you should use class names and properties, not table names and fields in DQL.
Thus, you should join to the class field name, not the table field name.
innerJoin('t.request_details', 'r') // where request_details is a propery on Tester
Also, you do not need the where that joins them (where(t.id = r.tester_id)), this is managed by Doctrine and will work provided that the entities are properly mapped.
You also do not need the request_details r in the from part, Doctrine will take care of this too.
Also, use class names in from, not table names.
EDIT (forgot the getQuery() before getResults()):
In the end you query would look something like this:
$queryBuilder = EntityManager::create(//em options)->createQueryBuilder();
$queryBuilder->select('t.department')
->from('Tester', 't')
->innerJoin('t.request_details', 'r') // request details is a propery on Tester, that maps to RequestDetails
->where('r.request_id = ?1')
->setParameter(1, 123);
Doctrine will take care of turning this into SQL and joining the thing.
In the end you'll also need to fetch the stuff:
$departments = $queryBuilder->getQuery()->getResult();
EDIT:
for Doctrine 1, something like this should work:
$q = Doctrine_Query::create()
->select('t.department')
->from('Tester t')
->innerJoin('t.request_details r') // request details is a propery on Tester, that maps to RequestDetails
->where('r.request_id = ?', 123);
$depts = $q->fetchArray();
I am not really familiar with Doctrine 1, so take a look at this for more info:
http://docs.doctrine-project.org/projects/doctrine1/en/latest/en/manual/dql-doctrine-query-language.html

Doctrine query returns extra field in result

I have a Doctrine query;
$q = Doctrine_Query::create()->select('s.monthly_volume')
->from('SearchVolume s')
->innerJoin('s.Keywords k')
->where('k.group_id = ?',array($group_id));
I just want it to return the monthly_volume value in the result array. It currently returns monthly_volume and id, I don't want it to return the id in result.
Doctrine automatically adds the primary key field to the results in almost every type of hydration mode.
In a case like this where you want a simple array and only have a single field being selected, the answer is the Single Scalar Hydration mode. Use it like this:
$q = Doctrine_Query::create()->select('s.monthly_volume')
->from('SearchVolume s')
->innerJoin('s.Keywords k')
->where('k.group_id = ?');
$monthly_volumes = $q->execute(array($group_id), Doctrine_Core::HYDRATE_SINGLE_SCALAR);
You should find that $monthly_volumes is a simple one-dimensional array containing only the value(s) you wanted.

Symfony Propel criteria

Is there any possible way to convert the MySQL object into criteria object? I tried this query:
select
p.disrepid,
p.subject, p.body,
c.disrepid as disrepid1,
c.subject as subject1,
c.body as body1
from discusreply as p, discusreply as c
where p.distopid=' . $this->id . '
and (c.disrepid = p.parentid or c.parentid = p.distopid)
order by p.disrepid ASC
I tried a lot for converting this query into a Criteria, But nothing happened. I want this criteria object for passing this into Pager class for completing the pagination.
$pager->setCriteria($c);.
You can use your own SQL do perform a query, but there is no automated way to turn sql into a Criteria object.
$con = Propel::getConnection(DATABASE_NAME);
$sql = "SELECT books.* FROM books
WHERE NOT EXISTS (SELECT id FROM review WHERE book_id = book.id)";
$stmt = $con->createStatement();
$rs = $stmt->executeQuery($sql, ResultSet::FETCHMODE_NUM);
$books = BookPeer::populateObjects($rs);
This bypasses Criterion objects all together. You mentioned wanting a criteria object so you could feed this into a pager. You can instead set a custom select method into your pager, which will then perform your custom query. If you need to pass a parameter into this, I would recommend extending sfPropel with your own pager class that can optionally pass parameters to your peer select methods so you don't have to use Criteria objects at all. As a quick alternative, you can do something like this, using your Criteria as a container for your select parameters:
$c = new Criteria();
$c->add(DiscussreplyPeer::ID, $myId);
$pager = new sfPropelPager();
$pager->setCriteria($c);
$pager->setPeerMethod('getReplies');
And then in your peer class:
public static function getReplies(Criteria $c) {
$map = $c->getMap();
$replyId = $map[DiscussreplyPeer::ID]->getValue();
$con = Propel::getConnection(DATABASE_NAME);
$sql = "select p.disrepid, p.subject, p.body, c.disrepid as disrepid1, c.subject as subject1, c.body as body1 from discusreply as p, discusreply as c where p.distopid=? and (c.disrepid = p.parentid or c.parentid = p.distopid) order by p.disrepid ASC";
$stmt = $con->prepareStatement($sql);
$stmt->setString(1, $replyId);
$rs = $stmt->executeQuery();
$results = array();
while ($rs->next()) {
// for example
$results['disrepid'] = $rs->getInt('disrepid');
}
return $results;
}
More tips on propel and symfony can be found at:
http://stereointeractive.com/blog/2007/06/12/propel-queries-using-custom-sql-peer-classes-and-criterion-objects/
This site will help a lot for learning to write criteria - you can use it to generate criteria code from pseudo SQL. I would also recommend grabbing the Symfony/Propel cheat sheets.
For your query in particular you will want something like this:
$c = new Criteria();
$c->addJoin(discusreply::DISREPID, discusreply::PARENTID, Criteria::INNER_JOIN);
$c->clearSelectColumns();
$c->addSelectColumn(discusreplyPeer::Disrepid);
...
$c->add(discusreplyPeer::DISTOPID, $this->id, Criteria::EQUAL);
...
$c->addAscendingOrderByColumn(discusreply::DISREPID);
I'm not sure that the Criteria system supports multiple clauses for an inner join so you may have to revert back to ad-hoc SQL for this query (if it does I would love to know how). The following code will create a ResultSet object similar to what you would get from simple database abstraction layers.
$sql = "SELECT ...";
$dbh = Propel::getConnection([DB]);
$sth = $dbh->createStatement();
$res = $sth->executeQuery($sql, ResultSet::FETCHMODE_NUM);
I don't think there is much of a disadvantage to using the ad-hoc method on a query like this since you will have to deal with ResultSet objects rather than table-specific objects when you are returning only specific columns.
You can try auto-generating the criteria from sql using this site.