Using Min/Max with conditional operator - sql

I am trying to run a query to find max and min values, and then use a conditional operator. However, when I try to run the following query, it gives me the error - "misuse of aggregate: min()".
My query is:
SELECT a.prim_id, min(b.new_len*36) as min_new_len, max(b.new_len*36) as max_new_len
FROM tb_first a, tb_second b
WHERE a.sec_id = b.sec_id AND min_new_len > 1900 AND max_new_len < 75000
GROUP BY a.prim_id
ORDER BY avg(b.new_len*36);
Any suggestions?

You need to use the HAVING clause to filter by expressions containing aggregates.
If you are using MySQL you can use the column aliases in that clause for other RDBMSs you can't.
SELECT a.prim_id,
min(b.new_len * 36) as min_new_len,
max(b.new_len * 36) as max_new_len
FROM tb_first a
JOIN tb_second b
ON a.sec_id = b.sec_id /*<-- Use explicit JOIN syntax...*/
GROUP BY a.prim_id
HAVING min(b.new_len * 36) > 1900
AND max(b.new_len * 36) < 75000
ORDER BY avg(b.new_len * 36);
In many RDBMSs you can also put the query into an inline view and select from that to use the column aliases instead of repeating the formulae. In that case you do use WHERE as below.
SELECT prim_id,
min_new_len,
max_new_len
from (SELECT a.prim_id,
min(b.new_len * 36) as min_new_len,
max(b.new_len * 36) as max_new_len,
avg(b.new_len * 36) as avg_new_len
FROM tb_first a
JOIN tb_second b
ON a.sec_id = b.sec_id
GROUP BY a.prim_id) derived
WHERE min_new_len > 1900
AND max_new_len < 75000
ORDER BY avg_new_len;

WHERE clauses filter the individual 'input' records prior to aggregation.
HAVING clauses filter the resulting 'output' records after the aggregation.
Giving the answer posted by Martin.

Related

SQL Sum with calculated column

hello I have an SQL query that selects a SUM value in the first column and I want the second column to be ( first_column / another_field )
select sum(a.amount), (sum(a.amount) / d.total_loading_weight * 1000) as MarginPerTon
from tra_affair a join tra_Delivery d on a.delivery=d.delivery_id
where a.delivery='394179' and a.is_margin='1'
I am getting the "not a single-group group function" error
The problem is: d.total_loading_weight. I'm not sure what you want, but something like:
select sum(a.amount),
(sum(a.amount) / sum(d.total_loading_weight * 1000)) as MarginPerTon
from tra_affair a join
tra_Delivery d
on a.delivery = d.delivery_id
where a.delivery = '394179' and a.is_margin = '1';
You can also use min() or max() instead of sum() if the values are always the same.

PostgreSQL use case when result in where clause

