The cure to duplicate nodes in a View when using multi-value fields

I recently had to build a list of events using Drupal Views 2 which included a repeating date field. The problem was that for those nodes that have multiple dates, the node would be duplicated in the view as many times as the date field has dates. Here’s a solution.

In my case, I have an event with three dates.  All of the dates are set in one date field.  The result can be seen by clicking on the screenshot to the right.

I only wanted the node to display once in the view. I edited my view display values. I set Distinct to Yes and checked “Group multiple values” on the field’s configuration form. But these did nothing to suppress the duplicates. Knowing a thing or two about MySQL, I can see why Views is returning three rows – the join requires this.

Still, there should be some way to force the removal of duplicate node IDs after the query runs.

Many Others Looking for a Solution

There are countless issues filed for this problem with many declarations of a fix only to have those good feelings dashed when somebody re-opened the issue with someone saying it doesn’t work for them:

And I’m sure there are more. In the end, there was nothing I could click, check or select to fix this problem for me. Looks like I’m going to have to dive into the code – which is fine – I just wanted to be sure I wasn’t recreating something. I should also note that I created a hack for this in the template layer for Views 1/Drupal 5, but that no longer works.

What I needed was the ability to manipulate the results after the query and before any rendering.

Enter hook_views_pre_render()

I called my friend and colleague Ben Di Maggio over at Digital Loom to discuss the issue. After a little groaning we started to go through the Views 2 API which is available from the Views project page.

I immediately saw hook_views_pre_render() and I was over-joyed. I threw that function name into Google and found a blog post comment that got me 90% there.

I created a module with that single function override. I interrogated the $view->results object to be sure I was only acting on the “events” module and then write a loop to build a new results array with unique node IDs.

Views Remove Duplicates

I named my module ‘viewsremoveduplicates’ and so you will see that reference throughout.

First create a directory named ‘viewsremoveduplicates’ in your sites/all/modules/ folder.  You will then add two files.  The first is the file:

name = Views Remove Duplicates
description = Removes duplicate nodes. Requires editing the module file 
to identify the views you want to affect.
package = "Views"
core = 6.x
dependencies[] = views

Then create the module file named viewsremoveduplicates.module

function viewsremoveduplicates_views_pre_render(&$view)
  $used_nids = array();

  if ($view->name == 'events')
    if ($view->current_display == 'page_2')
      foreach ($view->result as $row)
        if (!in_array($row->nid, $used_nids))
          $new_view_result[] = $row;
          $used_nids[] = $row->nid;
      $view->result = $new_view_result;

I enabled the module and that did it.

You will see that I am sure that the view name is “event”. You will need to modify this code to match then name of your view. Unfortunately the $view object doesn’t have the display name, otherwise, I would also narrow it down to a specific display. But, since we can’t do that, be sure that you want to apply this code to all displays in the view. Otherwise, create a new view with the display(s) that you want to have modified by this module. The override is also limited to a specific display within the view, “page_2”. You can change this to the ID of whatever display you are trying to override or remove the “if” statement completely to have affect the whole view.


Limit Configuration Field Adversely Affected, 2009-04-21

I’ve discovered that this solution doesn’t play well with the “limit” configuration option in Views.  The limit is applied during the query and this modification happens after the query and so we will always be in a position of reducing the list of nodes by the same amount as multiple dates.

For example, if we want to limit a list of events to four and one of the events has three dates and the others have one, then we will only see two events in the list – one for the three-date event and one for single-date event.

This is a classic “cart before the horse” issue and I don’t see how this can be resolved with my overall approach since the limit is being applied on the SQL and this code is reducing the number of nodes from there.  If you have any suggestions, please share them in the comments.

Pager Adversely Affected, 2009-04-23

Some more fallout in applying this technique – the pager is affected as well.  If you have a pager set to appear for any View that has over 20 items and you have multiple dates applied to events that total more than 20, you will get fewer dates appearing than 20.

Still looking for the right answer.

PHP Errors Fixed, 2009-05-05

On the same day I discovered PHP errors in my logs, “blue muse” posted a comment noting the same.  The good news is that I already had a fix for the problem by the time blue muse made the comment.

I have updated the code for the module (above).  I have removed the $query parameter so now there is only one.  The $query variable was never used in my code, so no need for it anyway.

I also added an array declaration for $used_nids at the top of the function to avoid the error generated when the the loop in it’s first cycle and the variable has not bee set yet.

Added Code To Override Just One Display, 2009-5-20

I discovered that I can in fact only override one display and I have altered the code above accordingly.