Forums / Developer / [solved] Function definition, Select Distinct

[solved] Function definition, Select Distinct

Author Message

Nathan Kelly

Thursday 22 February 2007 8:18:55 pm

Hi all,

Is there any way to use a "SELECT DISTINCT" query using the following function definition?

function &definition()  {
    return array( 'fields' => array(
            'myObjectID' => array(
                'name' => 'myObjectID',
                'datatype' => 'integer',
                'default' => 0,
                'required' => true
            ),		   
        ),
        'keys' => array( 'myObjectID' ),
        'function_attributes' => array(),
        'class_name' => 'myclass',
        'name' => 'myTable'
    );
}

Using this I get the following query:

SELECT myObjectID
FROM myTable

What I need is:

SELECT DISTINCT myObjectID
FROM myTable

But I'm not sure how to do this with the function definition? Or if it can even be done?

I have tried writing my query directly in the function definition but it returns a blank page, can anyone tell me what I'm doing wrong?

function &definition() {
	$return = array();
	$query = "SELECT DISTINCT myObjectID FROM myTable";
	$db =& eZDB::instance();
	$result =& $db->arrayQuery($query);
	$return =& myclass::handleRows( $result,'myclass',true );
	return $return;
}

if I change this line $result =& $db->arrayQuery($query); to $result =& $db->query($query); I get an error but the mySQL query looks to be correct. I get the following in the debug output:

Notice: eZMySQLDB::query(24 rows, 1.219 ms) query number per page:3  	Feb 23 2007 15:23:17

SELECT DISTINCT myObjectID FROM myTable

Warning: PHP 	Feb 23 2007 15:23:17

array_keys() [<a href='function.array-keys'>function.array-keys</a>]: The first argument should be an array in /var/www/html/kernel/classes/ezpersistentobject.php on line 689

Warning: PHP 	Feb 23 2007 15:23:17

Invalid argument supplied for foreach() in /var/www/html/kernel/classes/ezpersistentobject.php on line 706

Notice: eZMySQLDB::query(-1 rows, 0.187 ms) query number per page:4 	Feb 23 2007 15:23:17

SELECT 
FROM

Error: eZMySQLDB 	Feb 23 2007 15:23:17

Query error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'FROM' at line 2. Query: SELECT 
FROM

Can anyone help me with this?

Cheers.

Pardon me while I burst into flames...

Bruce Morrison

Thursday 22 February 2007 10:49:01 pm

Hiya Nathan

According to the &definition(), 'myObjectID' should be dictinct/unique as it is defined as the key. If it's not you may run into issues with using the eZPersistantObject class with this data.

I suspect you can do something like this (untested) to get the query you want

$custom = array( array( 'operation' => 'DISTINCT myObjectID',
                             'name' => 'myObjectID' ) );

$rows =& myclass::fetchObjectList( myclass::definition(), array(), null, null, null, false, false, $custom);

See http://pubsvn.ez.no/doxygen/classeZPersistentObject.html#3c3bffe097597c30da48c2f10039f470
for more info on this. The above is based on the "Counting number of elements." example

As for the 2nd method the &definition() has to return array as in the first example.

The code you have in the function will work is called something else.

HTH

Cheers
Bruce

My Blog: http://www.stuffandcontent.com/
Follow me on twitter: http://twitter.com/brucemorrison
Consolidated eZ Publish Feed : http://friendfeed.com/rooms/ez-publish

Nathan Kelly

Thursday 22 February 2007 11:11:14 pm

Cool thanks Bruce, this is the reference I needed, as it turns out my fetch just got a whole lot more complicated so the link is much appreciated.

My query now looks more like this:

SELECT DISTINCT tableOne.* 
FROM tableOne, tableTwo 
WHERE tableOne.myObjectID 
IN (tableTwo.myObjectID) 
ORDER BY myObjectName ASC

