![]() |
|
Snippets |
|
I haven't found a way to use subqueries as alias with criteria in the book.
Here is a small piece of code that works very well:
the subquery is in an alias defined by Criteria::addAsColumn($alias, $expr)
it can be used to order the result, or in case you have to deal with foreign keys, etc.
$c = new Criteria(); $c->addAsColumn('brandname', '(SELECT brand.name FROM brand WHERE brand.id=brand_id)'); $c->addAscendingOrderByColumn($c->getColumnForAs('brandname')); $this->products = ProductPeer::doSelect($c);
Product has a brand_id that references the id key of the brand table that contains the 'name' field which is used to order the result.
Patrice Blanchardie
This was asked on the forum, so I thought I would place it here too.
Suppose you have a model that resembles the following:
range: id: start_date: {type: date} end_date: {type: date}
And you want to know the following: are there any records where either $date1 or $date2 is between start_date and end_date?
Consider that $date2 could be some fixed $offset from $date1, so that the question becomes: are there any records which partially cover the $offset period following $date1?
Since the BETWEEN construct is unavailable through Propel (at least not as an object or constant), we have to use the knowledge that:
if
a >= x
and
a <= y
then
x <= a <= y
Which gives us:
$c = new Criteria(); $date1 = '2007-08-20'; $date2 = '2008-08-20'; // test against date1 $date1Criterion = $c->getNewCriterion(RangePeer::START_DATE, $date1, Criteria::LESS_EQUAL); // test against date2 $date2Criterion = $c->getNewCriterion(RangePeer::START_DATE, $date2, Criteria::LESS_EQUAL); // conjunction $date1Criterion->addAnd($c->getNewCriterion(RangePeer::END_DATE, $date1, Criteria::GREATER_EQUAL)); $date2Criterion->addAnd($c->getNewCriterion(RangePeer::END_DATE, $date2, Criteria::GREATER_EQUAL)); // disjunction $date1Criterion->addOr($date2Criterion); $c->add($date1Criterion);
Or if you absolutely must have your BETWEEN, try this:
$c = new Criteria(); $date1 = '2007-08-20'; $date2 = '2008-08-20'; $c->add(RangePeer::START_DATE, "'{$date1}' between ".RangePeer::START_DATE." and ".RangePeer::END_DATE, Criteria::CUSTOM); $c->addOr(RangePeer::START_DATE, "'{$date2}' between ".RangePeer::START_DATE." and ".RangePeer::END_DATE, Criteria::CUSTOM);
Note that the first arguments to Criteria::add() and Criteria::addOr() can be any column, but they must be the same column.
Note also that when providing the argument, {$dateN} appears between single quotes. It must appear quoted (and escaped) in order for this query to return correct (or any) results.
When you need to update several records in a row, you don't have to loop over the result of a doSelect() call and do a save() for each object (which would make way too many queries).
Instead, you can use the BasePeer method doUpdate() as follows:
BasePeer::doUpdate($select_criteria, $update_criteria, $connection);
For instance:
$con = Propel::getConnection(); // select from... $c1 = new Criteria(); $c1->add(CommentPeer::POST_ID, $post_id); // update set $c2 = new Criteria(); $c2->add(CommentPeer::RATING, 5); BasePeer::doUpdate($c1, $c2, $con);
If course, if you are in a Peer class, you will need to use the self object:
$con = Propel::getConnection(); // select from... $c1 = new Criteria(); $c1->add(self::POST_ID, $post_id); // update set $c2 = new Criteria(); $c2->add(self::RATING, 5); BasePeer::doUpdate($c1, $c2, $con);
Imagine that you have a table with the following columns:
MyTable ------- id col1 col2
If you want to retrieve the records having col1 greater than col2, the following doesn't work:
$c = new Criteria(); $c->add(MyTablePeer::COL1, MyTablePeer::COL2, Criteria::GREATER_THAN);
Instead, you need to add a custom condition to your Criteria:
$c = new Criteria(); $c->add(MyTablePeer::COL1, MyTablePeer::COL1.'>='.MyTablePeer::COL2, Criteria::CUSTOM);
I had some trouble finding information on how to use SQL aggregate functions like GROUP BY, COUNT and HAVING with Propel, so here is some info about that.
Suppose you have a system with a many-to-many relation between articles and authors, this example shows how to find out how many articles each author has worked on.
$c = new Criteria(); // optionally look only for certain authors whose IDs are in $results $c->add(AuthorPeer::ID, $results, Criteria::IN); // JOIN them with the article IDs $c->addJoin(ArticleAuthorPeer::AUTHOR_ID, AuthorPeer::ID); // list each author only once and count the number of articles they have worked on $c->addGroupByColumn(AuthorPeer::ID); $c->addAsColumn('numArticles', 'COUNT('.AuthorPeer::ID.')'); // optionally retrieve only those authors that have a certain number of articles (like 'numArticles=2' or 'numArticles>2') // the first argument does not really matter since this is a custom criteria // according to the SQL standard this cannot be done with a WHERE clause $c->addHaving($c->getNewCriterion(AuthorPeer::ID, 'numArticles=2', Criteria::CUSTOM)); // order by the number of articles $c->addDescendingOrderByColumn('numArticles'); // get a ResultSet and iterate over it $rs = AuthorPeer::doSelectRS($c); $counts = array(); $results = array(); while ($rs->next()) { $author = new Author(); // hydrate the object and store how many columns it has $lastColumn = $author->hydrate($rs); $results[] = $author; // then retrieve the COUNT from the first column not belonging to the object $counts[] = $rs->getInt($lastColumn); } $this->results = $results; $this->counts = $counts;
Thanks to netcrash from the symfony irc channel I came along regular expressions in MySQL: http://dev.mysql.com/doc/refman/4.1/en/regexp.html
Tutorial: http://www.brainbell.com/tutorials/MySQL/Using_MySQL_Regular_Expressions.htm
I'm using it to select all entries that don't start with a letter in an alphabetical pagination:
$c = new Criteria(); $regex = CmsUserPeer :: USERNAME." NOT REGEXP '^[[:alpha:]]'"; $c->add(CmsUserPeer :: USERNAME, $regex, Criteria::CUSTOM); $this->cms_user = CmsUserPeer :: doSelect($c);
Instead of selecting by REGEXP pookey had the idea of "using substr to get the first char, then getting it's character code, and doing a BETWEEN on it" for performance reasons.
There is another faster solution:
$c = new Criteria(); $cq = "substring( ".CmsUserPeer :: USERNAME.", 1, 1 ) NOT BETWEEN 'a' AND 'z'"; $c->add(CmsUserPeer :: USERNAME, $cq, Criteria::CUSTOM); $this->cms_user = CmsUserPeer :: doSelect($c);
Let say you want to write a sub-select, and have a Propel object returned.
As an example, lets generate the following SQL:
SELECT orders.ID, orders.TYPE, orders.STATUS FROM orders WHERE orders.STATUS IN ( SELECT STATUS.NAME FROM STATUS WHERE STATUS.ORDER_TYPE = 'purchase' )
Propel does not natively handle this sort of SQL. There are however two ways to create this SQL or to get this data set and have Propel objects returned.
$c = new Criteria(); $subSelect = "orders.STATUS IN ( SELECT status.NAME FROM status WHERE status.ORDER_TYPE = 'purchase' )"; $c->add(StatusPeer.STATUS, $subSelect, Criteria::CUSTOM); $orders = StatusPeer::doSelect($c);
Example of the rewritten SQL:
SELECT orders.ID, orders.TYPE, orders.STATUS FROM orders, STATUS WHERE orders.STATUS = STATUS.NAME AND STATUS.ORDER_TYPE = 'purchase'
This can be written as follows in your action:
$c = new Criteria(); $c->addJoin (OrderPeer::STATUS, StatusPeer::NAME); $c->add(StatusPeer.ORDER_TYPE, 'purchase'); $orders = StatusPeer::doSelect($c);
Say you look for the objects of class Foo being created between $from_date and $to_date. This should do the trick:
$c = new Criteria(); $criterion = $c->getNewCriterion(FooPeer::CREATED_AT , date('Y-m-d', $from_date), Criteria::GREATER_EQUAL ); $criterion->addAnd($c->getNewCriterion(FooPeer::CREATED_AT , date('Y-m-d', $to_date), Criteria::LESS_EQUAL )); $c->add($criterion); $shows = FooPeer::doSelect($c);
If you want to make an update according to the primary key you just need to use one criteria:
$c = new Criteria(); $c->add(MyObjectPeer::ID,123); $c->add(MyObjectPeer::NAME,'ola'); $c->add(MyObjectPeer::CITY,'pepito'); MyObjectPeer::doUpdate($c);
and the query is:
UPDATE my_object SET NAME = 'ola',CITY = 'pepito' WHERE my_object.ID=123
Compared to the update query using Propel snippet which pass 2 criterias to the BasePeer:doUpdate method, here we pass only one criteria to the MyObjectPeer::doUpdate method. This method remove the index ID of the "set" criteria and use it for the "select" criteria.
More a PHP trick than symfony's one, but as you can't with propel randomize order of results, just do:
$c = new Criteria() ... fill your criteria there ... $result = MyTablePeer::doSelect($c); shuffle($result);
obvious? sorry, seen someone asking on IRC once :D
Here is an example of obtaining a custom set of select columns using Propel Criteria, as opposed to using a custom SQL statement. The use of distinct is also demonstrated.
// Set up empty select $c = new Criteria(); $c->clearSelectColumns(); // Read distinct(owningdepot) from the load table... $c->addSelectColumn(TmsLoadPeer::OWNINGDEPOT); $c->addGroupByColumn(TmsLoadPeer::OWNINGDEPOT); $c->setDistinct(); // ...which are IN the provided list $c->add(TmsLoadPeer::LOAD_ID, $loadIds, Criteria::IN); $depots = TmsLoadPeer::doSelectRS($c); // Then return an array of depots $arrDep = array(); foreach ($depots as $depot) { $arrDep[] = $depot[0]; }