Imagine that a row with "category, category2" can be transformed to two rows (one with "category", one with "category2") to get what you want. You'd do that like this:
SELECT items.category /* , other columns... */
FROM items
UNION ALL
SELECT items.category2 /* , other columns... */
FROM items
So all you then need to do is aggregate across these:
SELECT category, count(*) FROM (
SELECT items.category FROM items
UNION ALL
SELECT items.category2 FROM items
) expanded
GROUP BY category
You can also do the aggregate by stages like this if your database supports it:
with subcounts as (
select items.category, items.category2, count(*) as subcount
from items
group by category, category2)
select category, sum(subagg) as finalcount from (
select subcounts.category, sum(subcount) as subagg from subcounts group by category
union all
select subcounts.category2, sum(subcount) as subagg from subcounts group by category2
) combination
group by category
This will limit to just one scan of the main items table, good if you only have a small number of categories. You can emulate the same thing with temp tables in databases that don't support "WITH..."
EDIT:
I was sure there had to be another way to do it without scanning Items twice, and there is. Well, this is the PostgreSQL version:
SELECT category, count(*) FROM (
SELECT CASE selector WHEN 1 THEN category WHEN 2 THEN category2 END AS category
FROM Items, generate_series(1,2) selector
) items_fixed GROUP BY category
The only postgresql-specific bit here is "generate_series(1,2)" which produces a "table" containing two rows-- one with "1" and one with "2". Which is IMHO one of the handiest features in postgresql. You can implement similar things in the like of SQL Server as well, of course. Or you could say "(select 1 as selector union all select 2)". Another alternative is "(values(1),(2)) series(selector)" although how much of that syntax is standard and how much is postgres-specific, I'm not sure. Both these approaches have an advantage of giving the planner an idea that there will only be two rows.
Cross-joining this series table items allows us generate two output rows for each row of item. You can even take that "items_fixed" subquery and make it a view -- which btw is the reverse of the process I tend to use to try and solve these kind of problems.