<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	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/"
		>
<channel>
	<title>Comments on: Using MySQL to generate daily sales reports with filled gaps</title>
	<atom:link href="http://www.richnetapps.com/using-mysql-generate-daily-sales-reports-filled-gaps/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.richnetapps.com/using-mysql-generate-daily-sales-reports-filled-gaps/</link>
	<description>Internet Applications - Flash, Flex, Silverlight, JavaFX</description>
	<lastBuildDate>Fri, 02 Dec 2011 23:04:55 +0000</lastBuildDate>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
	<item>
		<title>By: ipkwena</title>
		<link>http://www.richnetapps.com/using-mysql-generate-daily-sales-reports-filled-gaps/comment-page-1/#comment-816</link>
		<dc:creator>ipkwena</dc:creator>
		<pubDate>Sun, 09 Jan 2011 17:58:03 +0000</pubDate>
		<guid isPermaLink="false">http://www.richnetapps.com/?p=436#comment-816</guid>
		<description>Thanks for the article/tutorial. Its simplicity and level of detail makes it of particular relevance to a small project I am working on.</description>
		<content:encoded><![CDATA[<p>Thanks for the article/tutorial. Its simplicity and level of detail makes it of particular relevance to a small project I am working on.</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Jose</title>
		<link>http://www.richnetapps.com/using-mysql-generate-daily-sales-reports-filled-gaps/comment-page-1/#comment-652</link>
		<dc:creator>Jose</dc:creator>
		<pubDate>Thu, 04 Mar 2010 05:33:03 +0000</pubDate>
		<guid isPermaLink="false">http://www.richnetapps.com/?p=436#comment-652</guid>
		<description>Ok..Here&#039;s my try..I think this problem is easier to solve if we think in terms of unix timestamps:
I first create a &quot;numbers&quot; table, with a single field (id) that goes from 0 to &lt;max number of dates that may get involved in the query&gt;. The query then is this:

select @row+numbers.id*86400 , orders.quantity  FROM (select @row:=(select UNIX_TIMESTAMP(min(order_date)) FROM orders), @end:=(select UNIX_TIMESTAMP(max
(order_date)) FROM orders)) r , numbers LEFT JOIN orders ON UNIX_TIMESTAMP(order_date)=@row+numbers.id*86400 WHERE @row+numbers.id*86400 &lt; @end;

@row is initialized as the min date.The 86400 are the seconds in a day.The entries in the &quot;numbers&quot; table (0..1..2....n) makes the expression (@row+numbers.id*86400) jump one day at a time, which is then left joined to dates in the orders table.
In the case you&#039;d need hours, or minutes, instead of days, changing the constant should be enough.
I&#039;ve just crafted this query, so i dont know if it&#039;s bug-free! </description>
		<content:encoded><![CDATA[<p>Ok..Here&#8217;s my try..I think this problem is easier to solve if we think in terms of unix timestamps:<br />
I first create a &#8220;numbers&#8221; table, with a single field (id) that goes from 0 to &lt;max number of dates that may get involved in the query&gt;. The query then is this:</p>
<p>select @row+numbers.id*86400 , orders.quantity  FROM (select @row:=(select UNIX_TIMESTAMP(min(order_date)) FROM orders), @end:=(select UNIX_TIMESTAMP(max<br />
(order_date)) FROM orders)) r , numbers LEFT JOIN orders ON UNIX_TIMESTAMP(order_date)=@row+numbers.id*86400 WHERE @row+numbers.id*86400 &lt; @end;</p>
<p>@row is initialized as the min date.The 86400 are the seconds in a day.The entries in the &#8220;numbers&#8221; table (0..1..2&#8230;.n) makes the expression (@row+numbers.id*86400) jump one day at a time, which is then left joined to dates in the orders table.<br />
In the case you&#8217;d need hours, or minutes, instead of days, changing the constant should be enough.<br />
I&#8217;ve just crafted this query, so i dont know if it&#8217;s bug-free! </p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Mikolaj</title>
		<link>http://www.richnetapps.com/using-mysql-generate-daily-sales-reports-filled-gaps/comment-page-1/#comment-622</link>
		<dc:creator>Mikolaj</dc:creator>
		<pubDate>Mon, 18 Jan 2010 12:23:47 +0000</pubDate>
		<guid isPermaLink="false">http://www.richnetapps.com/?p=436#comment-622</guid>
		<description>OK, so query should look like this:
&lt;pre&gt;SELECT calendar.datefield AS date, 
       IFNULL(SUM(orders.quantity),0) AS total_sales
FROM calendar &lt;strong&gt;LEFT &lt;/strong&gt;JOIN orders ON (calendar.datefield = DATE(orders.order_date))
WHERE (calendar.datefield BETWEEN (&#039;2009-10-01&#039;) AND (&#039;2009-10-31&#039;) 
GROUP BY date;
And one mote thing... If you would like to get sales info per product_id or customer_id, please remember to add condition to JOIN part and not into WHERE part like:
SELECT calendar.datefield AS date, 
       IFNULL(SUM(orders.quantity),0) AS total_sales,
       customer_id
FROM calendar &lt;strong&gt;LEFT &lt;/strong&gt;JOIN orders ON (calendar.datefield = DATE(orders.order_date)) AND customer_id=1
WHERE (calendar.datefield BETWEEN (&#039;2009-10-01&#039;) AND (&#039;2009-10-31&#039;) 
GROUP BY date;
otherwise you will not get all NULLs from the join.
Regards
MJ




&lt;/pre&gt;</description>
		<content:encoded><![CDATA[<p>OK, so query should look like this:</p>
<pre>SELECT calendar.datefield AS date,
       IFNULL(SUM(orders.quantity),0) AS total_sales
FROM calendar <strong>LEFT </strong>JOIN orders ON (calendar.datefield = DATE(orders.order_date))
WHERE (calendar.datefield BETWEEN ('2009-10-01') AND ('2009-10-31')
GROUP BY date;
And one mote thing... If you would like to get sales info per product_id or customer_id, please remember to add condition to JOIN part and not into WHERE part like:
SELECT calendar.datefield AS date,
       IFNULL(SUM(orders.quantity),0) AS total_sales,
       customer_id
FROM calendar <strong>LEFT </strong>JOIN orders ON (calendar.datefield = DATE(orders.order_date)) AND customer_id=1
WHERE (calendar.datefield BETWEEN ('2009-10-01') AND ('2009-10-31')
GROUP BY date;
otherwise you will not get all NULLs from the join.
Regards
MJ
</pre>
]]></content:encoded>
	</item>
	<item>
		<title>By: Armand Niculescu</title>
		<link>http://www.richnetapps.com/using-mysql-generate-daily-sales-reports-filled-gaps/comment-page-1/#comment-621</link>
		<dc:creator>Armand Niculescu</dc:creator>
		<pubDate>Mon, 18 Jan 2010 10:45:01 +0000</pubDate>
		<guid isPermaLink="false">http://www.richnetapps.com/?p=436#comment-621</guid>
		<description>Yes, I did it that way for simplicity.

I&#039;ll leave it as an exercise for the user to change to show the dates from the start of the calendar :)</description>
		<content:encoded><![CDATA[<p>Yes, I did it that way for simplicity.</p>
<p>I&#8217;ll leave it as an exercise for the user to change to show the dates from the start of the calendar <img src='http://www.richnetapps.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Mikolaj</title>
		<link>http://www.richnetapps.com/using-mysql-generate-daily-sales-reports-filled-gaps/comment-page-1/#comment-620</link>
		<dc:creator>Mikolaj</dc:creator>
		<pubDate>Mon, 18 Jan 2010 10:34:25 +0000</pubDate>
		<guid isPermaLink="false">http://www.richnetapps.com/?p=436#comment-620</guid>
		<description>Hello
I thinkyour exaple will miss first days if no orders ware made this days.
I mean I yu have table orders filled with dates starting from 3rd of November 
 calendar.datefield BETWEEN (SELECT Min(Date(order_date))
wil still miss 1st and 2nd of November.

Regards
MJ</description>
		<content:encoded><![CDATA[<p>Hello<br />
I thinkyour exaple will miss first days if no orders ware made this days.<br />
I mean I yu have table orders filled with dates starting from 3rd of November <br />
 calendar.datefield BETWEEN (SELECT Min(Date(order_date))<br />
wil still miss 1st and 2nd of November.</p>
<p>Regards<br />
MJ</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Philippe</title>
		<link>http://www.richnetapps.com/using-mysql-generate-daily-sales-reports-filled-gaps/comment-page-1/#comment-570</link>
		<dc:creator>Philippe</dc:creator>
		<pubDate>Sun, 20 Sep 2009 18:40:47 +0000</pubDate>
		<guid isPermaLink="false">http://www.richnetapps.com/?p=436#comment-570</guid>
		<description>Hi Armand,

&quot;On what version of MySQL have you tested? My code works perfectly on MySQL 5.0.22, I just retested it.&quot;
Response : MySql 5.1.36
I agree for the inconvenience of temporary tables so I used a table in memory to get better perfromances.
Plus, using a stored proc permits to delimit the period to examine instead of querying for all records contained in orders table.
This is another approach to extend the point of view and to give more ideas for members looking at your great article. That was just my intention.
Regards.</description>
		<content:encoded><![CDATA[<p>Hi Armand,</p>
<p>&#8220;On what version of MySQL have you tested? My code works perfectly on MySQL 5.0.22, I just retested it.&#8221;<br />
Response : MySql 5.1.36<br />
I agree for the inconvenience of temporary tables so I used a table in memory to get better perfromances.<br />
Plus, using a stored proc permits to delimit the period to examine instead of querying for all records contained in orders table.<br />
This is another approach to extend the point of view and to give more ideas for members looking at your great article. That was just my intention.<br />
Regards.</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Armand Niculescu</title>
		<link>http://www.richnetapps.com/using-mysql-generate-daily-sales-reports-filled-gaps/comment-page-1/#comment-569</link>
		<dc:creator>Armand Niculescu</dc:creator>
		<pubDate>Sun, 20 Sep 2009 14:30:12 +0000</pubDate>
		<guid isPermaLink="false">http://www.richnetapps.com/?p=436#comment-569</guid>
		<description>I briefly mentioned the possibility of a temporary table in the article, it was actually my first thought when I had to deal with the situation. 

I chose to use a real table instead because I can effectively &quot;cache&quot; the results. A temporary table has to be created and filled every time you do the query. I&#039;ve done my share of work with sprocs, cursors and temporary tables (in TSQL mostly) and I developed a mild hatred for them :)</description>
		<content:encoded><![CDATA[<p>I briefly mentioned the possibility of a temporary table in the article, it was actually my first thought when I had to deal with the situation. </p>
<p>I chose to use a real table instead because I can effectively &#8220;cache&#8221; the results. A temporary table has to be created and filled every time you do the query. I&#8217;ve done my share of work with sprocs, cursors and temporary tables (in TSQL mostly) and I developed a mild hatred for them <img src='http://www.richnetapps.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Armand Niculescu</title>
		<link>http://www.richnetapps.com/using-mysql-generate-daily-sales-reports-filled-gaps/comment-page-1/#comment-568</link>
		<dc:creator>Armand Niculescu</dc:creator>
		<pubDate>Sun, 20 Sep 2009 14:22:13 +0000</pubDate>
		<guid isPermaLink="false">http://www.richnetapps.com/?p=436#comment-568</guid>
		<description>Hi Philippe,

On what version of MySQL have you tested? My code works perfectly on MySQL 5.0.22, I just retested it.

Your code does make sense, not relying on MySQL implicit casting to DATE, but I tried to keep the code to a minimum (as a sidenote, the PHP code used to generate the chart is more complex if you want to show the dates and months below, but I omitted it too for brevity).</description>
		<content:encoded><![CDATA[<p>Hi Philippe,</p>
<p>On what version of MySQL have you tested? My code works perfectly on MySQL 5.0.22, I just retested it.</p>
<p>Your code does make sense, not relying on MySQL implicit casting to DATE, but I tried to keep the code to a minimum (as a sidenote, the PHP code used to generate the chart is more complex if you want to show the dates and months below, but I omitted it too for brevity).</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Philippe</title>
		<link>http://www.richnetapps.com/using-mysql-generate-daily-sales-reports-filled-gaps/comment-page-1/#comment-567</link>
		<dc:creator>Philippe</dc:creator>
		<pubDate>Sun, 20 Sep 2009 11:55:52 +0000</pubDate>
		<guid isPermaLink="false">http://www.richnetapps.com/?p=436#comment-567</guid>
		<description>To respond to ralph neubauer, why not create a stored proc who creates automatically a memory table with the dates needed for the job ?
There is the source code I suggest for the stored proct (PL/SQL for MYsql )

DELIMITER //

DROP PROCEDURE IF EXISTS sales_by_period//

CREATE PROCEDURE sales_by_period(IN start_date DATE, IN end_date DATE)
COMMENT &#039;Returns a dataset for sales between start_date and end_date&#039;
BEGIN
  -- Drop and creates a temporary table in memory for the time of the session
  DROP TABLE IF EXISTS mem_calendar;
  CREATE TEMPORARY TABLE mem_calendar (
    datefield DATE
  ) ENGINE=MEMORY;

  -- fills temporary table with dates
  SET @temp_date := start_date;
  WHILE @temp_date &lt;= end_date DO
    INSERT INTO mem_calendar VALUES(@temp_date);
    SET @temp_date = ADDDATE(@temp_date, INTERVAL 1 DAY);
  END WHILE;

  -- retieves and returns data
  SELECT
    mem_calendar.datefield AS date_order,
    IFNULL(SUM(orders.quantity),0) AS total_sales
  FROM orders
  RIGHT JOIN mem_calendar ON DATE(orders.order_date) = mem_calendar.datefield
  WHERE mem_calendar.datefield BETWEEN start_date AND end_date
  GROUP BY date_order;
END //

DELIMITER ;

As you can see, instead of using a physical table I use a memory table wich is faster.
But you need to activate the &quot;memory&quot; engine which is not activated by default (it seems to me...).
With such a stored proc, you prevent every situation.

Hope this helps.
Regards,
Philippe</description>
		<content:encoded><![CDATA[<p>To respond to ralph neubauer, why not create a stored proc who creates automatically a memory table with the dates needed for the job ?<br />
There is the source code I suggest for the stored proct (PL/SQL for MYsql )</p>
<p>DELIMITER //</p>
<p>DROP PROCEDURE IF EXISTS sales_by_period//</p>
<p>CREATE PROCEDURE sales_by_period(IN start_date DATE, IN end_date DATE)<br />
COMMENT &#8216;Returns a dataset for sales between start_date and end_date&#8217;<br />
BEGIN<br />
  &#8212; Drop and creates a temporary table in memory for the time of the session<br />
  DROP TABLE IF EXISTS mem_calendar;<br />
  CREATE TEMPORARY TABLE mem_calendar (<br />
    datefield DATE<br />
  ) ENGINE=MEMORY;</p>
<p>  &#8212; fills temporary table with dates<br />
  SET @temp_date := start_date;<br />
  WHILE @temp_date &lt;= end_date DO<br />
    INSERT INTO mem_calendar VALUES(@temp_date);<br />
    SET @temp_date = ADDDATE(@temp_date, INTERVAL 1 DAY);<br />
  END WHILE;</p>
<p>  &#8212; retieves and returns data<br />
  SELECT<br />
    mem_calendar.datefield AS date_order,<br />
    IFNULL(SUM(orders.quantity),0) AS total_sales<br />
  FROM orders<br />
  RIGHT JOIN mem_calendar ON DATE(orders.order_date) = mem_calendar.datefield<br />
  WHERE mem_calendar.datefield BETWEEN start_date AND end_date<br />
  GROUP BY date_order;<br />
END //</p>
<p>DELIMITER ;</p>
<p>As you can see, instead of using a physical table I use a memory table wich is faster.<br />
But you need to activate the &quot;memory&quot; engine which is not activated by default (it seems to me&#8230;).<br />
With such a stored proc, you prevent every situation.</p>
<p>Hope this helps.<br />
Regards,<br />
Philippe</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Philippe</title>
		<link>http://www.richnetapps.com/using-mysql-generate-daily-sales-reports-filled-gaps/comment-page-1/#comment-566</link>
		<dc:creator>Philippe</dc:creator>
		<pubDate>Sun, 20 Sep 2009 11:07:35 +0000</pubDate>
		<guid isPermaLink="false">http://www.richnetapps.com/?p=436#comment-566</guid>
		<description>Very illustrative article.
Best techniques are sometimes so simple and obvious that we do forget they exist !
Thanks a lot for your article.

But, there is a &quot;but&quot;. The output is buggy and, with data provided above you don&#039;t obtain the good result.
Instead, you obtain this :
date_order, total_sales
&#039;2009-08-16&#039;, 0
&#039;2009-08-17&#039;, 1
&#039;2009-08-18&#039;, 12
&#039;2009-08-19&#039;, 0
&#039;2009-08-20&#039;, 0
&#039;2009-08-21&#039;, 1

As you can see, the two first  rows are not taken in account :
  (&#039;2009-08-15 12:20:20&#039;, &#039;1&#039;, &#039;2&#039;, &#039;123&#039;),
  (&#039;2009-08-15 12:20:20&#039;, &#039;2&#039;, &#039;2&#039;, &#039;123&#039;),
Why ? Because of this part of the query : SELECT MIN(order_date) FROM orders.
In fact, SQL returns &quot;2009-08-15 12:20:20&quot; which is greater than &quot;2009-08-15 00:00:00&quot; (the value for a date field only).
Using the keyword &quot;BETWEEN&quot;, you say SQL retrieve values that are greater or equal then &quot;2009-08-15 00:00:00&quot;.
So, the correct query would be :
SELECT MIN(DATE(order_date)) FROM orders
In integrality :
SELECT   calendar.datefield             AS date_order,
         Ifnull(Sum(orders.quantity),0) AS total_sales
FROM     orders
         RIGHT JOIN calendar
           ON Date(orders.order_date) = calendar.datefield
WHERE    calendar.datefield BETWEEN (SELECT Min(Date(order_date))
                                     FROM   orders) AND (SELECT Max(order_date)
                                                         FROM   orders)
GROUP BY date_order
And the result is :
&#039;2009-08-15&#039;, 4
&#039;2009-08-16&#039;, 0
&#039;2009-08-17&#039;, 1
&#039;2009-08-18&#039;, 12
&#039;2009-08-19&#039;, 0
&#039;2009-08-20&#039;, 0
&#039;2009-08-21&#039;, 1

Have a nice day ;)</description>
		<content:encoded><![CDATA[<p>Very illustrative article.<br />
Best techniques are sometimes so simple and obvious that we do forget they exist !<br />
Thanks a lot for your article.</p>
<p>But, there is a &#8220;but&#8221;. The output is buggy and, with data provided above you don&#8217;t obtain the good result.<br />
Instead, you obtain this :<br />
date_order, total_sales<br />
&#8217;2009-08-16&#8242;, 0<br />
&#8217;2009-08-17&#8242;, 1<br />
&#8217;2009-08-18&#8242;, 12<br />
&#8217;2009-08-19&#8242;, 0<br />
&#8217;2009-08-20&#8242;, 0<br />
&#8217;2009-08-21&#8242;, 1</p>
<p>As you can see, the two first  rows are not taken in account :<br />
  (&#8217;2009-08-15 12:20:20&#8242;, &#8217;1&#8242;, &#8217;2&#8242;, &#8217;123&#8242;),<br />
  (&#8217;2009-08-15 12:20:20&#8242;, &#8217;2&#8242;, &#8217;2&#8242;, &#8217;123&#8242;),<br />
Why ? Because of this part of the query : SELECT MIN(order_date) FROM orders.<br />
In fact, SQL returns &#8220;2009-08-15 12:20:20&#8243; which is greater than &#8220;2009-08-15 00:00:00&#8243; (the value for a date field only).<br />
Using the keyword &#8220;BETWEEN&#8221;, you say SQL retrieve values that are greater or equal then &#8220;2009-08-15 00:00:00&#8243;.<br />
So, the correct query would be :<br />
SELECT MIN(DATE(order_date)) FROM orders<br />
In integrality :<br />
SELECT   calendar.datefield             AS date_order,<br />
         Ifnull(Sum(orders.quantity),0) AS total_sales<br />
FROM     orders<br />
         RIGHT JOIN calendar<br />
           ON Date(orders.order_date) = calendar.datefield<br />
WHERE    calendar.datefield BETWEEN (SELECT Min(Date(order_date))<br />
                                     FROM   orders) AND (SELECT Max(order_date)<br />
                                                         FROM   orders)<br />
GROUP BY date_order<br />
And the result is :<br />
&#8217;2009-08-15&#8242;, 4<br />
&#8217;2009-08-16&#8242;, 0<br />
&#8217;2009-08-17&#8242;, 1<br />
&#8217;2009-08-18&#8242;, 12<br />
&#8217;2009-08-19&#8242;, 0<br />
&#8217;2009-08-20&#8242;, 0<br />
&#8217;2009-08-21&#8242;, 1</p>
<p>Have a nice day <img src='http://www.richnetapps.com/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
]]></content:encoded>
	</item>
</channel>
</rss>

<!-- Performance optimized by W3 Total Cache. Learn more: http://www.w3-edge.com/wordpress-plugins/

Minified using disk: basic (Feed is rejected)
Page Caching using disk: enhanced

Served from: www.richnetapps.com @ 2012-02-07 15:14:25 -->