<i>(This is to avoid doing 2 queries and then handling the data in the template).</i>
I'll have a play around with some of the examples on pubsvn and see what I can come up with.

Cheers.

Pardon me while I burst into flames...

Nathan Kelly

Friday 23 February 2007 12:23:32 am

Ouch, now my head really hurts...

Bruce, you said that my second example would work if it had a different name, can I assume you mean if I call it &getMyObject() I could then call on this function with something like:

$myclass =& myclass::fetchObject(myclass::getMyObject(), <conditions> );

or

$myclass =& myclass::fetchObjectList(myclass::getMyObject(), <conditions> );

If thats the case I think it might be the easier option, but I'm not sure what <conditions> I need to specify. My new function looks like this:

  function &getMyObject()
  {
    $return = array();
    $query = "SELECT DISTINCT tableOne.* 
    	      FROM tableOne, tableTwo 
	      WHERE tableOne.myObjectID 
	      IN (tableTwo.myObjectID) 
	      ORDER BY myObjectName ASC ";
    $db =& eZDB::instance();
    $result =& $db->arrayQuery($query);
    $return =& myclass::handleRows( $result,'myclass',true );
    return $return;
  }

So if I want to call this from a module what do I put in the <conditions>? I currently have:

$myclass =& myclass::fetchObjectList(myclass::getMyObject(), null, array(), null, array(), true, false, null );

but this returns the same warnings (array_keys and invalid arg foreach) as above. I have tried different arguments but I still don't get the result I want...

Any ideas welcome.

Cheers.

Pardon me while I burst into flames...

Claudia Kosny

Friday 23 February 2007 2:45:17 pm

Hi Nathan

To me it looks like you misunderstood the ezpersistantobject class a bit, so I try to explain. Please note that this refers to your first post, as I am not exactly sure what you want to achive with the two tables in your last post.
Your definition:

function &definition() {
   return array( 'fields' => array(
           'myObjectID' => array(
               'name' => 'myObjectID',
               'datatype' => 'integer',
               'default' => 0,
               'required' => true
           ),             
       ),
       'keys' => array( 'myObjectID' ),
       'function_attributes' => array(),
       'class_name' => 'myclass',
       'name' => 'myTable'
   );
}

The entry 'fields' describes the structure of the table in your database. You have a column that is named myObjectID, is of type integer and is required (which afaik means that eZ will put the default there inan insert query if you don't specify anything). By the way the key 'myObjectID' refers to the name of the column, the entry 'name' => 'myObjectID' refers to the name of the object member variable. They are not required to be the same.
The entry 'keys' means that myObjectID is the identifier for each row, which means that it must be unique. Therefore Bruce's comment that this column must be distinct/unique as otherwise you might get unexpected problems with the database interface. And if it is unique, then you don't need to make a 'DISTINCT' query on this column. So you should rethink your class/table layout.

For completeness's sake:
The entry 'function_attributes' is not necessary if you don't need it. Generally this is meant for having additional object member variables (which will be the keys in the array) whose values does not come from the table row but are computed with a function (whose names are used as values in the array)
The entry 'class_name' is the name of the class that is instantiated with the values from the fetched table row.
The entry 'name' is the name of the table in the database.

Ok, now to your query.

First of all: http://issues.ez.no/8448 states that select distinct is not supported yet. Depending on your needs the following might still be interesting:

As said before making a distinct query on your unique key does not make much sense. There let's assume that your table has an additional row, lets call it 'testRow' which you want to fetch using distinct. So your definition would look like this:

function &definition() {
   return array( 'fields' => array(
           'myObjectID' => array(
               'name' => 'myObjectID',
               'datatype' => 'integer',
               'default' => 0,
               'required' => true
           ),
           'testRow' => array(
               'name' => 'testRow',
               'datatype' => 'integer',
               'default' => '',
               'required' => false
           ),                        
       ),
       'keys' => array( 'myObjectID' ),
       'class_name' => 'myclass',
       'name' => 'myTable'
   );
}

Now you want to make a distinct query on testRow. Bruce's approach should work fine (although I haven't tested it either, so please report the results):

function fetchDistinctTestRow()
{
$fieldsToFetch = array(); //no other fields to fetch except testRow whis is specified later on
$conditions = null; //we don't have any WHERE conditions
$sortingInfo = null; //no sort on the result
$limitInfo = null; //no limit on result
$asObject = true; //if true it will return an array of objects of class myclass, else an array of arrays
$customFields = array( array( 'operation' => 'DISTINCT testRow',
'name' => 'testRow' ) );


return myclass::fetchObjectList(myclass:definition(),
$fieldsToFetch,
$conditions,
$sortingInfo,
$limitInfo,
$asObject,
$customFields);

}

The problem is that this fetches only the colum testRow, not the entire row. This means that any instantiated objects hold only the default values in the member variables except for testRow which is the current value.

The only way I see to fetch the entire row is to group by all fields which is a bit cumbersome, but should work at least in MySQL. Maybe I'll try it over the weekend, now it definitely to late...

Claudia

Nathan Kelly

Friday 23 February 2007 4:11:57 pm

Thanks for the detailed breakdown Claudia, I realized late last night that I didn't really explain my first example very well.

What I should have used as the example is more like the following:

SELECT DISTINCT myObjectID
FROM myTable

Where myObjectID's in myTable are not unique values. Basically there are approximately 420 rows in my table with only 24 out of a possible 200 unique myObjectID's in use. So my table looks something like:

myObjectID	myObjectName

12		whatever
12		another name
23		name on an object
198		example
12		test name

and so on...

What I needed to do was fetch only unique myObjectID's, so that the query should only return 24 results rather than 420. But this is not what I need to do now so this example is no longer valid.

Now however, as in my last example things have become more complex, in tableOne there are 200 rows and 2 columns, I need to collect myObjectID and myObjectName but only where myObjectID exists in tableTwo which has 420 rows. This should return the desired 24 unique results and ignore the other 396 redundant rows.

SELECT DISTINCT tableOne.*
FROM tableOne, tableTwo
WHERE tableOne.myObjectID
IN (tableTwo.myObjectID)
ORDER BY myObjectName ASC

It took me all of 1 minute to write my query and get the expected result, and in a normal PHP app I'd have no trouble doing so, but translating this into something that the ezpersistantobject can work with is proving very difficult. It honestly doesn't make sense to me why creating a simple query in ezpublish should be such a convoluted task, why can't I just write my query and get the expected result?

I really appreciate the assistance, I would like to become more familiar with the core functionality of ez but it's going to take time.

Cheers.

Pardon me while I burst into flames...

Bruce Morrison

Friday 23 February 2007 4:36:21 pm

Hi Nathan

Part of the trouble is that we are lacking context. From what your've described I'm not sure you need to use ezpersistantobject. ezpersistantobject is a object persistance library that does provide great savings if it's used with data that suits. I'm not sure your case does.

If all you want to do is extract from information from the database and say display in a template you can simply do something like the following:

// Grab some data from the DB
$query = "SELECT DISTINCT tableOne.*
FROM tableOne, tableTwo
WHERE tableOne.myObjectID
IN (tableTwo.myObjectID)
ORDER BY myObjectName ASC";
$db =& eZDB::instance();
$result =& $db->arrayQuery($query);

//Make data availiable to template
$tpl =& templateInit();
$tpl->setVariable( 'result', $result );
$Result['content'] = $tpl->fetch("design:path/to/template.tpl");

This code would suite a module view. It sets a temaplte variable $result that would contain an array with an array per row returned from the query. Each row is an array with the column names as keys. Use {$result|attribute(show,3)} in the template to have a look.

If this is still not what you are looking for I think you need to describe what you are tring to do so we have some context to work with.

Cheers
Bruce

My Blog: http://www.stuffandcontent.com/
Follow me on twitter: http://twitter.com/brucemorrison
Consolidated eZ Publish Feed : http://friendfeed.com/rooms/ez-publish

Nathan Kelly

Friday 23 February 2007 4:46:23 pm

Hi Bruce,

I think that is exactly what I need to do... As I'm not familiar with creating my own extensions it's quite difficult to know what I need to use and where, I'm really stumbling around in the dark because the only real reference I have is usually contained in other extensions or in mediocre or partial documentation, so knowing what to look for and how to explain my requirements can be difficult.

As I said I want to become more familiar with the core functionality of ezpublish, mainly because it's practically the only CMS I work with now, and I'm going to be using it for a long time to come.

Thanks for the help Bruce and Claudia, I'll try the method Bruce suggested and see how I go.

Cheers.

Pardon me while I burst into flames...

Nathan Kelly

Friday 23 February 2007 5:00:30 pm

Yep that did it.

Sorry for the confusion folks...

Cheers.

Pardon me while I burst into flames...

kracker (the)

Friday 23 February 2007 8:27:09 pm

Nathan,

Congratulations on successfully finding and implementing a solution to which meets your own unique needs.

If I may, I would recommend investing further in the eZ publish community by packaging the files related to your custom extension (solution) as posting it as an example solution for others to more quickly use to create their own similar solution based on the answers you all worked so hard to find and develop.

This allows more solutions of this kind to more quickly be developed by all in the future.
The contribution need not be much more than a copy of the extension you used to solve your problem as a reference for someone to customize on their own to meet their own unique needs.

I just know this would be very useful to others to have a single downloadable example solution which others can take to the next level in their own way.

Respectfully,
//kracker

<i>Home Movies - Duanetastic</i>

Member since: 2001.07.13 || http://ezpedia.se7enx.com/

Nathan Kelly

Friday 23 February 2007 9:40:39 pm

Hi Kracker,

I can appreciate what you are suggesting and under normal circumstances I would post up a package but this problem I faced was only a tiny part of a very large extension, on its own it really has no relevance as packaged solution, and as a lesson there really is nothing else in the extension that isn't in most of the extensions already available.

Also this solution was for one very specific query to the database, for the best part the rest of the extension interacts with the ezPersistantObject without issue, but because my custom query was quite a complex one I found myself stuck for a solution, and due to my lack of understanding of how extensions interact with ezpublish I was not aware that I could simply create a query that could be inserted directly into the template variable as Bruce pointed out.

By the time I finally finished the query the complexity had grown from a simple query to the following:

$result = "SELECT DISTINCT tableOne.*, count(tableTwo.myObjectID) 
FROM tableOne, tableTwo 
WHERE tableOne.myObjectID 
IN (tableTwo.myObjectID) 
AND tableTwo.myCondition = 2 
GROUP BY tableOne.myObjectID 
ORDER BY myObjectName ASC";
$db =& eZDB::instance();
$template_var =& $db->arrayQuery($result);

The final product works as I wanted it too, it collects and counts information depending on whether it is in use or not and returns only the objects that are in use, while returning a total count of myObjectID groups and their children.

One thing that stumped me however was how to use the count key which is returned as count(tableTwo.myObjectID). After some serious guess work I got the count value with the following:

{foreach $template_var as $var}
{$var['count(tableTwo.myObjectID)']}
{/foreach}

Seems odd but works fine...

The real issue here was my lack of understanding of when I need to use the ezPersistantObject and when I can do without it. The only thing I can suggest that might help others avoid the problem I faced is try to familiarize yourself with the core functionality, that's what i am going to start doing from now on, it can be frustrating when everything works and suddenly you hit a wall where your own understanding lets you down.

In future if I am doing something smaller and i think it could help other I'll definitely post up a solution.

Cheers.

Pardon me while I burst into flames...