Symfony 3 - Doctrine - Difficulties on big query - php

I'm annoyed because of a big query that goes a little in all directions.
She will use several tables:
User
Package
User Type
PackageDDLExternal (with foreign key User.id and Package.id to which I added a date attribute)
The purpose of this query is to be able to return a list that will contain:
The different existing packages with the number of times they were downloaded, for a certain period, with a given order (ASC or DESC) on the number of downloads, and all this depending on the user types whose ID table has passed as parameter.
So, I've :
Paquet :
Type Utilisateur :
User:
The manyToMany relation between TypeUtilisateur and Paquet:
And the PackageDDLExternal:
So I have already tried to create this query, but there seems to be synthax errors on the one hand (at the level of adding the order) and other things that seem to block...
public function getPackagesDDLBetween($debut, $fin,$typesUser,$ordre)
{
$qb = $this->createQueryBuilder('p');
$queryBuilder = $qb
->select("pa.titre, count(p.package)")
->join("p.package","pa")
->join("p.user","u")
->where("p.date between :debut and :fin")
->andWhere($qb->expr()->in("u.typeUser", $typesUser[0]))
->groupBy("pa.titre")
->orderBy("count(p.package) :ordre")
->setParameter('debut',$debut)
->setParameter('fin',$fin)
->setParameter('ordre', $ordre);
return $queryBuilder->getQuery()->getResult();
}
Using the $order, I get this error:
[Syntax Error] line 0, col 213: Error: Expected known function, got
'count'
But without it, my result is just null
Someone can help me please ?
EDIT: The query is almost good. The problem remains at:
->andWhere($qb->expr()->in("u.typeUser", $typesUser))
My $ tabUser is worth this:

Use an alias:
->select("pa.titre, count(p.package) as total")
Order by alias:
->orderBy("total", $ordre)

Related

Doctrine SELECT in array type

I have a problem with a project and array type in doctrine.
My entity got the field userslist who save list of user id. In my DB the field type is TEXT :
/**
* #Column(type="array", nullable=false)
*/
private $userslist;
I create my content like this :
$r->createNewLine(50, 'Name', 'Content', array(1, 5, 8))
The result in my table for my field userslist look,like this (I think it's key -> value) :
a:3:{i:0;i:1;i:1;i:5;i:2;i:8;}
I used QueryBuilder to get the results, here is my request :
$qb = $this->createQueryBuilder('r')
->andWhere('r.userslist IN (:userid)')
->setParameters(array('userid' => array(1)));
return $qb->getQuery()->getArrayResult();
I always got an empty array as result.
I have several lines in my table with userid 1 in userslist field.
a:3:{i:0;i:1;i:1;i:5;i:2;i:8;}
a:3:{i:0;i:1;i:1;i:5;i:2;i:8;}
a:3:{i:0;i:9;i:1;i:4;i:2;i:2;}
a:3:{i:0;i:1;i:1;i:5;i:2;i:8;}
a:3:{i:0;i:9;i:1;i:4;i:2;i:2;}
...
I also tried to use an expression :
$qb->add('where', $qb->expr()->in('r.userslist', array(':userid')));
The result is also empty.
But when I use LIKE it's working but not secure because I'm confuse between key and value in my table :
$qb = $this->createQueryBuilder('r')
->andWhere("r.userslist LIKE :userid")
->setParameters(array('userid' => '%i:1;%'));
How can I get my lines where the userid 1 is in my userslist field ?
Thanks
Doctrine requires you to parse the entire string to find a direct match, other wise it appears you need to use the LIKE comparison to find a record based upon a selection of the data. Here is a similar question FOS bundle - How to select users with a specific role?
To avoid this issue have you considered creating another entity and creation a relationship between that and your user object so that you don't have to rely on a LIKE condition to find your data.

Weird alias [0_ added by doctrine while trying to run with SQL SERVER

I'm trying to implement APYDataGridBundle on Symfony, with SQL Server. Symfony throws in this exception:
SQLSTATE[HY000]: General error: 105 General SQL Server error: Check messages from the SQL Server [105] (severity 15) [SELECT TOP 50 [0_.PI_PK AS PI_PK0, [0_.ProductName AS ProductName1, [0_.ProductDetails AS ProductDetails2 FROM [TSOFT_LEARN].[dbo].[tblProductDemo] [0_]
I tried:
$repo = $em->getRepository("ProductOrderLookupBundle:Product");
$product = $repo->findAll();
and everything worked fine, but it breaks for over 1 million records. Someone suggested to me to use APYDatagridBundle like here. I have tried ThraceDataGrid Bundle before and it gave me this same problem.
If I remove the "[0_" and everything worked fine while running the query on SQL Server.
Can anybody tell me what might be the problem?
Doctrine always create alias for tables in the generated queries. I always had the same kind of queries, i don't know if it can be disabled or not but better not because i don't know how it will react for joins , what you can do is to add a function in your repository :
public function getAllProducts(){
$qb = $this->_em->createQueryBuilder()
->select('partial p.{ /*add all fields needed separated by coma optimized for big queries get only whats needed */ }')
->from('Product', 'p');
$results = $qb->getQuery()->getResult();
return $results;
}
**SOLVED:**The problem was with how I defined my table names in the doctrine entities. I gave the name
[TSOFT_LEARN].[dbo].[tblOrders]
and it should be
TSOFT_LEARN.dbo.tblOrders
Because of that my alias was becoming [0_ instead of t0_. I suppose its how doctrine creates its aliases by taking the first character of the table name. Since the table name was starting with "[" the alias got converted to [0_.

Yii active record relation limit to one record

I am using PHP Yii framework's Active Records to model a relation between two tables. The join involves a column and a literal, and could match 2+ rows but must be limited to only ever return 1 row.
I'm using Yii version 1.1.13, and MySQL 5.1.something.
My problem isn't the SQL, but how to configure the Yii model classes to work in all cases. I can get the classes to work sometimes (simple eager loading) but not always (never for lazy loading).
First I will describe the database. Then the goal. Then I will include examples of code I've tried and why it failed.
Sorry for the length, this is complex and examples are necessary.
The database:
TABLE sites
columns:
id INT
name VARCHAR
type VARCHAR
rows:
id name type
-- ------- -----
1 Site A foo
2 Site B bar
3 Site C bar
TABLE field_options
columns:
id INT
field VARCHAR
option_value VARCHAR
option_label VARCHAR
rows:
id field option_value option_label
-- ----------- ------------- -------------
1 sites.type foo Foo Style Site
2 sites.type bar Bar-Like Site
3 sites.type bar Bar Site
So sites has an informal a reference to field_options where:
field_options.field = 'sites.type' and
field_options.option_value = sites.type
The goal:
The goal is for sites to look up the relevant field_options.option_label to go with its type value. If there happens to be more than one matching row, pick only one (any one, doesn't matter which).
Using SQL this is easy, I can do it 2 ways:
I can join using a subquery:
SELECT
sites.id,
f1.option_label AS type_label
FROM sites
LEFT JOIN field_options AS f1 ON f1.id = (
SELECT id FROM field_options
WHERE
field_options.field = 'sites.type'
AND field_options.option_value = sites.type
LIMIT 1
)
Or I can use a subquery as a column reference in the select clause:
SELECT
sites.id,
(
SELECT id FROM field_options
WHERE
field_options.field = 'sites.type'
AND field_options.option_value = sites.type
LIMIT 1
) AS type_label
FROM sites
Either way works great. So how do I model this in Yii??
What I've tried so far:
1. Use "on" array key in relation
I can get a simple eager lookup to work with this code:
class Sites extends CActiveRecord
{
...
public function relations()
{
return array(
'type_option' => array(
self::BELONGS_TO,
'FieldOptions', // that's the class for field_options
'', // no normal foreign key
'on' => "type_option.id = (SELECT id FROM field_options WHERE field = 'sites.type' AND option_value = t.type LIMIT 1)",
),
);
}
}
This works when I load a set of Sites objects and force it to eager load type_label, e.g. Sites::model()->with('type_label')->findByPk(1).
It does not work if type_label is lazy-loaded.
$site = Sites::model()->findByPk(1);
$label = $site->type_option->option_label; // ERROR: column t.type doesn't exist
2. Force eager loading always
Building on #1 above, I tried forcing Yii to always to eager loading, never lazy loading:
class Sites extends CActiveRecord
{
public function relations()
{
....
}
public function defaultScope()
{
return array(
'with' => array( 'type_option' ),
);
}
}
Now everything always works when I load Sites, but it's no good because there are other models (not pictured here) that have relations that point to Sites, and those result in errors:
$site = Sites::model()->findByPk(1);
$label = $site->type_option->option_label; // works now
$other = OtherModel::model()->with('site_relation')->findByPk(1); // ERROR: column t.type doesn't exist, because 't' refers to OtherModel now
3. Make the reference to the base table somehow relative
If there was a way that I could refer to the base table, other than "t", that was guaranteed to point to the correct alias, that would work, e.g.
'on' => "type_option.id = (SELECT id FROM field_options WHERE field = 'sites.type' AND option_value = %%BASE_TABLE%%.type LIMIT 1)",
where %%BASE_TABLE%% always refers to the correct alias for table sites. But I know of no such token.
4. Add a true virtual database column
This way would be the best, if I could convince Yii that the table has an extra column, which should be loaded just like every other column, except the SQL is a subquery -- that would be awesome. But again, I don't see any way to mess with the column list, it's all done automatically.
So, after all that... does anyone have any ideas?
EDIT Mar 21/15: I just spent a long time investigating the possibility of subclassing parts of Yii to get the job done. No luck.
I tried creating a new type of relation based on BELONGS_TO (class CBelongsToRelation), to see if I could somehow add in context sensitivity so it could react differently depending on whether it was being lazy-loaded or not. But Yii isn't built that way. There is no place where I can hook in code during query buiding from inside a relation object. And there is also no way I can tell even what the base class is, relation objects have no link back to the parent model.
All of the code that assembles these queries for active records and their relations is locked up in a separate set of classes (CActiveFinder, CJoinQuery, etc.) that cannot be extended or replaced without replacing the entire AR system pretty much. So that's out.
I then tried to see if I can create "fake" database column entries that would actually be a subquery. Answer: no. I figured out how I could add additional columns to Yii's automatically generated schema data. But,
a) there's no way to define a column in such a way that it can be a derived value, Yii assumes it's a column name in way too many places for that; and
b) there also doesn't appear to be any way to avoid having it try to insert/update to those columns on save.
So it really is looking like Yii (1.x) just does not have any way to make this happen.
Limited solution provided by #eggyal in comments: #eggyal has a suggestion that will meet my needs. He suggests creating a MySQL view table to add extra columns for each label, using a subquery to look up the value. To allow editing, the view would have to be tied to a separate Yii class, so the downside is everywhere in my code I need to be aware of whether I'm loading a record for reading only (must use the view's class) or read/write (must use the base table's class, does not have the extra columns). That said, it is a workable solution for my particular case, maybe even the only solution -- although not an answer to this question as written, so I'm not going to put it in as an answer.
OK, after a lot of attempts, I have found a solution. Thanks to #eggyal for making me think about database views.
As a quick recap, my goal was:
link one Yii model (CActiveRecord) to another using a relation()
the table join is complex and could match more than one row
the relation must never join more than one row (i.e. LIMIT 1)
I got it to work by:
creating a view from the field_options base table, using SQL GROUP BY to eliminate duplicate rows
creating a separate Yii model (CActiveRecord class) for the view
using the new model/view for the relation(), not the original table
Even then there were some wrinkles (maybe a Yii bug?) I had to work around.
Here are all the details:
The SQL view:
CREATE VIEW field_options_distinct AS
SELECT
field,
option_value,
option_label
FROM
field_options
GROUP BY
field,
option_value
;
This view contains only the columns I care about, and only ever one row per field/option_value pair.
The Yii model class:
class FieldOptionsDistinct extends CActiveRecord
{
public function tableName()
{
return 'field_options_distinct'; // the view
}
/*
I found I needed the following to override Yii's default table data.
The view doesn't have a primary key, and that confused Yii's AR finding system
and resulted in a PHP "invalid foreach()" error.
So the code below works around it by diving into the Yii table metadata object
and manually setting the primary key column list.
*/
private $bMetaDataSet = FALSE;
public function getMetaData()
{
$oMetaData = parent::getMetaData();
if (!$this->bMetaDataSet) {
$oMetaData->tableSchema->primaryKey = array( 'field', 'option_value' );
$this->bMetaDataSet = TRUE;
}
return $oMetaData;
}
}
The Yii relation():
class Sites extends CActiveRecord
{
// ...
public function relations()
{
return (
'type_option' => array(
self::BELONGS_TO,
'FieldOptionsDistinct',
array(
'type' => 'option_value',
),
'on' => "type_option.field = 'sites.type'",
),
);
}
}
And all that does the trick. Easy, right?!?

Doctrine: Cannot select entity through identification variables without choosing at least one root entity alias

I'm using the following code in the query builder, to select an average of score values, and the category entity to which that average belongs:
$queryBuilder = $this->createQueryBuilder('s')
->resetDQLPart('select')
->select('AVG(s.score) as score, partial c.{reviewCategoryID} as cat')
->setParameter('status', ReviewStatusType::ACCEPTED)
->join('s.review', 'r')
->join('s.category', 'c')
->where('r.campsite = :campsite')
->andWhere('r.status = :status')
->setParameter('campsite', $campsite)
->groupBy('c.reviewCategoryID');
$campsite is an entity to which a review belongs, while scores belong to a review, and scores have a category.
But when I try to execute this, i get the error
Error: Cannot select entity through identification variables without choosing at least one root entity alias.
When I debug and I check the root aliases, I see that 's' is defined, which is should be the root entity (Score).
Any idea what could be wrong?
createQueryBuilder() can only take a parameter when it is called from the repository of the matching entity. In case you do not call it from this repository you should define a from method.
->from('YourMappingSpace:Campsite', 's')
Passing a parameter to createQueryBuilder() is for conveniance anyway. You can always define it manually. The function looks like this (Only inside the entity repository):
public function createQueryBuilder($alias)
{
return $this->_em->createQueryBuilder()
->select($alias)
->from($this->_entityName, $alias);
}

Symfony2 - Custom Query in Entity Repo doesn't seem to return expected results

So I came to a point where I needed to create a custom query in my Entity Repository to do a sub-select.
The query itself seems sound and does indeed find the result I am after (I checked by running the query in Navicat) however, when trying to view the results (using Twig templates) I am getting the following error:
Item "id" for "Array" does not exist in DEMODemoBundle:Staff/Company:company.html.twig at line 42
Line 42 in that template is:
<td>{{ entity.id }}</td>
Previously, I was using a basic "findAll" query in my controller, and the Twig template worked fine (it uses a for loop to go through the results and print out a table)
So, I was under the assumption that if my custom query fetched a list of results, with all the same columns (just 1 added extra in the sub-select, not the ID mentioned though) then everything should be fine!
But clearly not. It seems as though there is another Array level for some reason and I am unsure as to why?
Here is my custom query in the repo (it does a sub select to create a parent_name column):
public function getCompanyWithParent()
{
$dql = 'SELECT c, (SELECT p.name FROM DEMO\DemoBundle\Entity\User\Company p WHERE p.id = c.parent) as parent_name FROM DEMO\DemoBundle\Entity\User\Company c';
$query = $this->getEntityManager()->createQuery($dql);
return $query->getArrayResult();
}
I have tried both:
return $query->getArrayResult();
and
return $query->getResult();
But to no avail.
Any ideas folks?
Sorted it. Seems I had to specify the columns I wanted to pull through e.g. SELECT c.id, c.name ..., and sadly couldn't just use SELECT c, ... FROM ...

Categories