I've made an additional field in the profile.module to enter and validate numbers. The profile.module stores its information using an EAV data model - therefore my numbers are being stored as text in the database. To make the numbers sort correctly they will need to be converted from text into numbers in the SQL statement...

I've discovered that MySQL 4.1 doesn't support CAST very well (there is no CAST AS DECIMAL), but you can trick MySQL into the conversion by adding 0 to the text value and MySQL will convert on the fly:

This statement will sort the numbers stored as text correctly:
SELECT value FROM {profile_values} WHERE fid = %d ORDER BY (value + 0) DESC

as opposed to:
SELECT value FROM {profile_values} WHERE fid = %d ORDER BY value DESC

So, I'm stuck with the specific question:
How do you write a views handler that changes 'value' to '(value +0)' ?

I've got this far:

function views_handler_filter_profile_number($op, $filter, $filterinfo, &$query) {

  switch ($op) {
    case 'handler':
	  $table = 'profile_value';
	  $column = 'value';
	  $query->ensure_table($table);
	  $field = "$table.$column";

	  $operator = $filter['operator'];
      $where = "(%s + 0) $operator %s";
      $query->add_where("$where", $field, $filter['value']);
	  $query->add_groupby('profile_value.value');
	  $filter->field = $field;
	
	  return $filter;
      break;
	}
}

But, for some reason the profile name is turning up in the SQL statement:

user warning: Unknown table 'profile_value' in where clause query: SELECT node.nid, profile_producer_total.value AS profile_producer_total_value, node.title AS node_title, node.changed AS node_changed, profile_producer_name.value AS profile_producer_name_value, profile_donated.value AS profile_donated_value FROM node node LEFT JOIN users users ON node.uid = users.uid LEFT JOIN profile_values profile_producer_total ON users.uid = profile_producer_total.uid AND profile_producer_total.fid = '4' LEFT JOIN profile_values profile_producer_name ON users.uid = profile_producer_name.uid AND profile_producer_name.fid = '2' LEFT JOIN profile_values profile_donated ON users.uid = profile_donated.uid AND profile_donated.fid = '3' WHERE (node.status = '1') AND (node.type IN ('usernode')) AND ((profile_value.value + 0) >= 10) AND ((profile_value.value + 0) < 100) GROUP BY profile_value.value, profile_value.value ORDER BY profile_producer_total_value DESC LIMIT 0, 50

I cannot figure out how to set the 2nd table to be the same as the first:
LEFT JOIN profile_values profile_values

My guess is that the routine that cycles through all the profile fields is getting messed up by my custom filter?

Please HELP! I've spent two days on this and I'm so close to adding a sweet feature to the profile.module.

Thanks so much for your help - I'm good for a beer (or two) for the solution.

Cheers, Jason

Comments

merlinofchaos’s picture

Well to start with, you've got the wrong kind of handler, which puts you in totally the wrong ballpark. You want a sort handler, not a filter handler.

First, I'm going to point you to the shiny new documentation (that I haven't made public but need to soon; was hoping more would get done on it but it's better unfinished and available than it is unfinished and unavailable) at http://drupal.org/handbook/modules/views/api

In the sorts section you'll find the info about the sort handler. DOn't worry about the $op, nothing really uses it.

Your handler should work very similar to how the random sort handler works; I was going to give some code, but actually copying that would be better. Also note that Views has been seeing some changes to $query->add_orderby in 1.2 and they'll affect what you're doing because you're using a formula. Be sure to use the latest.

jpsalter’s picture

merlinofchaos - thanks for the tip and links to the new documentation.

I didn't explain myself too well. I'll need to do two things with my new number profile field:

1) filter in views by greater than / less than
2) sort in views by number value

Both of these goals get tripped up by the fact the profile.module is storing the number as text in the database. I've solved #1 with the following code:

// this is added to the filter
      $others = array(
        'list-type' => 'list',
        'operator' => 'views_handler_operator_gtlt',
        'handler' => 'views_handler_filter_profile_number',
      );

// this is the handler
function views_handler_filter_profile_number($op, $filter, $filterinfo, &$query) {
  switch (trim($filter['value'])) {
    case (''):
      return;
      break;
  }
  switch ($op) {
    case 'handler':
      $table = $filterinfo['table'];
      $column = $filterinfo['field'];
      $field = "$table.$column";
      $query->ensure_table($table);

          $query->add_where("(%s + 0) {$filter['operator']} %d",
            $field, $filter['value']);
    break;
  }
}

The sort code is still giving me problems. I've read the documentation but I must be missing this point. Here is my sort function:

// this is added to the filter
      $others = array(
        'sortable' => true,
		'sort_handler' => 'views_handler_sort_profile_number',
		);

// this is the sort handler 
function views_handler_sort_profile_number($op, &$query, $sortinfo, $sort) {

  $table = $sortinfo['table'];
  $field = "(value + 0)";

  $query->add_field($field, $table);
  $query->orderby[] = "$field $sort[sortorder]";
}

Any pointer or suggestions would be greatly appreciated.

Thanks,

Jason

merlinofchaos’s picture

Er, 'value' in your code needs to be a more concrete database table + field name.

To start with, you want something in the form of:

      $query->add_orderby('', "rand()", "ASC", 'random_sort');

That's a much cleaner way to add your orderby. Of course, you then need to replace rand() with basically $table.$field.

jpsalter’s picture

Thank you for putting me on the right track. I think I'm close, but the views sorting still doesn't work for me. Here are the settings I have:

For the array:

    [profile_producer_total] => Array
    (
        [name] => profile_values
        [provider] => internal
        [join] => Array
        (
            [left] => Array
            (
                [table] => users
                [field] => uid
            )
            [right] => Array
            (
                [field] => uid
            )
            [extra] => Array
            (
                [fid] => 4
            )
        )
        [fields] => Array
        (
            [value] => Array
            (
                [name] => Profile: Received money
                [help] => Number based profile field help
                [sortable] => 1
            )
        )
        [sorts] => Array
        (
            [value] => Array
            (
                [name] => Profile: Received money
                [help] => Number based profile field help
                [sort_handler] => views_handler_sort_profile_number
            )
        )
        [filters] => Array
        (
            [value] => Array
            (
                [name] => Profile: Received money
                [help] => Number based profile field help
                [list-type] => list
                [operator] => views_handler_operator_gtlt
                [handler] => views_handler_filter_profile_number
            )
        )
    )

and for the functions:

/**
 * Custom filter handler for converting numbers in profile_value.value 
 * (stored as text) to a number 
 */
function views_handler_filter_profile_number($op, $filter, $filterinfo, &$query) {
  switch (trim($filter['value'])) {
    case (''):
      return;
      break;
  }
  switch ($op) {
    case 'handler':
      $table = $filterinfo['table'];
      $column = $filterinfo['field'];
      $field = "$table.$column";
      $query->ensure_table($table);

          $query->add_where("(%s + 0) {$filter['operator']} %d",
            $field, $filter['value']);
    break;
  }
}

/**
 * Custom sort handler for converting numbers in profile_value.value 
 * (stored as text) to a number 
 */
function views_handler_sort_profile_number($op, &$query, $sortinfo, $sort) {
  $field = "(profile_values.value + 0)";
  $query->add_orderby(NULL,$field, $sort['sortorder']);
}

Thanks again for all the help. If this works - this could be used as a helper function for those who need to treat a textfield as a number.

catch’s picture

Status: Active » Closed (won't fix)

4.7 no longer supported for feature requests.

memtkmcc’s picture

There is VERY easy workaround for this issue.

You can convert on the fly any value as with CAST AS DECIMAL, using Views Calc module.

1. Create new Views Calc field, for example: ROUND(%Profile:PostCount), where original Profile:PostCount is a plain text value, and choose the format of the result of this calculation to be integer/decimal.

2. Now add new calculated field to your view, but keep there also original Profile:PostCount field.

3. Use any template trick to hide the original Profile:PostCount, so it will be still availabe to "calculation", but not displayed as redundant data.

Thats all. Tested and works for me.

zapscribbles’s picture

I am using Drupal 6 and tried emaylx's solution. Unfortunately it did not work as it requires the Usernode module (http://drupal.org/project/usernode) to make the Views Calc field available to a User View (by default it is only exposed to a Node View), but currently there is no version of Usernode for Drupal 6.

My only solution to get around this was to create a View listing all posts by user, and requiring the user name as an argument. I set it so that when the argument is missing, a summary is displayed. This resulted in a list of user's with numbers next to them indicating how many posts belong to them.

The only issue with this is that it is ordered by user name, not by the summary number (i.e. the total posts for each user). Does anyone know of a way to do this in the View?

hozt’s picture

I faced this same problem and was able to solve it first by padding the number with leading zeros with lpad in mysql. This way the sort would work correctly. Next I added the module view_custom. This allows you to use php to format the output of the field. like <?=intval($data->points)?>

In the settings for the custom field check the box to prevent it from showing up. Then add your text field that has the number you want after the custom field and use the rewrite field option to display the value from your custom field. This enabled the sort and display to work perfect in Drupal 6.

Hope this can help!

barber75’s picture

Hey,

Trying to implement what you did...I can get the value to display in my view, but when using my filter it still treats the fields as text and not an integer or number.

Did you get this working with filters? Your're help would be much appreciated, really panicking on this now, might have to completely rebuild a lot of my site, which is going live in like a day!

cheers
Craig