theoleschool - views http://theoleschool.com/tags/views en Using hook_views_alter to Add a GROUP BY Statement http://theoleschool.com/blog/using-hookviewsalter-add-group-statement <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><p><em>I should note this post deals with Views 2.x on Drupal 6.x</em></p> <p>When Views doesn't quite spit out the query we are looking for we can turn to <span class="geshifilter"><code class="php geshifilter-php">hook_views_query_alter<span style="color: #009900;">(</span><span style="color: #339933;">&amp;</span><span style="color: #000088;">$view</span><span style="color: #339933;">,</span> <span style="color: #339933;">&amp;</span><span style="color: #000088;">$query</span><span style="color: #009900;">)</span></code></span> to nudge it in the right direction. This is by no means news, and there are many a blog post detailing various ways to alter the query using this function. However, recently, I ran into an odd issue when attempting to add a <span class="geshifilter"><code class="sql geshifilter-sql"><span style="color: #993333; font-weight: bold;">GROUP</span> <span style="color: #993333; font-weight: bold;">BY</span></code></span> statement.</p> <p>The <a href="http://drupal.org/node/385158">issue</a> can be observed when attempting to use the <a href="http://views.doc.logrus.com/classviews__query.html#089b7eab81ec9cb4b24aa489ff4ac448"><span class="geshifilter"><code class="php geshifilter-php">add_groupby</code></span> function</a> to a field in the query. As an example, say we have a node view with a few fields (node id, node title, and user id). This will give a simple query:</p> <div class="geshifilter"><div class="sql geshifilter-sql" style="font-family:monospace;"><span style="color: #993333; font-weight: bold;">SELECT</span> node<span style="color: #66cc66;">.</span>nid <span style="color: #993333; font-weight: bold;">AS</span> nid<span style="color: #66cc66;">,</span> <br />        node<span style="color: #66cc66;">.</span>title <span style="color: #993333; font-weight: bold;">AS</span> node_title<span style="color: #66cc66;">,</span> <br />        users<span style="color: #66cc66;">.</span>uid <span style="color: #993333; font-weight: bold;">AS</span> users_uid <br /><span style="color: #993333; font-weight: bold;">FROM</span> node node  <br /><span style="color: #993333; font-weight: bold;">INNER</span> <span style="color: #993333; font-weight: bold;">JOIN</span> users users <span style="color: #993333; font-weight: bold;">ON</span> node<span style="color: #66cc66;">.</span>uid <span style="color: #66cc66;">=</span> users<span style="color: #66cc66;">.</span>uid</div></div> <p>Now, say we want to do something silly and get this query to group by user id. A node view does not allow for this, so we turn to <span class="geshifilter"><code class="php geshifilter-php">hook_views_query_alter</code></span>.</p> <div class="geshifilter"><div class="drupal6 geshifilter-drupal6" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">function</span> my_module_views_query_alter<span style="color: #66cc66;">(</span><span style="color: #66cc66;">&amp;</span><span style="color: #0000ff;">$view</span>, <span style="color: #66cc66;">&amp;</span><span style="color: #0000ff;">$query</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />   <span style="color: #b1b100;">switch</span><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$view</span>-<span style="color: #66cc66;">&gt;</span><span style="color: #006600;">name</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />     <span style="color: #b1b100;">case</span> <span style="color: #ff0000;">'my_view_name'</span>:<br />       <span style="color: #808080; font-style: italic;">//add the group by  on the user id field </span><br />       <span style="color: #0000ff;">$query</span>-<span style="color: #66cc66;">&gt;</span><span style="color: #006600;">add_groupby</span><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'users_uid'</span><span style="color: #66cc66;">)</span>;<br />       <span style="color: #b1b100;">break</span>;<br />   <span style="color: #66cc66;">}</span><br /><span style="color: #66cc66;">}</span></div></div> <p>We expect to see a nice <span class="geshifilter"><code class="sql geshifilter-sql"><span style="color: #993333; font-weight: bold;">GROUP</span> <span style="color: #993333; font-weight: bold;">BY</span> users_uid</code></span> tacked on to the end of our query, but instead there is a disturbing <span class="geshifilter"><code class="sql geshifilter-sql"><span style="color: #993333; font-weight: bold;">GROUP</span> <span style="color: #993333; font-weight: bold;">BY</span> nid<span style="color: #66cc66;">,</span> node_title<span style="color: #66cc66;">,</span> users_uid</code></span>. What gives? Merlinofchaos <a href="http://drupal.org/node/385158#comment-1296924">clarifies</a> for us:</p> <p><em>"When using GROUP BY items that appear in the SELECT must either use aggregate functions OR appear in the GROUP BY. This is enforced by postgres, therefore Views must enforce this."</em></p> <p>It is good that Views is looking out for the rules of postgres; it's what helps make it the powerful and flexible tool it is. In this application though, I am not worried about postgres and simply want to alter a single Views query on an sql database. I know that I can pass an 'aggregate' flag to the fields in the query object to denote that they use an aggregate function, even if they do not. Worth a try...</p> <div class="geshifilter"><div class="drupal6 geshifilter-drupal6" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">function</span> dsic_judging_views_query_alter<span style="color: #66cc66;">(</span><span style="color: #66cc66;">&amp;</span><span style="color: #0000ff;">$view</span>, <span style="color: #66cc66;">&amp;</span><span style="color: #0000ff;">$query</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />   <span style="color: #b1b100;">switch</span><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$view</span>-<span style="color: #66cc66;">&gt;</span><span style="color: #006600;">name</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />     <span style="color: #b1b100;">case</span> <span style="color: #ff0000;">'dsic_judge_incomplete'</span>:<br />       <span style="color: #808080; font-style: italic;">//loop through the fields and add the aggregate indicator</span><br />       <span style="color: #b1b100;">foreach</span><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$query</span>-<span style="color: #66cc66;">&gt;</span><span style="color: #006600;">fields</span> <span style="color: #b1b100;">as</span> <span style="color: #0000ff;">$key</span> =<span style="color: #66cc66;">&gt;</span> <span style="color: #0000ff;">$field</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />         <span style="color: #0000ff;">$query</span>-<span style="color: #66cc66;">&gt;</span><span style="color: #006600;">fields</span><span style="color: #66cc66;">[</span><span style="color: #0000ff;">$key</span><span style="color: #66cc66;">]</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'aggregate'</span><span style="color: #66cc66;">]</span> = <span style="color: #000000; font-weight: bold;">TRUE</span>;<br />       <span style="color: #66cc66;">}</span><br />       <span style="color: #0000ff;">$query</span>-<span style="color: #66cc66;">&gt;</span><span style="color: #006600;">add_groupby</span><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'users_uid'</span><span style="color: #66cc66;">)</span>;<br />       <span style="color: #b1b100;">break</span>;<br />   <span style="color: #66cc66;">}</span><br /><span style="color: #66cc66;">}</span></div></div> <p>Turns out this works! Jumping back to our view we see the query we are after:</p> <div class="geshifilter"><div class="sql geshifilter-sql" style="font-family:monospace;"><span style="color: #993333; font-weight: bold;">SELECT</span> node<span style="color: #66cc66;">.</span>nid <span style="color: #993333; font-weight: bold;">AS</span> nid<span style="color: #66cc66;">,</span> <br />        node<span style="color: #66cc66;">.</span>title <span style="color: #993333; font-weight: bold;">AS</span> node_title<span style="color: #66cc66;">,</span> <br />        users<span style="color: #66cc66;">.</span>uid <span style="color: #993333; font-weight: bold;">AS</span> users_uid <br /><span style="color: #993333; font-weight: bold;">FROM</span> node node  <br /><span style="color: #993333; font-weight: bold;">INNER</span> <span style="color: #993333; font-weight: bold;">JOIN</span> users users <span style="color: #993333; font-weight: bold;">ON</span> node<span style="color: #66cc66;">.</span>uid <span style="color: #66cc66;">=</span> users<span style="color: #66cc66;">.</span>uid<br /><span style="color: #993333; font-weight: bold;">GROUP</span> <span style="color: #993333; font-weight: bold;">BY</span> users_uid</div></div> <p>If it looks like a hack, and quacks like a hack, then it's probably a... well ya know. However, I don't have big qualms with implementing this. Views behavior is in place to accommodate a database that, in this instance, is not being used. Further, I am already using a hook function to modify a view beyond what that view is capable of spitting out on its own. Lastly, I am able to accomplish this without modifying Views core. All of that adds up to a valid use case and helps me to sleep at night.</p></div></div></div> Mon, 12 Mar 2012 15:09:21 +0000 rho 159 at http://theoleschool.com Viewing Users Who Have Not Submitted a Webform http://theoleschool.com/blog/viewing-users-who-have-not-submitted-webform <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><p>On a recent Drupal 6 site, I received a request to build a view displaying a list of users who had not submitted a specific webform. <a href="http://drupal.org/project/webform&quot;">Webform</a> of course, has some very nice reporting as well as decent views integration for submissions. However, to view users that have yet to make a submission takes some creative views argument handling.</p> <p>I began by building a basic user view. I then set the style and fields to my liking, and filtered on the specific role I was looking for. So now we have views pulling a list of users that we wish to check against. To find out who has not submitted the webform, we first have to take a look at who has.</p> <div class="geshifilter"><div class="drupal6 geshifilter-drupal6" style="font-family:monospace;">webform_get_submissions<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$filters</span> = <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span>, <span style="color: #0000ff;">$header</span> = <span style="color: #000000; font-weight: bold;">NULL</span>, <span style="color: #0000ff;">$pager_count</span> = <span style="color: #cc66cc;">0</span><span style="color: #66cc66;">)</span>;</div></div> <p>Webform has the above function in the <span class="geshifilter"><code class="php geshifilter-php">webform<span style="color: #339933;">.</span>submission<span style="color: #339933;">.</span>inc</code></span> file that does just this. You can pass a node id to <span class="geshifilter"><code class="php geshifilter-php"><span style="color: #000088;">$filters</span></code></span> and the function will return a nice array of submission objects, detailing who has submitted the form. We can then add an argument of type <em>"User: UID"</em> to our view, select <em>"Provide default argument"</em> below "Action to take if argument is not present," and select <em>"PHP Code"</em> under "Default argument type." Here we can provide a code snippet to return a list of User ids of users that have submitted the form. </p><div class="geshifilter"><div class="drupal6 geshifilter-drupal6" style="font-family:monospace;"><a href="http://api.drupal.org/api/function/module_load_include/6"><span style="color: #000066;">module_load_include</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'inc'</span>, <span style="color: #ff0000;">'webform'</span>, <span style="color: #ff0000;">'includes/webform.submissions'</span><span style="color: #66cc66;">)</span>;<br /><span style="color: #0000ff;">$submission_array</span> = webform_get_submissions<span style="color: #66cc66;">(</span><span style="color: #808080; font-style: italic;">/*node id of webform we are checking for*/</span><span style="color: #66cc66;">)</span>;<br /><span style="color: #0000ff;">$uids</span> = <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span>;<br /><span style="color: #b1b100;">foreach</span><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$submission_array</span> <span style="color: #b1b100;">as</span> <span style="color: #0000ff;">$submission</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />   <span style="color: #0000ff;">$uids</span><span style="color: #66cc66;">[</span><span style="color: #66cc66;">]</span> = <span style="color: #0000ff;">$submission</span>-<span style="color: #66cc66;">&gt;</span><span style="color: #006600;">uid</span>;<br /><span style="color: #66cc66;">}</span><br /><span style="color: #b1b100;">return</span> <a href="http://www.php.net/implode"><span style="color: #000066;">implode</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">','</span>, <span style="color: #0000ff;">$uids</span><span style="color: #66cc66;">)</span>;</div></div> <p>This will gives us a comma separated string of user ids. The default "Validator options" are fine but <strong>be sure</strong> to check <em>"Allow multiple terms per argument"</em> and <em>"Exclude the argument."</em> After updating, your view now filters down to users that have not submitted the webform! To double check that this is happening, you can look at the query below the preview. Near the end of the query in the <span class="geshifilter"><code class="sql geshifilter-sql"><span style="color: #993333; font-weight: bold;">WHERE</span></code></span> clause, you should see something like <span class="geshifilter"><code class="sql geshifilter-sql">users<span style="color: #66cc66;">.</span>uid <span style="color: #993333; font-weight: bold;">NOT</span> <span style="color: #993333; font-weight: bold;">IN</span> <span style="color: #66cc66;">(</span><span style="color: #cc66cc;">110</span><span style="color: #66cc66;">,</span> <span style="color: #cc66cc;">13028</span><span style="color: #66cc66;">,</span> <span style="color: #cc66cc;">210</span><span style="color: #66cc66;">,</span> <span style="color: #cc66cc;">107</span><span style="color: #66cc66;">,</span> <span style="color: #cc66cc;">35329</span><span style="color: #66cc66;">,</span> <span style="color: #cc66cc;">35330</span><span style="color: #66cc66;">,</span> <span style="color: #cc66cc;">35331</span><span style="color: #66cc66;">)</span></code></span>. The list of numbers are the user ids that are being filtered. If no one has submitted the form, then this of course will be empty.</p> <p>Now I admit, this is kinda gross. I shudder any time I include php in configuration. It is, however, a very straight forward way to get the results requested while staying in views, therefore leaving the output in the hands of a site builder/administrator. There may be some clever ways to achieve the same thing using something like <a href="http://drupal.org/project/webform_report">Webform Report</a>, or even a different views configuration, but nothing jumped out at me. However, if you have solved this in a different way I would love to hear about it.</p></div></div></div> Sun, 04 Mar 2012 18:25:05 +0000 rho 158 at http://theoleschool.com