I am getting the error subquery must return only one column
when I try to run the following query:
SELECT mat.mat as mat1, sum(stx.total) as sumtotal1,
(
SELECT mat.mat as mat, sum(stx.total) as sumtotal
FROM stx
LEFT JOIN mat ON stx.matid = mat.matid
LEFT JOIN sale ON stx.saleid = sale.id
WHERE stx.date BETWEEN '2013-05-01' AND '2013-08-31'
AND sale.userid LIKE 'A%'
GROUP BY mat.mat
) AS MyField
FROM stx
LEFT JOIN mat ON stx.matid = mat.matid
LEFT JOIN sale ON stx.saleid = sale.id
WHERE stx.date BETWEEN '2013-05-01' AND '2013-08-31'
AND sale.userid LIKE 'B%'
GROUP BY mat.mat
What is causing this error?
Put a subquery that returns multiple columns in the FROM
list and select from it.
A correlated subquery would be a bad idea to begin with. However, your query is not even correlated (no link to outer query) and seems to return multiple rows. This leads to a (possibly very expensive and nonsensical) cross join producing a Cartesian product.
Looks like you really want something like this:
SELECT m1.mat AS mat1, m1.sumtotal AS sumtotal1
, m2.mat AS mat2, m2.sumtotal AS sumtotal2
FROM (
SELECT mat.mat, sum(stx.total) AS sumtotal
FROM stx
LEFT JOIN mat ON mat.matid = stx.matid
LEFT JOIN sale ON stx.saleid = sale.id
WHERE stx.date BETWEEN '2013-05-01' AND '2013-08-31'
AND sale.userid LIKE 'A%'
GROUP BY mat.mat
) m1
JOIN (
SELECT mat.mat, sum(stx.total) AS sumtotal
FROM stx
LEFT JOIN mat ON mat.matid = stx.matid
LEFT JOIN sale ON sale.id = stx.saleid
WHERE stx.date BETWEEN '2013-05-01' AND '2013-08-31'
AND sale.userid LIKE 'B%'
GROUP BY mat.mat
) m2 USING (mat);
Both LEFT JOIN
are also pointless. The one on sale
is forced to a INNER JOIN
by the WHERE
condition. The one on mat
seems pointless, since you GROUP BY mat.mat
- except if you are interested in mat IS NULL
? (I doubt it.)
The whole query can further simplified to:
SELECT m.mat
, sum(x.total) FILTER (WHERE s.userid LIKE 'A%') AS total_a
, sum(x.total) FILTER (WHERE s.userid LIKE 'B%') AS total_b
FROM sale s
JOIN stx x ON x.saleid = s.id
JOIN mat m ON m.matid = x.matid
WHERE s.userid LIKE 'ANY ('{A%,B%}')
AND x.date BETWEEN '2013-05-01' AND '2013-08-31'
GROUP BY 1;
Using the aggregate FILTER
clause (Postgres 9.4+). See:
In Postgres 11 or later, this can be optimized further with the "starts with" operator ^@
for prefix matching. See:
SELECT m.mat
, sum(x.total) FILTER (WHERE s.userid ^@ 'A') AS total_a
, sum(x.total) FILTER (WHERE s.userid ^@ 'B') AS total_b
FROM sale s
JOIN stx x ON x.saleid = s.id
JOIN mat m ON m.matid = x.matid
WHERE s.userid ^@ ANY ('{A,B}')
AND x.date BETWEEN '2013-05-01' AND '2013-08-31'
GROUP BY 1;
The WHERE
condition might get even simpler, depending on your secret data types and indices. Here is an overview over pattern matching operators: