Posted by Jaypan on 

I have created a custom Entity for blogs. I am not using nodes. The way my entity works is that all languages are stored in a single Entity. The only way to determine if a language has been submitted is if there is a title for that language. As the title is a field, the data for the titles is stores in {field_data_field_blog_title}. The way to determine whether an English version of the Entity in question exists, is to check if there is a row in the {field_data_field_blog_title} table for the current Entity ID and language.

So in hook_views_data_alter() I have added the following code:

function ba_views_data_alter(&$data)
{
    $data['j_blog']['language'] = array
    (
        'title' => t('Blog language'),
        'help' => t('The language of the blog'),
        'filter' => array
        (
            'handler' => 'views_handler_filter_ba_blog_language',
        ),
    );
}

And in my handler, views_handler_filter_ba_blog_language.inc, I have the following:


/**
* Filter by current_language (bundle)
*
* @ingroup views_filter_handlers
*/
class views_handler_filter_ba_blog_language extends views_handler_filter
{
    function value_form(&$form, &$form_state)
    {
        parent::value_form($form, $form_state);
       
        $languages = language_list();
        foreach($languages as $l)
        {
            $options[$l->language] = $l->name;
        }
        $options['current'] = t('Current language');
       
        $form['blog_language'] = array
        (
            '#type' => 'radios',
            '#title' => t('Language'),
            '#options' => $options,
        );
    }

    function value_submit($form, &$form_state)
    {
        $form_state['values']['options']['value'] = $form_state['values']['options']['blog_language'];
    }

    function query()
    {
        global $language;
       
        if (empty($this->value))
        {
            return;
        }
        $this->ensure_my_table();
       
        $value = ($this->value == 'current') ? $language->language : $this->value;

        // THIS IS WHERE I WANT TO ADD MY JOIN
    }
}

Basically, I want to force something like this in my query:
JOIN {field_data_field_blog_title} ON {field_data_field_blog_title}.id = blog.id AND {field_data_field_blog_title}.language = :language

By forcing a join where a row exists with the language, it will ensure that only blogs with the given language are returned.

The problem is that I don't know how to add my table with a join. Can someone give me some assistance with this?

 Categories: Drupal 7.x

Comments

absoludo的头像

Why not try with hook_views_query_alter()?

absoludo commented 

I have not read your question thouroughly, but you can try it with hook_views_query_alter().

You still have to work on this, but you will get something like this:

/**
 * Implements hook_views_query_alter().
 */
function YOUR_MODULE_views_query_alter(&$view, &$query) {
  if ($view->name == 'MY_VIEW') {
    // Determine language, not sure where to find this here
    $language = SOMEWHERE_YOU_MUST_DETERMINE_LANUAGE;

    // Join your tables
    $query->join('field_data_field_blog_title', 'fbt', 'fbt.id = blog.id AND fbt.language = ' . $language);
  }
}
Jaypan的图片

Thanks for the suggestion,

Jaypan commented 

Thanks for the suggestion, and I think I'm on the right track, but unfortunately join() isn't a method on the current class for the $query here, as I get the following error:

Fatal error: Call to undefined method views_plugin_query_default::join()

Any idea how I can add a join on to the query at this point?

absoludo的头像

I always mix up the views queries with dynamic queries

absoludo commented 

I always mix up the views queries with dynamic queries.

Fortunatelly I did do a similair join for a project once:

  // Write the join conditions
  $join = new views_join();
  $join->table = 'field_data_field_blog_title';
  $join->left_table = 'blog';// or wathever table you want to join with
  $join->field = 'id';
  $join->left_field = 'id';// or wathever column you want to join with
  $join->type = 'LEFT';// if you want another join type play with this value

  // Do the actual join
  $query->table_queue['blog_title'] = array (
    'alias' => 'blog_title',// I believe this is optional
    'table' => 'field_data_field_blog_title',
    'relationship' => 'blog',
    'join' => $join,
  );
  $query->where[] = array(
    'conditions' => array(
      array(
        'field' => 'blog_title.language',
        'value' => $language,
        'operator' => '=',
      ),
      // add more conditions if you want to
    ),
    //'type' => 'OR' ,// I believe this is an operator for multiple conditions
  );
ann b的图片

Big Help

ann b commented 

Thank you. I didn't know the database api methods won't work in this hook. This worked perfectly when I needed to sort a workbench view query programmatically.

ann b的图片

Can you do it in ba_views_data_alter() ?

yhuet commented 

I dont think you should add a join there. I believe at this point your table is already joined and has an alias.

Maybe you can do something like this in ba_views_data_alter() :

    $data['j_blog']['table']['join'] = array(
        'blog' => array(
            'left_field' => 'id',
            'field' => 'id',
            'extra' => array(
                array('field' => 'language', 'value' => '***CURRENT_LANGUAGE***'),
            ),
        ),
    );

I hope this helps.

Jaypan的图片

Thank you for your

Jaypan commented 

Thank you for your assistance, but maybe I wasn't clear on my reasoning for doing the join there. The join itself is a bit of a tricky way of forcing a conditional. I don't want my table to always be joined, on the contrary, I will only want it to join if there is a value for the current language.

I think hook_views_query_alter() suggested by absoludo (thanks!) may solve my problem. I'll look at that when I get to the office tomorrow.

mortona2k的头像

I'm working on a similar

mortona2k commented 

I'm working on a similar solution.

I've added a relationship to a field on field_collection item, using hook_data_alter. That allows me to add the relationship to my view. I can throw in 'extra' = "AND field_name ='whatever'" which gives me the exact sql query i'm looking for.

But I need to add this extra to the join dynamically, based on options set when adding the relationship to the view.
Use pre_query to modify $this->definition and add the extra.

Now I just need to define the options form and use those values.

The other Andrew Morton

Jaypan的图片

Nice, this is an issue I have

Jaypan commented 

Nice, this is an issue I have to fix within the next week, so I'll work with that and see if we can't help each other come to a solution on it.

ann b的图片

Yet another way to JOIN

bucefal91 commented 

Hi, guys!

I had to do INNER JOIN in my sort handler and while searching in google I got to this thread. I used the info from comments above to compile my solution which I am gonna share in a second. On a different note, I'd like to say that achieving it via hook_views_query_alter() from my point of view is a bit inprofessional. For example in my case I am building a general purpose module that will be used in many different occasions and of course I can't forsee all possible use cases in hook_views_query_alter(). So...

$table = 'table_i_wanna_join'; // i.e. how the table is represented in drupal database schema, without prefixes.
$left_table = $this->table; // the table to which I want to join it. Mostly I assume you'll want to join it to the table of your handler
$left_field = 'left_id'; // a column in $left_table on which you wanna join
$field = 'id'; // a column in $table on which you wanna join
// We create a new join object and provide it with information about the join we wanna make.
$join = new views_join();
$join->construct($table, $left_table, $left_field, $field, array(), 'INNER');

// Now, here we've kind of compiled the relationship between 2 tables, yet we gotta throw it into the View to take effect.

$alias = 'alias_i_want_for_table'; // Alias under which you wanna see $table once it has been JOINed with all the other tables.
// $this->table_alias below exposes to view under what alias $left_table is manipulated in the SQL we are building.
// $this->relationship below is used when the view has relationships and the same table may be referred for example from the base table of view, or from one of the relationships.
$alias = $this->query->add_relationship($alias, $join, $this->table_alias, $this->relationship);
// Last note, you gotta get $alias as return of this method because the desired alias may be already occupied by some other previously joined table, so this method will kindly adjust your desired alias to some unique string based on the original value of $alias.

Above is a general case. Jaypan, as far as I got it from your post, you will need to use parameter $extra in views_join::construct() method to pass on the language restriction, and your left_field and field will be the IDs of your entities. It took me the whole evening to figure it out, so I decided it'd be worth to post it somewhere :)

http://takearest.ru - my Drupal baby :)

Jaypan的图片

Thanks for posting - I still

Jaypan commented 

Thanks for posting - I still haven't resolved this problem, and it's on the back burner, but something that needs to be fixed. I'll look closely at your post and see if I can get it to work now. Thanks!

Jaypan的图片

Thank you again for the code,

Jaypan commented 

Thank you again for the code, but you did not give the context in which it is used. It would appear you are not doing so in hook_views_query_alter(), so where is it? Have you written a custom handler? What function is this that we are looking at?

ann b的图片

Yes, the snippet I showed

bucefal91 commented 

Yes, the snippet I showed should be from a custom handler's query() method. I thought to include this informaiton into my comment, but now I don't see edit link, so I guess I can't fix it :(

I read your 1st post in this thread, basically you should use my spinnet right where you had this comment:

// THIS IS WHERE I WANT TO ADD MY JOIN

http://takearest.ru - my Drupal baby :)

Jaypan的图片

Thank you bucafel91, much

Jaypan commented 

Thank you bucafel91, much appreciated.

Jaypan的图片

With your help, I was able to

Jaypan commented 

With your help, I was able to come up with a solution that works. It's not my ideal solution, but it works.

What I wanted to was join the blog title table on two fields, the ID and the language. This would effectively filter blogs by the given languages. Unfortunately, I couldn't figure out how to join on two fields, so in the end I used a condition (WHERE) to choose only blogs that have a title in the second language. This means the query won't be as fast as if I were joining on both fields with no WHERE statement, but I could not figure out how to join on multiple fields, so I am making do with what I have. It's really just academic, as I am only selecting 10 results, so the difference is negligible. That said, if someone else knows how to join a table on multiple fields, please feel free to explain!


/**
* Filter by current_language (bundle)
*
* @ingroup views_filter_handlers
*
*/
class views_handler_filter_ba_blog_language extends views_handler_filter
{
	function value_form(&$form, &$form_state)
	{
		parent::value_form($form, $form_state);

		$view = $form_state['view'];
		$keys = array();
		foreach($view as $k => $v)
		{
			$keys[] = $k;
		}

		$languages = language_list();
		foreach($languages as $l)
		{
			$options[$l->language] = $l->name;
		}
		$options['current'] = t('Current language');

		$form['blog_language'] = array
		(
			'#type' => 'radios',
			'#title' => t('Language'),
			'#options' => $options,
			'#default_value' => $form_state['values']['blog_language'],
		);
	}

	function value_submit($form, &$form_state)
	{
		$form_state['values']['options']['value'] = $form_state['values']['options']['blog_language'];
	}

	function query()
	{
		global $language;
		
		// If no value has been given, then we don't need to do anything.
		if(empty($this->value))
		{
			return;
		}

		$this->ensure_my_table();
		
        // Determine the language on which to filter. If the given value is 'current'
        // Then we use the current language. Otherwise we use the provided language 
        $selected_language = ($this->value == 'current') ? $language->language : $this->value;

		$table = 'field_data_field_blog_title'; // The table to be joined
		$left_table = $this->table; // the table to be joined to
		$left_field = 'jeid'; // a column in $left_table on which to join
		$field = 'entity_id'; // a column in $table on which to join
		// We create a new join object and provide it with information about the join
		$join = new views_join();
		$join->construct($table, $left_table, $left_field, $field, array('blog_title_language.language' => $selected_language), 'INNER');

		// Add a condition so that only items with the selected langauge are shown
		$this->query->add_where(0, 'blog_title_language.language', $selected_language, '=');
	}
}
ann b的图片

I am glad you were able to

bucefal91 commented 

I am glad you were able to achieve it! :)

Last note, on the additional conditions for JOIN. The argument $extra for method $join->construct(). Easiest case is to provide a string there. Then your JOIN will be:
JOIN .... ON left_table.left_field = table.field AND ($your_extra_string)
If you want to go a bit more heuristic, or if alias of either table is not yet known to you, first of all study the code of views_join::build_join() to see how it handles $extra. But generally speaking the structure should be something like the following:

$extra = array();
$extra[] = array(
  'table' => 'table_alias_here',
  'field' => 'field_name_here',
  'value' => 1,
  'operator' => '>',
);
// and so on, as many times, as many extra conditions you need.

http://takearest.ru - my Drupal baby :)

Jaypan的图片

Thank you bucefal! I was

Jaypan commented 

Thank you bucefal! I was trying to get 'extra' to work last night, but I was passing it a string.

ann b的图片

Thanks, i have modified it for my use case :

derekrezek commented 

class views_handler_filter_inbound_calling_filter_source extends views_handler_filter {

  function value_form(&$form, &$form_state) {
    parent::value_form($form, $form_state);
    if (!empty($form_state['exposed'])) {
      $form['value'] = array(
        '#type' => 'select',
        '#title' => t('Select Source'),
        '#options' => inbound_calling_source_options(),
      );
    }
//    else {
//      $form['value'] = array(
//        '#type' => 'hidden',
//        '#value' => $this->options['filter_default_value'],
//      );
//    }
  }

  function query() {
    $this->ensure_my_table();
    if (is_array($this->value)) {
      $value = array_filter($this->value);
    }
    else {
      $value = $this->value;
    }
    $table = 'inbound_calling_complain_source'; // The table to be joined
    $left_table = $this->table; // the table to be joined to
    $left_field = $this->real_field; // a column in $left_table on which to join
    $field = 'ibcl_hid'; // a column in $table on which to join
    // We create a new join object and provide it with information about the join
    $join = new views_join();
    $extra = array();
    $extra[] = array(
      'table' => 'alias_i_want_for_table',
      'field' => 'source',
      'value' => $value,
      'operator' => '=',
    );
    $join->construct($table, $left_table, $left_field, $field, $extra, 'INNER');
    $alias = 'alias_i_want_for_table';
    $alias = $this->query->add_relationship($alias, $join, $this->table_alias, $this->relationship);

//    $this->query->add_where(0, 'alias_i_want_for_table.source', $value, '='); 
// this where clause was working, but wanted to add the AND in the join itself to improve efficiency, so after adding the $extra, i commented it out.

  }

}

ann b的图片

the join i get in the query:

derekrezek commented 

INNER JOIN {inbound_calling_complain_source} alias_i_want_for_table ON inbound_calling_calllog_header.ibcl_hid = alias_i_want_for_table.ibcl_hid AND alias_i_want_for_table.source = '2'

here 2 is the dynamic value assigned to $value

来自  https://www.drupal.org/node/2049051



在hook_views_query_alter()中添加JOIN语句

我需要修改查询生成的视图,以便我可以使用高度自定义过滤器。我已经实现了add_where()函数与一些ORs感谢这个问题:在Drupal视图过滤器中的OR运算符

但这只解决了我的问题的一部分。有一些字段,我无法过滤,因为我需要在查询中有额外的JOIN。

有什么东西沿线

$view->query->add_where()

可以插入JOIN语句?

6月28日在'10 18:06
自动存储塔
1,15311227
 
   
嗯,有$view->query->add_table()$view->query->add_relationship(),但他们的使用,关系和前提条件是不明显的代码(至少不是为我)。您不能只通过视图UI添加关系,以确保您需要的表加入? 评论时间70年01月01日原作者:  Henrik Opel评论 时间70年01月01日原作者:
   
@Henrik在察看模块中的query.inc文件后,我发现了你提到的函数。除了评论之外没有太多的东西,所以使用有点模糊。此外,让你的评论一个答案,所以我可以接受它。 评论时间70 年01月01日原作者:  Jukebox
   
Yup,使用不明显,并且文档缺乏 - 我没有尝试使用它们,所以我不能真正地帮助。打开评论,回答建议 - 谢谢。 

正确答案 

 
2下投票接受

嗯,有$view->query->add_table()$view->query->add_relationship()(在'includes / query.inc'中的views_query类中),但是它们的使用,关系和前提条件从代码中是不明显的(至少不是我的)。

也许你可以通过视图UI添加关系,以确保你需要的表加入。
 

 
实际上,在视图UI中添加另一个关系对我有用。所有我需要做的是看看从视图生成的查询,以找出需要进入add_where函数的表。


 

在这里找到了答案无耻的副本:

function hook_views_query_alter(&$view, &$query) {
    $join = new views_join();
    $join->table = 'my_table';
    $join->field = 'my_field';
    $join->left_table = 'left_table';
    $join->left_field = 'left_field';
    $join->type = 'left';
    $join->extra = array(
        array(
            'field' => 'bundle',
            'value' => 'user',
        )
    );
    $query->add_relationship('relationship_name', $join, 'node');
}

来自  http://stackoverflow.com/questions/3134941/adding-a-join-statement-in-hook-views-query-alter