6

I have a function which is selecting some data from a table. I would like to return the selected data and the total number of rows in that table.

How can I do that or how can I get the same result in the most efficient way?

I have tried few things and I ended up with the code below, now this is the format that I want, but count(*) over () as total_count will return 1 all the time, what I need it to return is the total number of rows from that records select.

SELECT 
row_to_json(selected_records) as data
FROM
(   
    SELECT
    count(*) over () as total_count,
    array_to_json(array_agg(row_to_json(records))) as data
    FROM (
        SELECT
            sum(entrances) as entrances
        FROM report_la
        WHERE profile_id = 3777614
        GROUP BY landing_path_id
        limit 10 offset 0
    ) records
) as selected_records

Updated, the code below is producing the result that I want, it will be nice if I can hide that total_count column from the records select

SELECT 
row_to_json(selected_records) as data
FROM
(   
    SELECT
    min(total_count) as total_count
    ,array_to_json(array_agg(row_to_json(records))) as data
    FROM (
        SELECT
            sum(entrances) as entrances
            ,count(*) over () as total_count
        FROM ga.report_la
        WHERE ga_profile_id = 3777614
        GROUP BY landing_path_id
        limit 10
    ) records
) as selected_records
2
  • 2
    You can add a column containing the total count using: count(*) over () as total_count (but that will be repeated for each row)
    – user1822
    Dec 4, 2014 at 15:50
  • I believe it's returning 1 all the time because you're grouping before you count(*) so there is only one row at that point. Dec 4, 2014 at 17:48

4 Answers 4

6

As I understand the question, you don't need a window function. Aggregate functions do the job:

  • count() in the lowest level (-> row_ct).
  • sum() the resulting row_ct in the next level (-> total_row_ct).
SELECT row_to_json(selected_records)::text AS data
FROM  (   
   SELECT array_to_json(array_agg(row_to_json(records))) AS data
        , sum(row_ct) AS total_row_ct
   FROM (
      SELECT landing_path_id
           , sum(entrances) AS entrances
           , count(*) AS row_ct
      FROM   report_la
      WHERE  profile_id = 3777614
      GROUP  BY landing_path_id
      LIMIT  10
      ) records
   ) selected_records;

I also included landing_path_id so the resulting data makes sense.

SQL Fiddle.

Window function?

A window function (count(*) over ()) does not seem to be what you want, since you don't have unaggregated rows.
You could add to the inner subquery:

count(*) OVER ()

.. to get the count of distinct landing_path_id, which is one other possible number that might be of interest. But that doesn't seem to be what you meant by "the total number of rows from that records select".
Or you could add to the inner subquery:

sum(count(*)) OVER ()

.. to get the total count with every landing_path_id redundantly, but that would seem pointless. Just mentioning that to demonstrate it's possible to run a window function over the result of an aggregate function in a single pass. Details for that:

Updated question

Your result, just without total_count in the records subquery. Now accounting for the LIMIT in the inner SELECT. Even though a maximum of 10 distinct landing_path_id is selected, all qualifying landing_path_id are counted.

To get both in one scan and reuse count and sum separately I introduce a CTE:

WITH cte AS (
  SELECT sum(entrances) AS entrances
       , count(*) over () AS total_count
  FROM   report_la
  WHERE  profile_id = 3777614
  GROUP  BY landing_path_id
  LIMIT  10
  )
SELECT row_to_json(selected_records)::text AS data
FROM  (   
   SELECT (SELECT total_count FROM cte LIMIT 1) AS total_count
        , array_to_json(array_agg(row_to_json(records))) AS data
   FROM  (SELECT entrances FROM cte) records
   ) selected_records;

If you don't care about the attribute name, you can have that cheaper with a subquery:

SELECT row_to_json(selected_records)::text AS data
FROM  (   
   SELECT min(total_count) AS total_count
        , array_to_json(array_agg(row_to_json(ROW(entrances)))) AS data
   FROM (
      SELECT sum(entrances) AS entrances
           , count(*) over () AS total_count  -- shouldn't show up in result
      FROM   report_la
      WHERE  profile_id = 3777614
      GROUP  BY landing_path_id
      LIMIT  1
      ) records
   ) selected_records;

You get the default attribute name f1 instead of entrances, since the ROW expression does not preserve the column name.

If you need a certain attribute name, you could cast the row to a registered type. (Ab-)using a TEMP TABLE to register my row type for the session:

CREATE TEMP TABLE rec1 (entrances bigint);

...
        , array_to_json(array_agg(row_to_json(ROW(entrances)::rec1))) AS data
...

This would be a bit faster than the CTE. Or, more verbose but without cast:

...
        , array_to_json(array_agg(row_to_json(
                   (SELECT x FROM (SELECT records.entrances) x)))) AS data
...

Detailed explanation in this related answer:

SQL Fiddle.

6
  • I have updated my question, thank you for the answer.
    – RockNinja
    Dec 5, 2014 at 10:06
  • well total_countis indeed returning the total number of records but with my updated solution I get the total number of records even if my limit is 10 (I get the total number of rows where profile_id is xxxx) For example I have 1000 rows where profile_id is xxxx, because of the group by, I get 622, so even if I put a limit of 10 the total_count is still going to be 622, where in your example the total_count is the limit number.
    – RockNinja
    Dec 5, 2014 at 11:08
  • Have a look at the limits here: sqlfiddle.com/#!15/2f787/3
    – RockNinja
    Dec 5, 2014 at 11:14
  • @alex: I see. I solved this with a CTE now. Consider the update. Dec 5, 2014 at 11:45
  • 1
    @alex: I added some more alternatives you might be interested in. Dec 5, 2014 at 12:19
2

You can do what a_horse_with_no_name or ForguesR suggested (I believe a_horse_with_no_name's suggestion is more efficient than ForguesR).. However, those will give you an additional column in your result set - which you may or may not want. It sort of depends on if you want that additional column (all showing the same data repeated) or if you want a row in your result set that's akin to a "summary row"..

My answer will address if you are interested in a "summary row" type of result.

Another option would be to have a query that gives you the COUNT(*) that you are looking for and then UNION it with your original query. The trick to this, of course, is to make sure that you have the same number of columns in both queries and that the COUNT(*) query ends up last. To do that, I have added a column that's used to sort by.. See simple example below.

-- assume col1, col2 are VARCHAR and col3, col4 are NUMERIC
SELECT
  col1, col2, col3, col4
FROM (
  SELECT
    col1, col2, col3, col4, 1 AS sorter
  FROM tab1
  UNION
  SELECT
    NULL::VARCHAR AS col1, NULL::VARCHAR AS col2, NULL::NUMERIC AS col3, COUNT(*) AS col4, 2 AS sorter
  FROM tab1
) a
ORDER BY a.sorter, a.col1, a.col2;
1

Just include a subquery to get the count of all the rows in your table. Something like :

SELECT *, (SELECT COUNT(*) FROM yourTable) AS TotalNbRows FROM yourTable
0

Put your aggregated query into subquery

SELECT COUNT( * ) 
  FROM (
  SELECT COUNT( * ) AS  `count` 
  FROM lms
  WHERE `lead_school_type` =3
  AND subscription.vad =1
  GROUP BY lms.user_id
) AS subcount

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.