Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Here are three different ways to do it. All depend on generating a time series then scanning it. One uses subqueries to aggregate data for each month. One joins the table twice against the series with different criteria. An alternate form does a single join on the time series, retaining rows that match <em>either</em> start or end date, then uses predicates in the counts to further filter the results.</p> <p><code>EXPLAIN ANALYZE</code> will help you pick which approach works best for your data.</p> <p><a href="http://sqlfiddle.com/#!12/99c2a/9" rel="nofollow">http://sqlfiddle.com/#!12/99c2a/9</a></p> <p>Test setup:</p> <pre><code>CREATE TABLE accounts ("id" int, "signup_date" timestamp, "cancel_date" timestamp); INSERT INTO accounts ("id", "signup_date", "cancel_date") VALUES (1, '2012-01-13 00:00:00', NULL), (2, '2012-01-15 00:00:00', '2012-02-05'), (3, '2012-03-01 00:00:00', '2012-03-20') ; </code></pre> <p>By single join and filter in count:</p> <pre><code>SELECT m, count(nullif(date_trunc('month',a.signup_date) = m,'f')), count(nullif(date_trunc('month',a.cancel_date) = m,'f')) FROM generate_series( (SELECT date_trunc('month',min(signup_date)) FROM accounts), (SELECT date_trunc('month',greatest(max(signup_date),max(cancel_date))) FROM accounts), INTERVAL '1' MONTH ) AS m INNER JOIN accounts a ON (date_trunc('month',a.signup_date) = m OR date_trunc('month',a.cancel_date) = m) GROUP BY m ORDER BY m; </code></pre> <p>By joining the <code>accounts</code> table twice:</p> <pre><code>SELECT m, count(s.signup_date) AS n_signups, count(c.cancel_date) AS n_cancels FROM generate_series( (SELECT date_trunc('month',min(signup_date)) FROM accounts), (SELECT date_trunc('month',greatest(max(signup_date),max(cancel_date))) FROM accounts), INTERVAL '1' MONTH ) AS m LEFT OUTER JOIN accounts s ON (date_trunc('month',s.signup_date) = m) LEFT OUTER JOIN accounts c ON (date_trunc('month',c.cancel_date) = m) GROUP BY m ORDER BY m; </code></pre> <p>Alternately, using subqueries:</p> <pre><code>SELECT m, ( SELECT count(signup_date) FROM accounts WHERE date_trunc('month',signup_date) = m ) AS n_signups, ( SELECT count(signup_date) FROM accounts WHERE date_trunc('month',cancel_date) = m )AS n_cancels FROM generate_series( (SELECT date_trunc('month',min(signup_date)) FROM accounts), (SELECT date_trunc('month',greatest(max(signup_date),max(cancel_date))) FROM accounts), INTERVAL '1' MONTH ) AS m GROUP BY m ORDER BY m; </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload