How to query all related items in a many to many relationship using Pods API? - wordpress

I have two Pods:
course
participant
The pod course has a PICK field to the pod participant. The field is a multiple relationship field. So, each course item has multiple participants.
I want to find all the course items where a certain participant is related.
So, I guess a SQL query such as the following would do what I want:
SELECT DISTINCT `t`.* FROM `wp_pods_course` AS `t` WHERE t.id IN
(SELECT DISTINCT r.item_id FROM wp_podsrel AS r WHERE r.related_item_id = '42')
42 is the id of a participant.
I am trying to figure out how to write such a SQL query using Pods API:
$pod = pods('course');
$participant_id = $participant->field('id');
$params['where'] = "t.id in (SELECT r.id FROM ??? WHERE ???)";
$pod->find($params);
Is this the correct way to write such a query?

You're overcomplicating things, Pods does all of the joins for you automatically with one of it's most powerful features, field traversal.
Try this:
$pod = pods( 'course' );
// I use ->id() because it's always the right ID field, no matter what pod type
$participant_id = $participant->id();
$params = array(
'where' => 'participants.id = ' . (int) $participant_id
);
$pod->find( $params );
Where particpants is your relationship field name, id is the field id on that related object.

Related

Drupal entityQuery multiple node types and join

Looking to join multiple nodes via a query and join. Let's say one node has a matching ID of another field in another node. I would like to join them into an array so that I can output other fields with them indexed together on the same ID. Example: node1.nid and node2.field.target_id
How do I do this?
$nids = \Drupal::entityQuery('node')->accessCheck(FALSE)
->join('node2', 'n2', 'n.nid = n2.field.target_id');
->condition('type', 'node1', 'node2')
->sort('title')->execute();
$nodes = \Drupal\node\Entity\Node::loadMultiple($nids);
$response = array();
This is definitely doable and it looks like you're almost there, but it looks like you're combining the Database API with the Entity API, specifically trying to run Database joins on EntityQuery fetches.
Now, if you want to continue the Database query route, which may make it a little easier to join the values of multiple entities and output them, this is what I would recommend:
The first thing that I notice is that you're attempting to chain the join. Unfortunately, according to the Database API Docs
Joins cannot be chained, so they have to be called separately (see Chaining).
I have done similar to this with a join between two custom content types. Here is the code I used:
$connection = Database::getConnection();
if ($connection->schema()->tableExists('companies') && $connection->schema()->tableExists('users')) {
$query = $connection->select('companies', 'c');
$query->join('users', 'u', 'c.sourceid1 = u.sourceid1');
$results = $query->fields('u', ['destid1', 'sourceid1'])
->fields('c', ['destid1'])
->execute()
->fetchAll();
return $results;
}
Now this assumes that you've brought in Drupal\Core\Database\Database, but let's walk through the process.
$connection = Database::getConnection();
This is fetching the database connection for your site
if ($connection->schema()->tableExists('companies') && $connection->schema()->tableExists('users')) {
This is a check I always do to make sure the tables I'm working with exist.
$query = $connection->select('companies', 'c');
One of your tables needs to be the "base" table. In this case, I chose companies.
$query->join('users', 'u', 'c.sourceid1 = u.sourceid1');
This line is where the join actually happens. Notice that it's not chained, but it's own command. We're joining these tables, users and companies, on this sourceid1.
$results = $query->fields('u', ['destid1', 'sourceid1'])
->fields('c', ['destid1'])
->execute()
->fetchAll();
This is where I'm pulling whichever fields I want from both entities. In this case I want destid1 and sourceid1 from my user table and destid1 from my company table.
The ->execute() call actually runs the query, and ->fetchAll() returns an array of all matching results. This will get you to being able to JSONify all the things.
However, if you want to stick with the entityQuery route, that's also viable. My personal preference is to use a regular query for more complex things and an entityQuery if I just need to get a value from a single field or return a single entity.
For an entityQuery, well, that's a bit different. I wouldn't really recommend using an entityQuery for a join here. Instead, using entityTypeManager if you know or can get the matching value from both tables.
For example, let's assume that field_content_type_a_id on content type A matches field_related_type_a_id on content type B.
You could load one, let's go with type A for now:
// Get the current NID somehow... that's up to you
$entity_a = \Drupal::entityTypeManager()->getStorage('node')->load($nid);
// Get the value of the field you want to match to the other content type
$matching_value = $entity_a->get('field_content_type_a_id')->getValue();
// Get the entities with matching type A values
$entity_b_array = \Drupal::entityTypeManager()->getStorage('node')
->loadByProperties(['field_related_type_a_id' => $matching_value]);
// Do something with $entity_b_array.

One of listeners must count and slice given target

I'm getting the above error when performing a search in a Symfony2 CRM I've been working on. According to Google searches it seems this is an issue relating to the KNP Paginator bundle, but I cannot seem to find a solid solution.
In this instance, I am using data from an OpenCart database, and I need to be able to search by Postcode and Company name, both of which exist in the address table which is joined via a mapped value within the Customer entity, defaultaddress.
To this end, I've had to write a custom query in a function called findCustomerByPostcode like so:
$this->getEntityManager()
->createQuery('SELECT c FROM AppBundle:Oc49Customer c JOIN AppBundle:Oc49Address a WITH c.defaultaddress = a.id WHERE a.postcode LIKE :postcode')
->setParameter('postcode','%'.$postcode.'%')
->getResult();
However, when I perform a search on postcode, I get the following error in the browser:
One of listeners must count and slice given target
which refers to the Paginator.php file within the KNP bundle. I have updated to the most recent version, 2.5 yet I cannot seem to shake this error, and to me it does not even make sense.
Any help is much appreciated, as I cannot think of another way of search via a value within a joined table.
Which returns results from the postcode search, and then in the Controller:
$customers = $customer_repository->findCustomerByPostcode($filter_value);
$paginator = $this->get('knp_paginator');
$pagination = $paginator->paginate(
$customers,
$request->query->get('page', 1),
20
);
You can use the queryBuilder with the left join
public function findCustomerByPostcode($postcode) {
$query = $this->entityManager->createQueryBuilder();
$query
->select('c', 'a')
->from('AppBundle:Customer', 'c')
->leftJoin('c.address', 'a')
->setParameter('postcode', $postcode)
return $query->getQuery()->getResult();
}

Multiple orderby within Criteria

I currently use the Criteria to filter a collection of objects. But when I want to achieve with 2 orderBy fields, only the first is considered. I do not understand.
$events = new Collections\ArrayCollection($results);
$dateFrom = new \DateTime($date);
$dateTo = new \DateTime(date('Y-m-d H:i:s', strtotime($date . ' + 1 day')));
$criteria = Criteria::create()
->where(Criteria::expr()->eq('activity', $activity));
$criteria->orderBy(array(
"time" => "ASC",
"title" => "ASC"
));
How can I make it work with two orderby fields and not only the first ?
Thank you in advance for any answers !
Your code is correct, assuming that $criteria is then applied correctly to the ArrayCollection.
Judging by the names of your fields you're trying to order by, it's possible that title doesn't affect the order because time doesn't repeat among the elements in the collection.
If this isn't the case, please provide more information on the results you're getting and I will update my answer.
Update (in response to additional data provided that has since been deleted):
You are sorting the collection correctly. The problem is that you're then feeding the ArrayCollection into the Paginator, which apparently cannot sort by more than one field.
There's an open issue in Knp's tracker about this: https://github.com/KnpLabs/KnpPaginatorBundle/issues/109.

Doctrine2 left join on multiple levels making multiple requests

I have 4 entities that are related in hierarchical levels: Company, Department and Employee. Company and Department are related with a ManyToOne bidirectional relation. Department and Employee are related through another entity with 2 OneToMany bidirectional relations because I needed additional parameters for the relation. So basically the final schema is this :
Company <-> Department <-> DepartmentEmployee <-> Employee
I'm trying to select one department from the company of the current user and to get all the employees of this department. I'm using a custom repository to build my query with the query builder like this:
// DepartmentRepository.php
public function getOneWithEmployees($slug, $company)
{
$qb = $this->createQueryBuilder('d')
->where('d.slug = :slug')
->andWhere('c.slug = :company')
->setParameters(array('slug' => $slug, 'company' => $company))
->leftJoin('d.company', 'c')
->addSelect('c')
->leftJoin('d.departmentEmployee', 'r')
->addSelect('r')
->leftJoin('r.employee', 'e')
->addSelect('e');
return $qb->getQuery()->getOneOrNullResult();
}
The point being to reduce the number of queries made, but when I execute this query, I still get 32 queries made to the database (I have 15 employees in the department).
When I remove the part
->leftJoin('r.employee', 'e')
->addSelect('e')
I get only one query executed like expected.
How can I do a left join on a left join without triggering multiples queries?
My Employee entity is the inverse side of 2 OneToOne relations: User and Invitation. When I explicitly include these relations in the query with left join, no extra queries are made, but if I leave them out then Doctrine automatically makes queries to fetch them. Looking in the Doctrine FAQ I found this:
4.7.1. Why is an extra SQL query executed every time I fetch an entity with a one-to-one relation?
If Doctrine detects that you are fetching an inverse side one-to-one association it has to execute an additional query to load this object, because it cannot know if there is no such object (setting null) or if it should set a proxy and which id this proxy has.
To solve this problem currently a query has to be executed to find out this information.
Link
So the only solution to avoid extra queries is to build my query like this:
$qb = $this->createQueryBuilder('d')
->where('d.slug = :slug')
->andWhere('c.slug = :company')
->setParameters(array('slug' => $slug, 'company' => $company))
->leftJoin('d.company', 'c')
->addSelect('c')
->leftJoin('d.departmentEmployee', 'r')
->addSelect('r')
->leftJoin('r.employee', 'e')
->addSelect('e')
->leftJoin('e.user', 'u')
->addSelect('u')
->leftJoin('e.invitation', 'i')
->addSelect('i');
return $qb->getQuery()->getOneOrNullResult();

Add Table Join, Where, and Order By to Views Query in views_query_alter()

I am trying to modify the query for Views in Drupal (Views version 3, Drupal version 7).
What I want to do is change the query prior to running such that it LEFT JOINs a table in which I have weights assigned to the nodes.
If I was to write the query I want in SQL, it would look like this:
SELECT a.nid, a.title, a.description
FROM node a
LEFT OUTER JOIN node_weights b
ON a.nid = b.nid
WHERE b.uid = $uid
ORDER BY b.weight DESC
This query works like a champ when I run it in the query analyzer. So, now I need to make it work in my module.
I've seen multiple approaches detailed on various blogs for different ways to modify View queries, but they seem to be addressing different versions of Views. So it is very confusing to try to determine whether anything I'm looking at could even possibly work for my application.
It seems that I need to use a MODULE_NAME_views_tables() function to tell Views what the relationship is between the table I want to join and the node table.
I've added the following functions to MODULE_NAME.views.inc:
function MODULE_NAME_views_tables() {
$tables['node_weights'] = array(
"name" => "node_weights",
"join" => array(
"left" => array(
"table" => "node",
"field" => "nid"
),
"right" => array(
"field" => "nid"
),
),
);
return $table;
}
This does seem to be working because when I use Krumo to look at the query array, I see my "node_weights" table in the "table_queue" element.
In the views_query_alter() function, I'd like it to work something like this:
function MODULE_NAME_views_query_alter(&$view, &$query) {
$uid = $_COOKIE['uid'];
$view->query->add_relationship('node_weights', new views_join('node_weights', 'nid', 'node', 'nid','LEFT'));
$view->query->add_where('node_weights', "node_weights.uid", $uid);
krumo($query);
}
This function barfs pretty badly. Although my join table is appearing in the $view object, the add_relationship method is throwing an error for a 3rd argument, but I don't see any examples online that have 3 arguments so I don't know what it's missing.
Also, I'm pretty sure my add_where method isn't correct, but I don't know what the inputs should actually be. This is just a blind guess.
The bottom line is that I want to join the node table to my node_weights table, and then make sure my weights are used in the query to sort the results in a descending fashion where the user id = the user id in my table, and the tables are joined on the nid field.
Thanks in advance.
WHEREs are pretty easy to add once you've got the JOIN in. You can both in a query alter (Drupal 7).
function MODULE_NAME_views_query_alter(&$view, &$query){
// Only alter the view you mean to.
if($view->name == 'VIEW NAME' && $view->current_display == 'DISPLAY'){
// Create the join.
$join = new views_join();
$join->table = 'table_name';
$join->field = 'entity_id';
$join->left_table = 'node';
$join->left_field = 'nid';
$join->type = 'left';
// Add the join the the view query.
$view->query->add_relationship('table_name', $join, 'node');
// Add the where.
$view->query->where[1]['conditions'][] = array(
'field' => 'table_name.collumn_name',
'value' => 'value',
'operator' => '='
);
}}
I found the OP's comments helpful in creating a join in the hook_views_query_alter function, so I wanted to put the parts I found useful in a more digestible answer.
I was using Views 2x on Drupal 6x, but I assume it would be very similar to use on D7 Views 2.
The OP mentions describing the relationship of the join in hook_views_table. This wasn't necessary for me, as I was not linking to a custom table, but one that existed in core.
The join creation in the HOOK_views_query_alter() function was very helpful though:
$join = new views_join;
$join->construct('table_name',
'node', // left table
'nid', // left field
'nid', // field
)
See views_join::construct documentation for more information. In particular, I didn't need to use the 'extra' parameter that the OP used. Perhaps this is necessary with a custom table.
Finally, add the join to the query, and whatever other elements are needed from it:
// Add join to query; 'node' is the left table name
$view->query->add_relationship('table_name',$join,'node');
// Add fields from table (or where clause, or whatever)
$view->query->add_field('table_name','field_name');
...
You already have the $query in parameters, so you can just do:
myhook_views_query_alter(&$view, &$query) {
if ($view->name = ....) {
$join = new views_join();
$join->construct(
...
);
$query
->add_relationship(...)
->add_where(...)
->add_orderby(...)
...
;
}
No need to use $view->query->...

Resources