I use complex CASE WHEN for selecting values. I would like to use this result in WHERE clause, but Postgres says column 'd' does not exists.
SELECT id, name, case when complex_with_subqueries_and_multiple_when END AS d
FROM table t WHERE d IS NOT NULL
LIMIT 100, OFFSET 100;
Then I thought I can use it like this:
select * from (
SELECT id, name, case when complex_with_subqueries_and_multiple_when END AS d
FROM table t
LIMIT 100, OFFSET 100) t
WHERE d IS NOT NULL;
But now I am not getting a 100 rows as result. Probably (I am not sure) I could use LIMIT and OFFSET outside select case statement (where WHERE statement is), but I think (I am not sure why) this would be a performance hit.
Case returns array or null. What is the best/fastest way to exclude some rows if result of case statement is null? I need 100 rows (or less if not exists - of course). I am using Postgres 9.4.
Edited:
SELECT count(*) OVER() AS count, t.id, t.size, t.price, t.location, t.user_id, p.city, t.price_type, ht.value as houses_type_value, ST_X(t.coordinates) as x, ST_Y(t.coordinates) AS y,
CASE WHEN t.classification='public' THEN
ARRAY[(SELECT i.filename FROM table_images i WHERE i.table_id=t.id ORDER BY i.weight ASC LIMIT 1), t.description]
WHEN t.classification='protected' THEN
ARRAY[(SELECT i.filename FROM table_images i WHERE i.table_id=t.id ORDER BY i.weight ASC LIMIT 1), t.description]
WHEN t.id IN (SELECT rl.table_id FROM table_private_list rl WHERE rl.owner_id=t.user_id AND rl.user_id=41026) THEN
ARRAY[(SELECT i.filename FROM table_images i WHERE i.table_id=t.id ORDER BY i.weight ASC LIMIT 1), t.description]
ELSE null
END AS main_image_description
FROM table t LEFT JOIN table_modes m ON m.id = t.mode_id
LEFT JOIN table_types y ON y.id = t.type_id
LEFT JOIN post_codes p ON p.id = t.post_code_id
LEFT JOIN table_houses_types ht on ht.id = t.houses_type_id
WHERE datetime_sold IS NULL AND datetime_deleted IS NULL AND t.published=true AND coordinates IS NOT NULL AND coordinates && ST_MakeEnvelope(17.831490030182, 44.404640972306, 12.151558389557, 47.837396630872) AND main_image_description IS NOT NULL
GROUP BY t.id, m.value, y.value, p.city, ht.value ORDER BY t.id LIMIT 100 OFFSET 0
To use the CASE WHEN result in the WHERE clause you need to wrap it up in a subquery like you did, or in a view.
SELECT * FROM (
SELECT id, name, CASE
WHEN name = 'foo' THEN true
WHEN name = 'bar' THEN false
ELSE NULL
END AS c
FROM case_in_where
) t WHERE c IS NOT NULL
With a table containing 1, 'foo', 2, 'bar', 3, 'baz' this will return records 1 & 2. I don't know how long this SQL Fiddle will persist, but here is an example: http://sqlfiddle.com/#!15/1d3b4/3 . Also see https://stackoverflow.com/a/7950920/101151
Your limit is returning less than 100 rows if those 100 rows starting at offset 100 contain records for which d evaluates to NULL. I don't know how to limit the subselect without including your limiting logic (your case statements) re-written to work inside the where clause.
WHERE ... AND (
t.classification='public' OR t.classification='protected'
OR t.id IN (SELECT rl.table_id ... rl.user_id=41026))
The way you write it will be different and it may be annoying to keep the CASE logic in sync with the WHERE limiting statements, but it would allow your limits to work only on matching data.

Why colums in SELECT not belongs to SELECT

