Propel multiple-parameter binding in where() clause - symfony-1.4

I'm trying to run this query on Propel 1.6 with symfony 1.4.20.
I want to bind 2 parameters onto this subquery but its not working.
$paginas = PaginaQuery::create()
->where("pagina.id not in (select id from cliente_artista where cliente_artista.cliente_id = ? and cliente_artista.culture = ?)"
,array('XXX', 'en')
)
->limit(5)
->find();
This gives me the error:
Cannot determine the column to bind to the parameter in clause
I also found this post but there is no answer (https://groups.google.com/forum/?fromgroups=#!topic/propel-users/2Ge8EsTgoBg)

Instead of using placeholders. You may use $id and $culture:
//first, get an array of the id's
//define your vars
$id = $your_id_param;
$culture = 'en';
$cliente_artistas = ClienteArtistaQuery::create()
->select('id')
->distinct()
->filterByClienteId($id)
->filterByCulture($culture)
->find();
$paginas = PaginaQuery::create()
->where("pagina.id NOT IN ?", $cliente_artistas)
->limit(5)
->find();
If this has to be done in one query, recommend using raw sql and binding the parameters into the PDO statement (but then you lose the convenience of PropelObjectCollections):
public function getResultSet($id, $culture) {
$id = $id_param;
$culture = $culture_param;
$sql = <<<ENDSQL
SELECT * from pagina
WHERE id NOT IN (SELECT distinct id
FROM cliente_artista
WHERE cliente_id = ?
AND culture = ?
)
LIMIT 5
ENDSQL;
$connection = Propel::getConnection();
$statement = $connection->prepare($sql);
$statement->bindValue(1, $id);
$statement->bindValue(2, $culture);
$statement->execute();
$resultset = $statement->fetchAll(PDO::FETCH_ASSOC); // or whatever you need
if (! count($resultset) >= 1) {
// Handle empty resultset
}
return $resultset;
}
You could also write some query methods to use propel orm query methods. Ofcourse, the propel api is beneficial reference. There are several ways to do this. I have indicated one method here which should work for you.
EDIT:
Here's an idea on doing this as one query [since useSelectQuery() requires 'relation' name], this idea assumes tables are not related but that id's are:
$paginas = PaginaQuery::create()
->addJoin(PaginaPeer::ID, ClienteArtistaPeer::CLIENTE_ID, Criteria::LEFT_JOIN)
->where("ClienteArtista.Id <> ?", $id)
->where("ClienteArtista.Culture <> ?", $culture)
->select(array('your','pagina','column','array'))
->limit(5)
->find();

Related

Query with join not work

i am doing a query in my controller like this:
$aviso = $em->getRepository("FabricacionBundle:Aviso")->findBy(array("fecha" => $fecha));
$dql = "SELECT a FROM PedidosBundle:Articulo a WHERE a.aviso = :aviso";
if(isset($_GET['filterField']) && isset($_GET['filterValue'])){
$dql = "SELECT a FROM PedidosBundle:Articulo a JOIN ProductosBundle:Producto p WHERE a.aviso = :aviso";
$dql .= " AND " . $_GET['filterField'] . " LIKE '%" . $_GET['filterValue'] . "%'";
}
$query = $em->createQuery($dql)
->setParameter("aviso", $aviso[0]->getId());
//dump($query);die();
$paginator = $this->get('knp_paginator');
$articulos = $paginator->paginate(
$query,
$request->query->get('page', 1),
25
);
When i dont use the filter, this work, but when i use the filter i get the next error:
Cannot count query which selects two FROM components, cannot make distinction
Where is the problem? Thanks!
What SQL engine are you running ?
Note, that it's usually a very bad practice to insert anything directly from your $_GET variable into your SQL query, as this can lead to SQL injection.
Imagine, that someone sends \'; DROP TABLE something; -- in your $_GET['filterField'] - everything is gone.
From the top of my head, there is a lacking JOINing condition.

delete several rows where attribute = x

I need to delete all rows where the image_id = x applies
like this
DELETE FROM `ImageVoters` WHERE image_id =1
How do I do this using DQL?
Since im trying to delete several rows at once the remove() function won't work
EntityManager#remove() expects parameter 1 to be an entity object, array given.
$em = $this->getDoctrine()->getManager();
$query = $em->createQuery('SELECT u FROM GabrielUploadBundle:ImageVoters u WHERE u.image_id = 1');
$db_imageactions = $query->getResult();
$em->remove($db_imageactions);
$em->flush();
This is the code that works
$qb = $em->createQueryBuilder();
$qb->delete('GabrielUploadBundle:ImageVoters', 'v')
->where($qb->expr()->eq('v.image_id', ':imageId'))
->setParameter('imageId', $current_imageId);
$qb->getQuery()->execute();
You could create an ORM queryBuilder, to create a clean/safe query object by using internal orm methods
$qb->delete('My\Image\Namespace\ImageVoters', 'ivoter')
->where($qb->expr()->eq('ivoter.image_id', ':imageId')
->setParameter('imageId', 1);
$qb->getQuery()->execute();
You can also execute any raw SQL like this:
$sql = "DELETE FROM ImageVoters WHERE image_id =".$image_id;
$q = $em->getConnection()->exec($sql);

Get Avg value in Doctrine / symfony

i try to get AVG value in doctrine, in my Repositoru i do this:
public function getFotoSpecifica($idFoto) {
$q = $this->createQueryBuilder('f');
$q->leftJoin("f.foto_votata", 'v');
$q->where('f.id =:idFoto');
$q->andWhere('(f.foto_privata IS NULL OR f.foto_privata != 1)');
$q->andWhere('(f.foto_eliminata IS NULL OR f.foto_eliminata != 1)');
$q->expr()->avg('v.punteggio', true);
$q->setParameter('idFoto', $idFoto);
$dql = $q->getQuery();
$results = $dql->getArrayResult();
return $results;
}
but i don't find any value avg, i see all my object...i try to use createQuery in my controller but i have many errors like: Error: Invalid PathExpression. Must be a StateFieldPathExpression. ( this is a foreign key)
mySql query is:
SELECT profilo_id, proprietario_id, round(AVG( punteggio ),2) as avg_rate, SUM(punteggio) as score, foto_id, count(*) as numero_votanti
FROM prof_voto_foto
WHERE foto_id = ?
Assuming the variable name for the foreign key is named $foto and the entity is ProfVotoFoto
In your controller you can do something like this.
$em = $this->getDoctrine()->getManager();
$q=$em->createQuery("
SELECT profilo_id, proprietario_id, round(AVG( punteggio ),2) as avg_rate,
SUM(punteggio) as score, foto_id, count(*) as numero_votanti
FROM YourBundleName:ProfVotoFoto f
WHERE f.foto = :idFoto
")
->setPArameter('idFoto',$idFoto);
$averageResult = $q->getSingleResult();
You are forget to add a select statement to query. Also possible requires to add group by.
->addSelect('AVG(v.punteggio) as punteggio_avg')

How can I order by NULL in DQL?

I'm building an app using Symfony2 framework and using Doctrine ORM. I have a table with airlines for which some IATA codes are missing. I'm outputting a list, ordered by this IATA code, but I'm getting the undesirable result that the records with null IATA codes are sorted at the top.
In MySQL this is simple enough to do, with ORDER BY ISNULL(code_iata), code_iata but I'm clueless as to what the equivalent would be for DQL. I tried
$er->createQueryBuilder('airline')->orderBy('ISNULL(airline.codeIata), airline.codeIata', 'ASC')
but this gives me a syntax error.
The Doctrine docs give me no answer either. Is there a way?
You can use the following trick in DQL to order NULL values last
$em->createQuery("SELECT c, -c.weight AS HIDDEN inverseWeight FROM Entity\Car c ORDER BY inverseWeight DESC");
The HIDDEN keyword (available since Doctrine 2.2) will result in omitting the inverseWeight field from the result set and thus preventing undesirable mixed results.
(The sort fields value is inverted therefore the order has to be inverted too, that's why the query uses DESC order, not ASC.)
Credits belong to this answer.
The most unobtrusive generic solution would be to use the CASE expression in combination with the HIDDEN keyword.
SELECT e,
CASE WHEN e.field IS NULL THEN 1 ELSE 0 END HIDDEN _isFieldNull
FROM FooBundle:Entity e
ORDER BY _isFieldNull ASC
Works with both numeric as well as other field types and doesn't require extending Doctrine.
If you want to do something similar to "NULLS LAST" in SQL (with PostgreSQL in my case):
ORDER BY freq DESC NULLS LAST
You can use the COALESCE function with the Doctrine Query Builder
(HIDDEN will hide the field "freq" on your query result set).
$qb = $this->createQueryBuilder('d')
->addSelect('COALESCE(d.freq, 0) AS HIDDEN freq')
->orderBy('freq', 'DESC')
->setMaxResults(20);
Here it is an example for a custom walker to get exactly what you want. I have taken it from Doctrine in its github issues:
https://github.com/doctrine/doctrine2/pull/100
But the code as it is there didn't work for me in MySQL. I have modified it to work in MySQL, but I haven't test at all for other engines.
Put following walker class for example in YourNS\Doctrine\Waler\ directory;
<?php
namespace YourNS\Doctrine\Walker;
use Doctrine\ORM\Query\SqlWalker;
class SortableNullsWalker extends SqlWalker
{
const NULLS_FIRST = 'NULLS FIRST';
const NULLS_LAST = 'NULLS LAST';
public function walkOrderByClause($orderByClause)
{
$sql = parent::walkOrderByClause($orderByClause);
if ($nullFields = $this->getQuery()->getHint('SortableNullsWalker.fields'))
{
if (is_array($nullFields))
{
$platform = $this->getConnection()->getDatabasePlatform()->getName();
switch ($platform)
{
case 'mysql':
// for mysql the nulls last is represented with - before the field name
foreach ($nullFields as $field => $sorting)
{
/**
* NULLs are considered lower than any non-NULL value,
* except if a – (minus) character is added before
* the column name and ASC is changed to DESC, or DESC to ASC;
* this minus-before-column-name feature seems undocumented.
*/
if ('NULLS LAST' === $sorting)
{
$sql = preg_replace_callback('/ORDER BY (.+)'.'('.$field.') (ASC|DESC)/i', function($matches) {
if ($matches[3] === 'ASC') {
$order = 'DESC';
} elseif ($matches[3] === 'DESC') {
$order = 'ASC';
}
return ('ORDER BY -'.$matches[1].$matches[2].' '.$order);
}, $sql);
}
}
break;
case 'oracle':
case 'postgresql':
foreach ($nullFields as $field => $sorting)
{
$sql = preg_replace('/(\.' . $field . ') (ASC|DESC)?\s*/i', "$1 $2 " . $sorting, $sql);
}
break;
default:
// I don't know for other supported platforms.
break;
}
}
}
return $sql;
}
}
Then:
use YourNS\Doctrine\Walker\SortableNullsWalker;
use Doctrine\ORM\Query;
[...]
$qb = $em->getRepository('YourNS:YourEntity')->createQueryBuilder('e');
$qb
->orderBy('e.orderField')
;
$entities = $qb->getQuery()
->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, '\YourNS\Doctrine\Walker\SortableNullsWalker')
->setHint('SortableNullsWalker.fields', array(
'sortOrder' => SortableNullsWalker::NULLS_LAST
))
->getResult();
DQL does not contain every function of plain SQL. Fortunately you can define your custom DQL method to accomplish this.
Some resources:
http://punkave.com/window/2012/07/24/for-the-php-crowd-adding-custom-functions-to-doctrine-2-dql
http://docs.doctrine-project.org/en/2.1/cookbook/dql-user-defined-functions.html
http://symfony.com/doc/2.0/cookbook/doctrine/custom_dql_functions.html
By default, MySQL will still sort a NULL value; it will just place it at the beginning of the result set if it was sorted ASC, and at the end if it was sorted DESC. Here, you're looking to sort ASC, but you want the NULL values to be at the bottom.
Unfortunately, as powerful as it is, Doctrine isn't going to offer much support here, since function support is limited, and most of it is limited to SELECT, WHERE, and HAVING clauses. You actually wouldn't have a problem at all if any of the following were true about the QueryBuilder:
select() accepted ISNULL()
orderBy() or addOrderBy() supported ISNULL()
the class supported the concept of UNIONs (with this, you could run two queries: one where the codeIata was NULL, and one where it wasn't, and you could sort each independently)
So that said, you can go with the user-defined functions that ArtWorkAD mentioned already, or you could replicate that last point with two different Doctrine queries:
$airlinesWithCode = $er->createQueryBuilder("airline")
->where("airline.iataCode IS NULL")
->getQuery()
->getResult();
$airlinesWithoutCode = $er->createQueryBuilder("airline")
->where("airline.iataCode IS NOT NULL")
->getQuery()
->getResult();
Then you can combine these into a single array, or treat them independently in your templates.
Another idea is to have DQL return everything in one data set, and let PHP do the heavy lifting. Something like:
$airlines = $er->findAll();
$sortedAirlines = array();
// Add non-NULL values to the end if the sorted array
foreach ($airlines as $airline)
if ($airline->getCodeIata())
$sortedAirlines[] = $airline;
// Add NULL values to the end of the sorted array
foreach ($airlines as $airline)
if (!$airline->getCodeIata())
$sortedAirlines[] = $airline;
The downside to both of these is that you won't be able to do LIMITs in MySQL, so it might only work well for relatively small data sets.
Anyway, hope this gets you on your way!

Magento: Filtering a Collection with grouped Clauses

I would like to filter a collection with grouped clauses. In SQL this would look something like:
SELECT * FROM `my_table` WHERE col1='x' AND (col2='y' OR col3='z')
How can I "translate" this to filtering a collection with ->addFieldToFilter(...)?
Thanks!
If your collection is an EAV type then this works well:
$collection = Mage::getResourceModel('yourmodule/model_collection')
->addAttributeToFilter('col1', 'x')
->addAttributeToFilter(array(
array('attribute'=>'col2', 'eq'=>'y'),
array('attribute'=>'col3', 'eq'=>'z'),
));
However if you're stuck with a flat table I don't think addFieldToFilter works in quite the same way. One alternative is to use the select object directly.
$collection = Mage::getResourceModel('yourmodule/model_collection')
->addFieldToFilter('col1', 'x');
$collection->getSelect()
->where('col2 = ?', 'y')
->orWhere('col3 = ?', 'z');
But the failing of this is the order of operators. You willl get a query like SELECT * FROM my_table WHERE (col1='x') AND (col2='y') OR (col3='z'). The OR doesn't take precedence here, to get around it means being more specific...
$collection = Mage::getResourceModel('yourmodule/model_collection')
->addFieldToFilter('col1', 'x');
$select = $collection->getSelect();
$adapter = $select->getAdapter();
$select->where(sprintf('(col2 = %s) OR (col3 = %s)', $adapter->quote('x'), $adapter->quote('y')));
It is unsafe to pass values unquoted, here the adapter is being used to safely quote them.
Finally, if col2 and col3 are actually the same, if you're OR-ing for values within a single column, then you can use this shorthand:
$collection = Mage::getResourceModel('yourmodule/model_collection')
->addFieldToFilter('col1', 'x')
->addFieldToFilter('col2', 'in'=>array('y', 'z'));

Resources