Aggregating Attributes
HOWTO

Your query needs to do the following:


UNPIVOT the data
PIVOT the data to get the COUNT()
add missing rows


sub-query of all row LEFT OUTER JOIN calculated data
use of then not-well-known MODEL clause

and, of course, the ORDER BY clause


EXAMPLE 1 (sub-query)

WITH DATA_SET
AS
    (   SELECT  1   AS REVIEW_ID,
                'a' AS PRODUCT_ID,
                5   AS OVERALL,
                4   AS COMFORT,
                5   AS FIT,
                4   AS APPEARANCE
        FROM    DUAL
        UNION ALL
        SELECT  2   AS REVIEW_ID,
                'a' AS PRODUCT_ID,
                4   AS OVERALL,
                4   AS COMFORT,
                4   AS FIT,
                4   AS APPEARANCE
        FROM    DUAL
        UNION ALL
        SELECT  3   AS REVIEW_ID,
                'b' AS PRODUCT_ID,
                4   AS OVERALL,
                5   AS COMFORT,
                4   AS FIT,
                5   AS APPEARANCE
        FROM    DUAL
        UNION ALL
        SELECT  4   AS REVIEW_ID,
                'c' AS PRODUCT_ID,
                3   AS OVERALL,
                2   AS COMFORT,
                2   AS FIT,
                4   AS APPEARANCE
        FROM    DUAL
        UNION ALL
        SELECT  5   AS REVIEW_ID,
                'c' AS PRODUCT_ID,
                2   AS OVERALL,
                1   AS COMFORT,
                2   AS FIT,
                1   AS APPEARANCE
        FROM    DUAL
    )
,unpivot_data as (
  SELECT  product_id, score, catagory
  FROM    DATA_SET
    UNPIVOT ( score
              for catagory in (
                overall, comfort, fit, appearance
              )
            )
  )
,count_values as (  
  select product_id, score, overall, comfort, fit, appearance
  from unpivot_data
  pivot (
    count(catagory)
    for catagory in ( 'OVERALL' AS overall, 'COMFORT' as comfort, 'FIT' as fit, 'APPEARANCE' as appearance)
  )
)
,all_rows as (
  select *
  from (select distinct product_id from data_set),
       (select level as score from dual connect by level &lt;= 5)
)
select a.product_id, a.product_id
  ,nvl( b.overall, 0) overall
  ,nvl( b.comfort, 0) comfort
  ,nvl( b.fit, 0) fit
  ,nvl( b.appearance, 0) appearance
from all_rows a
  left outer join count_values b
    on (a.product_id=b.product_id and a.score=b.score)
order by a.product_id, a.score


EXAMPLE 2 (model)

WITH DATA_SET
AS
    (   SELECT  1   AS REVIEW_ID,
                'a' AS PRODUCT_ID,
                5   AS OVERALL,
                4   AS COMFORT,
                5   AS FIT,
                4   AS APPEARANCE
        FROM    DUAL
        UNION ALL
        SELECT  2   AS REVIEW_ID,
                'a' AS PRODUCT_ID,
                4   AS OVERALL,
                4   AS COMFORT,
                4   AS FIT,
                4   AS APPEARANCE
        FROM    DUAL
        UNION ALL
        SELECT  3   AS REVIEW_ID,
                'b' AS PRODUCT_ID,
                4   AS OVERALL,
                5   AS COMFORT,
                4   AS FIT,
                5   AS APPEARANCE
        FROM    DUAL
        UNION ALL
        SELECT  4   AS REVIEW_ID,
                'c' AS PRODUCT_ID,
                3   AS OVERALL,
                2   AS COMFORT,
                2   AS FIT,
                4   AS APPEARANCE
        FROM    DUAL
        UNION ALL
        SELECT  5   AS REVIEW_ID,
                'c' AS PRODUCT_ID,
                2   AS OVERALL,
                1   AS COMFORT,
                2   AS FIT,
                1   AS APPEARANCE
        FROM    DUAL
    )
,unpivot_data as (
  SELECT  product_id, score, catagory
  FROM    DATA_SET
    UNPIVOT ( score
              for catagory in (
                overall, comfort, fit, appearance
              )
            )
  )
,count_values as (  
  select product_id, score, overall, comfort, fit, appearance
  from unpivot_data
  pivot (
    count(catagory)
    for catagory in ( 'OVERALL' AS overall, 'COMFORT' as comfort, 'FIT' as fit, 'APPEARANCE' as appearance)
  )
)
select *
from count_values
model return all rows
  DIMENSION by (product_id,score)
  measures ( overall, comfort, fit, appearance )
  rules (
    overall[ for product_id in ( select distinct product_id from data_set), for score in (1,2,3,4,5)] =
      nvl( overall[ cv(product_id), cv(score) ], 0),
    comfort[ for product_id in (select distinct product_id from data_set), for score in (1,2,3,4,5)] =
      nvl( comfort[ cv(product_id), cv(score) ], 0),
    fit[ for product_id in (select distinct product_id from data_set), for score in (1,2,3,4,5)] =
      nvl( fit[ cv(product_id), cv(score) ], 0),
    appearance[ for product_id in (select distinct product_id from data_set), for score in (1,2,3,4,5)] =
      nvl( appearance[ cv(product_id), cv(score) ], 0)
    )
order by product_id, score