19

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?

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
user2431581
  • 305
  • 1
  • 2
  • 8
  • 2
    **@user2431581**, You are defining field `MyField` via a sub query. When you do that the sub query must only return one value and one field. You are returning `mat.mat` as well as `sum(stx.total)`. That is illegal SQL syntax. – Linger Oct 29 '13 at 03:52
  • Does this answer your question? [subquery must return only one column](https://stackoverflow.com/questions/6673834/subquery-must-return-only-one-column) – philipxy Apr 24 '23 at 22:41

2 Answers2

23

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:

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
  • 1
    "Put a subquery that returns multiple columns in the FROM list and select from it.". This! Thanks a lot. – András Aszódi Nov 24 '15 at 14:49
  • What if the select is performed against a function that has multiple OUT parameters (but the function doesn't return a record, setof, or any similar type and the function can't be altered)? For example, suppose a function has 2 out parameters, 'hi' and 'lo' -- so that regular usage returns a record tuple like (4, 5). But "select *" would return the results separately as two columns. Now suppose you want to execute that function on multiple columns of the result of some join. Some thing like `select my_func(a.colA, b.colB) from tabA a join tabB b on a.id = b.id`. – ely Jul 11 '16 at 00:58
  • 1
    @Mr.F: This is beyond a comment. I suggest you start a new question with the necessary information. You can always link to this one for reference and drop a comment to link to the related question and notify me. There is a solution ... – Erwin Brandstetter Jul 11 '16 at 01:00
  • Good advice. Here is a [link to the question](http://stackoverflow.com/questions/38297935/split-function-returned-record-into-multiple-columns). – ely Jul 11 '16 at 01:22
  • Nice Solution Saved my Neck Thanks Bro – Er. Amit Joshi Nov 16 '18 at 05:20
0

Instead of subquery select statement

SELECT mat.mat  as mat, sum(stx.total)  as sumtotal

Try this statement

SELECT sum(stx.total)  as sumtotal
Marcio Mazzucato
  • 8,841
  • 9
  • 64
  • 79
Snehal
  • 1,070
  • 12
  • 20
  • 1
    Try this state ment SELECT sum(stx.total) as sumtotal ERROR: more than one row returned by a subquery used as an expression – user2431581 Oct 29 '13 at 04:47