How to Join with Native SQL Query and doctrine - sql

I'm developping an application with symfony 3.4. I want to execute a specific query. So i have two entities: the first is PrPurchaseRequest. the second is PrSpecificFieldValue. PrPurchaseRequest has oneToMany prSpecificFieldValues.
I want to get id of purchaseRequest and prSpecificFieldValues
i did that
$queryBuilder = $this->getEntityManager()->createQuery('select p.id as purchaseId, pr.keyField AS keyField,pr.ID AS prkeyvalueid from '.PrPurchaseRequest::class. ' p LEFT JOIN '. PrSpecificFieldValue::class .' spec ON p.id = spec.purchaseId ');
and that didn't work for me
[Syntax Error] Error: Expected end of string, got
'ON'
how can i do it

Using doctrine you need to play around your entities and their mappings with other entities in order to relate them like
use Doctrine\Common\Collections\ArrayCollection;
/** #Entity */
class PrPurchaseRequest
{
/**
*
* #OneToMany(targetEntity="PrSpecificFieldValue", mappedBy="prPurchaseRequest")
*/
private $prSpecificFieldValues;
// ...
public function __construct() {
$this->prSpecificFieldValues = new ArrayCollection();
}
}
/** #Entity */
class PrSpecificFieldValue
{
/**
*
* #ManyToOne(targetEntity="PrPurchaseRequest", inversedBy="prSpecificFieldValues")
* #JoinColumn(name="pr_purchase_request_id", referencedColumnName="id")
*/
private $prPurchaseRequest;
}
Now you have defined relationship between your entities you can join them based on their mapping (prSpecificFieldValues defined on PrPurchaseRequest class ) like
Its DQL (DQL != SQL)
SELECT p,v
FROM PrPurchaseRequest p
JOIN p.prSpecificFieldValues v
No need to specify ON clause doctrine will handle this for you.
One-To-Many, Bidirectional

Related

Doctrine ORM, many to many, lazy loading. Retrieve an Entity and fetch join all the feautures

I have Zend Framework 3 + Doctrine ORM application.
Class Goods have link "characters":
/**
* Goods
*
* #ORM\Entity
* #ORM\Table(name="goods")
* #property int $id
*/
class Goods implements InputFilterAwareInterface
{
/**
* #ORM\ManyToMany(targetEntity="\Application\Entity\CharacterValue", inversedBy="goods")
* #ORM\JoinTable(name="character_value_item",
* joinColumns={#ORM\JoinColumn(name="good_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="value_id", referencedColumnName="id")})
**/
protected $characters;
public function getCharacters()
{
return $this->characters;
}
}
I trying to use this method to get characters by method for lazy loading, but it returns just one character. Not all characters for the product.
$dql = 'SELECT u, ch FROM Goods u LEFT JOIN u.characters ch';
This method from here:
$query = $em->createQuery('SELECT u, p FROM CmsUser u JOIN u.phonenumbers p');
$users = $query->getResult(); // array of CmsUser objects with the phonenumbers association loaded
$phonenumbers = $users[0]->getPhonenumbers();
https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/dql-doctrine-query-language.html#dql-select-examples
I do not understand why documentation's method working wrong. What is the right way to deside my issue?

Moodle get SQL data but don't get all

I'm working on a plugin for showing all users completed courses.
But I only get 10 records, when I place the SQL inside my database I get 40+. I think there is a limit or it does only return 1 course from every user.
Any tips?
externallib.php file:
/**
* External Web Service Template
*
* #package specialist_in_websites
* #copyright 2011 Moodle Pty Ltd (http://moodle.com)
* #license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once($CFG->libdir . "/externallib.php");
class specialist_in_websites_users_courses_external extends external_api
{
/**
* Returns description of method parameters
* #return external_function_parameters
*/
public static function hello_world_parameters()
{
return new external_function_parameters(
array()
);
}
/**
* Returns welcome message
* #return string welcome message
*/
public static function hello_world()
{
global $DB;
$sql = 'SELECT u.id as user_id, c.id as course_id, DATE_FORMAT(FROM_UNIXTIME(p.timecompleted),"%Y-%m-%d") AS completed_time
FROM mdl_course_completions AS p
JOIN mdl_course AS c ON p.course = c.id
JOIN mdl_user AS u ON p.userid = u.id
WHERE c.enablecompletion = 1 ORDER BY u.id';
$datas = $DB->get_records_sql($sql);
return (array)$datas;
}
/**
* Returns description of method result value
* #return external_description
*/
public static function hello_world_returns()
{
return new external_multiple_structure(
new external_single_structure(
array(
'user_id' => new external_value(PARAM_INT, 'user id'),
'course_id' => new external_value(PARAM_INT, 'course id'),
'completed_time' => new external_value(PARAM_TEXT, 'Time of Completed'),
)
)
);
}
}
and response code:
string(510) "[{"user_id":2,"course_id":12,"completed_time":null},{"user_id":3,"course_id":10,"completed_time":null},{"user_id":4,"course_id":9,"completed_time":null},{"user_id":5,"course_id":41,"completed_time":null},{"user_id":6,"course_id":14,"completed_time":null},{"user_id":7,"course_id":10,"completed_time":null},{"user_id":8,"course_id":9,"completed_time":null},{"user_id":9,"course_id":9,"completed_time":null},{"user_id":10,"course_id":10,"completed_time":null},{"user_id":11,"course_id":10,"completed_time":null}]"
As stated in the docs the $DB->get_records_*() functions get a hashed array of records, indexed by the first field returned.
So, if you return more than one record with the same user_id field, then they will overwrite the previous record in the array.
Make sure you turn on debugging when developing for Moodle and you will see warning messages about this.
Solutions - either find a different field to be the first field returned (e.g. course_completions.id) or use $DB->get_recordset_sql() and then loop through the results.
Also, do not use the 'mdl_' prefix in your code, as that will break on any site with a different prefix, or if you try to run any behat or unit tests on your site. As stated in the docs, write:
FROM {course_completions} p
JOIN {course} c ON p.course = c.id
etc.
(and don't use AS with tables, as that's not compatible with all DB types)

Doctrine Many to Many relations

i edit the thread for put more info.
I have the "User" entity and the "Rol" entity, and i am fighting to do work the collection of Roles of a User.
In the User entity i defined:
/**
* #ManyToMany(targetEntity="AppsManantiales\CommonBundle\Entity\Perfil")
* #JoinTable(name="usuarios_perfiles",
* joinColumns={#JoinColumn(name="idUsuario", referencedColumnName="idusuario")},
* inverseJoinColumns={#JoinColumn(name="idPerfil", referencedColumnName="idperfil")}
* )
*/
protected $perfiles;
And in the constructor:
public function __construct(){
$this->perfiles = new \Doctrine\Common\Collections\ArrayCollection();
$this->contacto = new \Doctrine\Common\Collections\ArrayCollection();
}
Before the class namespace put:
use AppsManantiales\CommonBundle\Entity\Perfil;
When execute:
php app/console generate:doctrine:entities CommonBundle
An error appear:
[Doctrine\Common\Annotations\AnnotationException]
[Semantical Error] The annotation "#ManyToMany" in property AppsManantiales\CommonBundle\Entity\Usuario::$perfiles was never impo
rted. Did you maybe forget to add a "use" statement for this annotation?
Any ideas ?.
First part: So in this case, u got a relation many-to-many between Role entity and User entity. First of all, check, r entities correct after generting. Here u can find examples of establishing different realtions: http://docs.doctrine-project.org/en/latest/reference/association-mapping.html && http://docs.doctrine-project.org/en/2.0.x/reference/association-mapping.html (the second has more information with examples of Doctrine queries)
Second part of your question: after establishing right relations, select query of your User gonna be smth like:
$user = $em->createQueryBuilder()
->select('u, r')
->from('YourBundle:User', 'u')
->innerJoin('u.roles', 'r')
->where('u.id IN (:ids)')
->setParameter('ids', $ids)
->getQuery()
->getResult();
And as u guess, u can get roles with the help of your accessor: $user->getRoles()
p.s. yes, ofcource if all entities r correct, u can add methods manually.
EDITED
Oh sry, I forgot, u use Symfony2. So by default in your entities, u got such line:
use Doctrine\ORM\Mapping as ORM;
As u can notice, all annotations u used were with prefix #ORM\. exmpls:
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
So just add prefix #ORM and the result:
/**
* #ORM\ManyToMany(targetEntity="AppsManantiales\CommonBundle\Entity\Perfil")
* #ORM\JoinTable(name="usuarios_perfiles",
* joinColumns={#ORM\JoinColumn(name="idUsuario", referencedColumnName="idusuario")},
* inverseJoinColumns={#ORM\JoinColumn(name="idPerfil", referencedColumnName="idperfil")}
* )
*/

Doctrine2 update many-to-many relations

I hava relations Many-to-Many with Product entity and Feature entity
Product entity:
/**
* #ORM\ManyToMany(targetEntity="Feature")
* #ORM\JoinTable(name="Product_Feature",
* joinColumns={#ORM\JoinColumn(name="Product_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="Feature_id", referencedColumnName="id")}
* )
*/
private $features;
Feature entity:
/**
* #ORM\ManyToMany(targetEntity="Product", mappedBy="features")
* #ORM\OrderBy({"position" = "ASC"})
*/
private $products;
ProductRepository.php:
public function updateFeatures($id, $featuresIds)
{
return $this->getEntityManager()->createQueryBuilder()
->update('TestCatalogBundle:Product', 'p')
->set('p.features', ':features')
->where('p.id = :id')
->setParameter('features', $featuresIds)
->setParameter('id', $id)
->getQuery()
->getResult();
}
But when I call updateFeatures I get error:
features = :features': Error: Invalid PathExpression.
StateFieldPathExpression or SingleValuedAssociationField expected
How can I update Product_Feature table? Also I can't delete all features from Product_Feature by product's id.
I changed my controller in next way:
$em = $this->getDoctrine()->getEntityManager();
$features = $em->getRepository('TestCatalogBundle:Feature')->findBy(array('id' => $featureIds));
$product = $em->getRepository('TestCatalogBundle:Product')->find($id);
$product->getFeatures()->clear();
foreach ($features as $feature) {
$product->addFeature($feature);
}
$em->persist($product);
$em->flush();
But if I use native sql I need 2 queries for deleting features and insert new features. But here I need 2 select queries. Maybe I made this task wrong?
You're doing it the wrong way. You should read this chapter of the documentation: Working with associations. You should add an "inversedBy" keyword in the $features field of the Product class.
When you have a bi-directional many-to-many relation, the usual way to do this is:
$product->getFeatures()->add($feature); // Or $product->setFeatures($features);
$feature->getProducts()->add($product);
$em->persist($product);
$em->persist($feature);
$em->flush();

Doctrine 2.1 DQL - Many-to-many query multiple values - Item in multiple categories?

This may seem like a rudimentary request, but I can't seem to get it to work, so I'm either missing something stupid, or am not understanding how it should be done. Thanks in advance.
I have two doctrine entities with a many-to-many relationship: Items and Categories. Which are joined by items_has_categories.
/**
* Item
*
* #Table(name="items")
* #Entity(repositoryClass="Entity\Repository\Item")
*/
class Item
{
....
/**
* #var Categories
*
* #ManyToMany(targetEntity="Categorie", inversedBy="items", cascade={"persist"})
* #JoinTable(name="items_has_categories",
* joinColumns={
* #JoinColumn(name="items_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #JoinColumn(name="categories_id", referencedColumnName="id")
* }
* )
*/
private $categories;
....
}
/**
* Categorie
*
* #Table(name="categories")
* #Entity(repositoryClass="Entity\Repository\Categorie")
*/
class Categorie
{
.....
/**
* #var Items
*
* #ManyToMany(targetEntity="Item", mappedBy="categories")
*/
private $items;
....
}
And what I'm trying to do is run a query to return all items that are in all of "x" number of categories - which I think/thought should be a SELECT with and AND clause:
class Item extends EntityRepository
{
public function findItemsByCategories($categories)
{
$qString = 'SELECT j, t, c FROM Technique\Entity\Item j LEFT JOIN j.itemImages t JOIN j.categories c WHERE';
$i = 0;
foreach ($categories as $c)
{
$qString .= ' c.name = ?' . $i;
if ($i < (count($categories)-1))
{
$qString .= ' AND';
}
$i++;
}
$query = $this->_em->createQuery($qString);
$query->setParameters($categories);
return $query->getResult();
}
That little bit of code has no errors and spits out the following DQL SELECT query (when 2 categories are sent in the array: $categories):
SELECT j, t, c FROM Technique\Entity\Item j LEFT JOIN j.itemImages t JOIN j.categories c WHERE c.name = ?0 AND c.name = ?1
This is always returning an empty array, i.e., no results. Even though in my DB, there are more than 20 items fitting the criteria: are in both categories.
Anyone see what I'm doing wrong here? Is this supposed to be an AND selection...? Basically I'd just like to know how to query a many-to-many relationship in Doctrine 2+, where there's more than one value that must be met...
For anyone interested, I figured it out (painfully). Doctrine should really explain this better instead of the the one line they have on the DQL page....
Basically it's not an AND query, it's a MEMBER OF AND query. For each category a MEMBER OF must be created and then added to the whole query with an AND:
SELECT j, t FROM Entity\Item j
LEFT JOIN j.itemImages t
WHERE ?0 MEMBER OF j.categories AND ?1 MEMBER OF j.categories
AND ?2 MEMBER OF j.categories, etc.
That will return all items that are in all categories requested.