I have this select, but does not work.
select
a.code1,
a.data1,
a.stval,
(select sum(col1+col2+col3) from tad ) as sum1,
(select sum(col7+col8+col9) from tbac) as sum2,
CASE
WHEN (sum1+sum2) > 100 THEN (a.stval * sum1)
WHEN (sum1+sum2( <= 100 THEN (a.stval * sum2)
END as newdat1
from arti as a
Where is the error? why (sum1+sum2) its error?
Thanks
(sum1 + sum2) is an error because these identifiers are not defined in the scope where you are trying to use them. In an SQL select list, you cannot use symbols declared in the same select list, irrespective of their position on the list. Use a subquery if you need to access sum1 and sum2.
The specific reason is that SQL is a descriptive language that does not guarantee the order of evaluation of expressions. This is true in the select clause. This is true in the where clause. It is true in the from clause. SQL describes what the results look like. It does not prescribe the specific actions.
As a result, SQL does not allow identifiers defined in the select to be used in the same select clause (nor in the where clause at the same level). The expressions can be processed in any order.
The normal solution in your case is to use a subquery or a CTE. In your case, though, the subqueries are independent of the outer query (as written), so I would move them to the from clause:
select a.code1, a.data1, a.stval, x1.sum1, x2.sum2,
(CASE WHEN x1.sum1 + x2.sum2 > 100 THEN a.stval * x1.sum1
WHEN x1.sum1 + x2.sum2 <= 100 THEN a.stval * x2.sum2
END) as newdat1
from arti a cross join
(select sum(col1+col2+col3) as sum1 from tad ) x1 cross join
(select sum(col7+col8+col9) as sum2 from tbac) x2;
EDIT:
You can use a subquery or CTE. But there is an approach that builds on the above:
select a.code1, a.data1, a.stval, x1.sum1, x2.sum2,
(CASE WHEN x1.sum1 + x2.sum2 > 100 THEN a.stval * x1.sum1
WHEN x1.sum1 + x2.sum2 <= 100 THEN a.stval * x2.sum2
END) as newdat1
from arti a join
(select ascon, sum(col1+col2+col3) as sum1
from tad
group by ascon
) x1
on x1.ascon = arti.code1 cross join
(select sum(col7+col8+col9) as sum2 from tbac) x2;

Using created column in select statement twice

I have a big problem with this query in SQL.
select distinct
b.*,
case
when b.Cash > b2.Cash
then ((b.Cash - b2.Cash) / b.Cash) * 100
end as Increased,
('Cash Increased by' + convert(VARCHAR(20), Increased))) as
Case
from
Accounting b
join
(…
In select statement I created column Increased. Then I want to created another column Case with the following value Cash Increased by… (value from Increased column).
My question is how can I do it in one select statement?
You have two options
Use this query as a subquery and do the concatenation in the outer query
You have to copy-paste the CASE..WHEN into the concatenations
Subquery
SELECT
*
, ('Cash Increased by' + convert(VARCHAR(20), Increased))) AS CASE
FROM (
SELECT DISTINCT
b.*
, CASE
WHEN b.Cash > b2.Cash THEN ((b.Cash - b2.Cash) / b.Cash) * 100
END AS Increased
FROM
Accounting b JOIN (...)
) SubQuery
Copy the CASE part
SELECT DISTINCT
b.*
, CASE
WHEN b.Cash > b2.Cash THEN ((b.Cash - b2.Cash) / b.Cash) * 100
END AS Increased
, (
'Cash Increased by' + CONVERT(VARCHAR(20),
CASE
WHEN b.Cash > b2.Cash THEN ((b.Cash - b2.Cash) / b.Cash) * 100
END)
) AS CASE
FROM
Accounting b JOIN (...)
NOTE
Do not forget to escape (or change) the alias for the concatenation. The CASE is a reserved word in most DMBS!
NOTE 2 Next time please mention the DBMS you are using!

SQL: Using COUNT(*) Instead of EXISTS

Is it possible to use COUNT in place of EXISTS?
I have following query:
SELECT *
FROM Goals G
WHERE EXISTS (SELECT NULL FROM tfv_home_last6(G.Date, G.Home) WHERE GameNumber <= 6 AND
HomeGoals >= 3)
Instead of returning the row if at least one row exists in the subquery, I'd like to specify a number of rows that need to be returned in the subquery, something like
SELECT *
FROM Goals G
WHERE ROWCOUNT(*) >= 2 (SELECT NULL FROM tfv_home_last6(G.Date, G.Home) WHERE GameNumber <= 6 AND
HomeGoals >= 3)
I'm not sure how to go about it?
I'm using SQL Server 2012.
You can do the subquery pretty much just like you describe:
SELECT *
FROM Goals G
WHERE (SELECT count(*)
FROM tfv_home_last6(G.Date, G.Home)
WHERE GameNumber <= 6 AND HomeGoals >= 3
) > 0;
However, this requires calculating the entire count. The exists form is more efficient, because it stops at the first matching record.
In SQL Server 2012, you could also use `cross apply:
SELECT *
FROM Goals G cross apply
(select count(*) as cnt
FROM tfv_home_last6(G.Date, G.Home)
WHERE GameNumber <= 6 AND HomeGoals >= 3
) a
WHERE a.cnt > 0;
I do not know which would have better performance, the correlated subquery in the where clause or the
cross apply version.