<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>HirdWeb &#187; custom queries</title>
	<atom:link href="http://www.hirdweb.com/tag/custom-queries/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.hirdweb.com</link>
	<description>Another Blog clogging up the already crowded internet</description>
	<lastBuildDate>Wed, 18 Jan 2012 20:54:49 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3</generator>
		<item>
		<title>Count the Number of Cakes &#8211; Finding complex results with CakePHP</title>
		<link>http://www.hirdweb.com/2011/05/10/count-the-number-of-cakes-finding-complex-results-with-cakephp/</link>
		<comments>http://www.hirdweb.com/2011/05/10/count-the-number-of-cakes-finding-complex-results-with-cakephp/#comments</comments>
		<pubDate>Wed, 11 May 2011 03:23:27 +0000</pubDate>
		<dc:creator>stephen</dc:creator>
				<category><![CDATA[Applications]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[cakePHP]]></category>
		<category><![CDATA[custom queries]]></category>

		<guid isPermaLink="false">http://www.hirdweb.com/?p=663</guid>
		<description><![CDATA[CakePHP offers a good selection of tools to help you retrieve the data. Recently, I came into a situation where I needed to find and paginate results based on a single, distinct column in the table. Distinct data can be tricky, especially if the tools do not allow you to select the distinct based on [...]]]></description>
			<content:encoded><![CDATA[<p>CakePHP offers a good selection of tools to help you retrieve the data. Recently, I came into a situation where I needed to find and paginate results based on a single, distinct column in the table. Distinct data can be tricky, especially if the tools do not allow you to select the distinct based on a column. Distinct will check all columns returned, and coupling in time stamps, 99% of the time all rows will be distinct. So how do you grab the data? Well, first lets examine the sample data that is needed to be extracted first. </p>
<p>The sample data is in a MS SQL Server database. The table contains a record ID, title id, author id, genre, type, last check out date, and edit date. It is possible to have duplicate title, author IDs in the table. We need to extract all DISTINCT title IDs, along with the other information listed where the type is not a paperback, and provide a paginated list. I am sure this would be better architected if needed in the real world. Paginate will only get us so far, as this would only show all records. </p>
<pre>class BooksController extends AppController {
    var $paginate = array(
        'order'        => array('Book.id' => 'desc'),
        'fields'    => array('Book.id', 'Book.title_id', 'Book.author_id', 'Book.genre_id', 'Book.type', 'Book.check_date', 'Book.edit_date'),
        'limit'        => 15,
    );
    . . .
    function index(){
        $this->set('hardback_books', $this->paginate());
    }
}</pre>
<p>We need to use more to build a conditional query so the paginate will query against this. We can use CakePHP&#8217;s data source to help in this. Now, we could also just write this query out ourselves, but this is helpful to know so when you have to build sub-queries for other items. All data is in MS SQL Server, and we can use normal SQL expressions, but we need to grab DISTINCT data, which goes by rows, not columns, which means we will need to do 2 sub-queries in addition to the main one. So we first need to grab a list of the TOP 1 items. This will be our inner query. </p>
<pre>        SELECT TOP 1 *
        FROM [books] AS [bk_inner]
        WHERE
            [bk_inner].[title_id] = [Book].[title_id]
            AND
            [bk_inner].[type] <> 'paperback' </pre>
<p>Next, we need to encapsulate that query with an outer one that will select all items which match up to the main query ID. </p>
<pre>    SELECT * FROM
    (
        SELECT TOP 1 *
        FROM [books] AS [bk_inner]
        WHERE
            [bk_inner].[title_id] = [Book].[title_id]
            AND
            [bk_inner].[type] <> 'paperback'
    ) AS [bk_outer]
    WHERE bk_outer.[title_id] = Book.[title_id] </pre>
<p>So we have the queries, and it needs the main query needs to constrain the results that exists in the sub-queries. </p>
<pre>SELECT TOP 15
    [Book].[id],
    [Book].[title_id],
    [Book].[author_id],
    [Book].[genre_id],
    [Book].[type],
    CONVERT(VARCHAR(20), [Book].[check_date], 20)
    CONVERT(VARCHAR(20), [Book].[edit_date], 20)
FROM [books] AS [Book]
WHERE EXISTS
(
    SELECT * FROM
    (
        SELECT TOP 1 *
        FROM [books] AS [bk_inner]
        WHERE
            [bk_inner].[title_id] = [Book].[title_id]
            AND
            [bk_inner].[type] <> 'paperback'
    ) AS [bk_outer]
    WHERE bk_outer.[title_id] = Book.[title_id]
)
ORDER BY [Book].[id] desc</pre>
<p>We have the final full query. Now how do we get that? First, we need to invoke the getDataSource() method. </p>
<pre>class Book extends AppModel {
    . . .
    function getHardbackBooks(){
        $dbo = $this->getDataSource();
</pre>
<p>Next we need to use the buildStatement() to build each statement. Since CakePHP will build a sub query with this, we have to do this twice: once for the inner query, and once for the outer query. The &#8220;table&#8221; for subquery2 will actually be subquery1, so we need to add that as a &#8220;table&#8221; in the array. </p>
<pre>$subquery1 = $dbo->buildStatement(
	array(
		'fields' => array('TOP 1 *'),
        'table' => $dbo->fullTableName($this),
        'alias' => 'bk_inner',
        'limit' => null,
        'offset' => null,
        'joins' => array(),
        'conditions' => 'bk_inner.title_id = Book.title_id AND bk_inner.type <> \'paperback\'',
        'order' => null,
        'group' => null

	),
	$this
);

$subQuery2 = $dbo->buildStatement(
    array(
        'fields' => array('*'),
        'table' => '(' . $subquery1 . ')',
        'alias' => 'bk_outer',
        'limit' => null,
        'offset' => null,
        'joins' => array(),
        'conditions' => 'bk_outer.[title_id] = Book.[title_id]',
        'order' => null,
        'group' => null
    ),
    $this
);</pre>
<p>Now, we need to make sure we add an EXISTS:</p>
<pre>$subQuery = ' EXISTS (' . $subQuery2 . ') ';
return $subQuery;</pre>
<p>Return the data from the model to the controller. In the controller function we need to add a new condition to the paginate. In the conditions, we do not need to use a paired item value to set it, we can use the straight SQL returned from the model. </p>
<pre>class BooksController extends AppController {
    var $paginate = array(
        'order'        => array('Book.id' => 'desc'),
        'fields'    => array('Book.id', 'Book.title_id', 'Book.author_id', 'Book.genre_id', 'Book.type', 'Book.check_date', 'Book.edit_date'),
        'limit'        => 15,
    );
    . . .
    function index(){
        <b>$data = $this->Book->getHardbackBooks();
        // Set to the paginate object conditions
        $this->paginate['conditions'] = array($data);</b>
        $this->set('hardback_books', $this->paginate());
    }
}</pre>
<p>And it returns the items based on the paginate parameters, ready to use in the view. It provides a DISTINCT list. And yes, I know I used more than 400 words in this one. It was closer to 500 without the code. Oh well, maybe tomorrow will be shorter. </p>
]]></content:encoded>
			<wfw:commentRss>http://www.hirdweb.com/2011/05/10/count-the-number-of-cakes-finding-complex-results-with-cakephp/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Custom Pagination in CakePHP</title>
		<link>http://www.hirdweb.com/2008/08/04/custom-pagination-in-cakephp/</link>
		<comments>http://www.hirdweb.com/2008/08/04/custom-pagination-in-cakephp/#comments</comments>
		<pubDate>Mon, 04 Aug 2008 14:43:08 +0000</pubDate>
		<dc:creator>stephen</dc:creator>
				<category><![CDATA[Applications]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[cakePHP]]></category>
		<category><![CDATA[custom queries]]></category>
		<category><![CDATA[pagination]]></category>

		<guid isPermaLink="false">http://www.hirdweb.com/?p=10</guid>
		<description><![CDATA[To continue on last weeks thought of a &#8220;lite&#8221; forum, I needed 2 tables (Forums, Posts). Since this is a &#8220;lite&#8221; forum, I did not want to create a mid-table labeled topics, so I incorporated that in the Posts table. The other reasoning behind this, is that to create a hybrid forum/blog, the topic is [...]]]></description>
			<content:encoded><![CDATA[<p>To continue on last weeks thought of a &#8220;lite&#8221; forum, I needed 2 tables (Forums, Posts). Since this is a &#8220;lite&#8221; forum, I did not want to create a mid-table labeled topics, so I incorporated that in the Posts table. The other reasoning behind this, is that to create a hybrid forum/blog, the topic is really just a beginning post in the thread, so keep those in the Posts table, just mark it as a topic to differentiate this from the other posts.</p>
<p>I created two controllers, forums_controller.php and posts_controller.php. All of the links on the application will point to the forums_controller.php file. The models need to be created, forum.php and post.php, with the relationships.</p>
<p>The file user.php (User model) needs to have a &#8220;hasMany&#8221; relationship with Posts.</p>
<pre>
var $hasMany = array(
	'Post' => array('className' => 'Post',
		'foreignKey' => 'post_id',
		'dependent' => false,
	)
);</pre>
<p>The Forum model needs a &#8220;hasMany&#8221; relationship with the Post model</p>
<pre>
var $hasMany = array(
	'Post' => array('className' => 'Post',
		'foreignKey' => 'forum_id',
		'dependent' => false,
	)
);</pre>
<p>The Post model needs a &#8220;belongsTo relationship with both the Forum and User models.</p>
<pre>var $belongsTo = array(
    'Forum' =&gt; array('className' =&gt; 'Forum',
        'foreignKey' =&gt; 'forum_id',
        'conditions' =&gt; '',
        'fields' =&gt; '',
        'order' =&gt; ''
    ),
    'User' =&gt; array('className' =&gt; 'User',
        'foreignKey' =&gt; 'user_id',
        'conditions' =&gt; '',
        'fields' =&gt; '',
        'order' =&gt; ''
)
);</pre>
<p><span id="more-10"></span></p>
<p>Now that these are completed, it is time to look at the pagination. Blind pagination will just pull the entire model and its relationships. Thus meaning, that if I were to do a blind pagination, as the Bake method does, it would list all entries in the Post table, including posts marked as topics, posts that belong to other forums, etc. Since I am not interested in paginating the Forum model, we need to address this in the Post model. </p>
<p>Since a full customized pagination is not needed, I need to alter the blind pagination and call just those posts needed. To do this, there are a few additions that need to be made to the forums_controller.php file. Near the top where all the vars are called, I entered:</p>
<pre>
var $paginate = array(
	'Post'	=> array(
		'limit'	=> 2,
		'page'	=> 1,
		'order'	=> array(
			'Post.created'	=> 'asc')
	)
);
</pre>
<p>This sets the variables to use for the paginator object. I am setting the limit to 2 right now, so I can test the links in the view, I will change that to 15 or 20 (depending on final pass with the customer). I am also setting a sort order. Now I will go to the &#8220;viewtopics&#8221; action I created.</p>
<pre>
function viewtopic($forum = null, $topic = null) {
 ...
}</pre>
<p>I need the forum_id and the $topic_id to build correct queries, links, etc. To set up the pagination for this, I need to set a conditions variable.</p>
<pre>$cond = array('Post.parent_topic' => $topic);</pre>
<p>The $topic variable is the &#8220;parent_topic&#8221; post. This will grab all of the posts where the parent_id == $topic. But I should also grab the &#8220;parent&#8221; (topic post) so I can adjust the the $cond var to be:</p>
<pre>$cond = array('OR' =>array('Post.parent_topic' => $topic, 'Post.post_id' => $topic));</pre>
<p>This will find all posts for this topic, by using &#8220;WHERE (`Post`.`parent_topic` = 3) <b>OR</b> (`Post`.`post_id` = 3)&#8221;. Now set the paginator object and view variable. To do this, I need to call the Model object I want to paginate, and the conditions variable I just set. </p>
<pre>$this->set('posts', $this->paginate("Post", $cond));</pre>
<p>This now grabs all of the right posts, and creates an array with the results and ready to paginate in the view. Since this function relies on 2 parameters from the URL, we will need to adjust the links in the view. </p>
<p>To view all the posts, I created a viewtopic action, please follow the documentation in the <a href="http://book.cakephp.org/view/164/pagination">CakePHP manual</a>. This basically sets up the total number of results, how many pages, and the navigation for each page. At the top of my viewtopic.ctp page I have</p>
<pre>
echo $paginator->counter(array(
'format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%', true)
));</pre>
<p>At the bottom of the view, I have the following:</p>
<pre>
&lt;div class="paging"&gt;
	<?php echo $paginator->prev('<< '.__('Previous Page', true), array(), null, array('class'=>'disabled'));?>
 | 	<?php echo $paginator->numbers();?>
	<?php echo $paginator->next(__('Next Page', true).' >>', array(), null, array('class'=>'disabled'));?>
&lt;/div&gt;
</pre>
<p>(Yours may differ slightly, and that is ok). This sets the view up. </p>
<p>The code at the top is ok, as that is not going to have any problems. However, the bottom navigation is a little broken, as I need it to point to: example.com/forums/viewtopic/1/3/page:2. But it points to example.com/forums/viewtopic/page:2, which will create an error because those parameters are not in the URL. The paginator functions as listed above do not pass in the parameters, so we need to add those in. </p>
<p>The <a href="http://api.cakephp.org/1.2/class_paginator_helper.html">Paginator API documentation </a> has a list of the different functions available. Using these, and viewing the actual code for those functions is also very helpful. But to break these down:<br />
prev ($title= &#8216;<< Previous', $options=array(), $disabledTitle=null, $disabledOptions=array())<br />
numbers ($options=array())<br />
next ($title= 'Next >>&#8217;, $options=array(), $disabledTitle=null, $disabledOptions=array())</p>
<p>The prev() and the next() functions have four parameters, the title for the link, options to set, title when the link is disabled, and options for the disabled pagination link. The numbers() function has only one, which is the $options. This is the one we need to work on to get the links working. However, the options requires a specific set of keys, which can be found at the <a href="http://api.cakephp.org/1.2/paginator_8php-source.html">API source code</a>. The one we are going to focus on is, &#8220;<i>$options['url']</i> &#8211; Url of the action&#8221;.</p>
<p>To update the code above to pass in the correct forum, and topic, we need to alter the prev tag as follows:</p>
<pre>
$paginator->prev('<< '.__('Previous Page', true), array('url' => $paginator->params['pass']), null, array('class'=>'disabled'));?></pre>
<p>Just so that it is more readable, I am going to split this up:</p>
<dl>
<dt>&#8216;<< '.__('Previous Page', true) </dt>
<dd>This is the title parameter</dd>
<dt>array(&#8216;url&#8217; => $paginator->params['pass']) </dt>
<dd>this is the options section I modified. It is passing in the &#8216;url&#8217; key, then passing the params from the $paginator object (which hold any type of parameters from the link). So if there are 2 parameters like in this example, or 10, it will get all of those.</dd>
<dt>null </dt>
<dd> title link disabled, no change</dt>
<dt>array(&#8216;class&#8217;=>&#8217;disabled&#8217;) </dt>
<dd> disabled options link, which we kept at the same. </dd>
</dl>
<p>So by just copying and pasting the following<br />
array(&#8216;url&#8217; => $paginator->params['pass'])<br />
in the prev, next and numbers functions, it will paginate the results, and put in the correct forum and post ids in the link. </p>
<p>And just like that, the pagination is complete for a semi-custom pagination of forum/posts results. It may not exactly match up any other ones, but it will work. There may also be short cuts to this as well, and if you know of any, then please mention them. </p>
]]></content:encoded>
			<wfw:commentRss>http://www.hirdweb.com/2008/08/04/custom-pagination-in-cakephp/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
	</channel>
</rss>

