How to compare max value in each group to current row? - SQL - sql

I want to apply conditional logic based on whether or not my item_no is the highest in its group.
Sample Data:
group_id item_no
oiegown 1
oiegown 2
oiegown 3
wefwefw 1
wefwefw 2
My Approach:
CASE WHEN (
SELECT MAX(item_no)
FROM my_table
GROUP BY group_id
) = item_no
THEN /*logic here*/
My subquery, as desired, retrieves the highest item_no per group.
However, the query does not work as I get the Scalar subquery produced more than one element error. How can I work around this?

Your approach corrected (correlate the subquery to get the maximum for the group ID of the current row only):
CASE WHEN (
SELECT MAX(item_no)
FROM my_table m
WHERE m.group_id = my_table.group_id
) = item_no
THEN /*logic here*/
The alternative with a window function:
CASE WHEN MAX(item_no) OVER (PARTITION BY group_id) = item_no
THEN /*logic here*/

Related

SQL GROUP BY prioritise value based on other column

I have a table something like this:
value
high_priority
grouping
1
TRUE
one
2
FALSE
one
3
FALSE
one
3
FALSE
two
4
FALSE
two
I would like to get the MAX value by grouping unless the entry is high_priority in which case I should prioritise that over the non high_priority entries.
For example, on the above table I want these results:
value
grouping
1
one
4
two
The simple solution of GROUP BY won't account for the high_priority entries:
SELECT
MAX(value) AS value,
grouping
FROM the_table
GROUP BY grouping
How can I extend this to also account for the high_priority entries?
Based on your description, you can use aggregation like this:
select grouping,
coalesce(case when max(high_priority) filter (where high_priority) then id end,
max(id)
) as id
from the_table
group by grouping;
However distinct on might be a simpler solution:
select distinct on (grouping) t.*
from the_table t
order by grouping, high_priority desc;
Alternatively group by grouping, high_priority and row_number the results as needed.
SELECT value, grouping
FROM (
SELECT
MAX(value) AS value,
grouping, high_priority,
row_number() over(partition by grouping order by high_priority desc) rn
FROM the_table
GROUP BY grouping,high_priority
)t
where rn=1
By joining on the same table and using COALESCE this provides the correct results:
SELECT
COALESCE(MAX(high_priorities.value), MAX(the_table.value)) AS value,
the_table.grouping
FROM the_table
LEFT JOIN the_table AS high_priorities
ON the_table.grouping = high_priorities.grouping
AND high_priorities.high_priority = TRUE
GROUP BY the_table.grouping
This works because in the event that any high priority exists, it will pick that. In the event that none exist the first clause of COALESCE will become NULL and it will fall back to the second option.

Oracle SQL how to find count less than avg

my code is like :
SELECT
number,
name,
count(*) as "the number of correct answer"
FROM
table1 NATURAL JOIN table2
WHERE
answer = 'T'
GROUP BY
number,
name
HAVING
count(*) < avg(count(*))
ORDER BY
count(*);
Here I want to find the group with count less than the average number of count for each group, but here I failed to use HAVING or WHERE, could anyone help me?
How can I only select the 1 name1 2 since avg of count is (2+6+7)/3 = 5 and only 2 is less than avg.
number name count
1 name1 2
2 name2 6
3 name3 7
I would advise you to never use natural joins. They obfuscate the query and make the query a maintenance nightmore.
You can use window functions:
SELECT t.*
FROM (SELECT number, name,
COUNT(*) as num_correct,
AVG(COUNT(*)) OVER () as avg_num_correct
FROM table1 JOIN
table2
USING (?). -- be explicit about the column name
WHERE answer = 'T'
GROUP BY number, name
) t
WHERE num_correct < avg_num_correct;
As with your version of the query, this filters out all groups that have no correct answers.
I would place your current query logic into a CTE, and then tag on the average count in the process:
WITH cte AS (
SELECT number, name, COUNT(*) AS cnt,
AVG(COUNT(*)) OVER () AS avg_cnt
FROM table1
NATURAL JOIN table2
WHERE answer = 'T'
GROUP BY number, name
)
SELECT number, name, cnt AS count
FROM cte
WHERE cnt < avg_cnt;
Here we are using the AVG() function as an analytic function, with the window being the entire aggregated table. This means it will find the average of the counts per group, across all groups (after aggregation). Window functions (almost) always evaluate last.

SQL Oracle Find Max of count

I have this table called item:
| PERSON_id | ITEM_id |
|------------------|----------------|
|------CP2---------|-----A03--------|
|------CP2---------|-----A02--------|
|------HB3---------|-----A02--------|
|------BW4---------|-----A01--------|
I need an SQL statement that would output the person with the most Items. Not really sure where to start either.
I advice you to use inner query for this purpose. the inner query is going to include group by and order by statement. and outer query will select the first statement which has the most items.
SELECT * FROM
(
SELECT PERSON_ID, COUNT(*) FROM TABLE1
GROUP BY PERSON_ID
ORDER BY 2 DESC
)
WHERE ROWNUM = 1
here is the fiddler link : http://sqlfiddle.com/#!4/4c4228/5
Locating the maximum of an aggregated column requires more than a single calculation, so here you can use a "common table expression" (cte) to hold the result and then re-use that result in a where clause:
with cte as (
select
person_id
, count(item_id) count_items
from mytable
group by
person_id
)
select
*
from cte
where count_items = (select max(count_items) from cte)
Note, if more than one person shares the same maximum count; more than one row will be returned bu this query.

How to find the first nearest value up and down in SQL?

I have table like this:
When I set id =4 , the results I need are:
Note that I selected the ID=4 and the two nearest values from both sides .
How can write sql code for do it?
You can use LEAD() function , that selects the next value by a specific order , like this:
Note that LEAD was only introduced to SQL-Server 2012+.
SELECT s.id,s.name,s.number
FROM (
SELECT t.*
LEAD(t.id,1) OVER(ORDER BY t.Number DESC) as Next_val,
LEAD(t.id,1) OVER(ORDER BY t.Number) as Last_val
FROM YourTable t) s
WHERE 4 IN(s.id,next_Val,s.last_val)
You can replace 4 with your desired ID or with a parameter .
EDIT: A little explanation - LEAD function provides a way to access the next row, without the use of a SELF JOIN or a sub query , it orders the results by the order you provided inside the OVER() clause, and select the value inside the parentheses - LEAD(value) that belong to the record above the current record that is being processed. So, this query selects each ID , and the ID that belongs to the nearest value up and down , and then check if one of them is your desired ID .
declare #id int = 4
select sn.*
from table sn
inner join
(
select top 2 sn_prev.*
from table sn_prev
where id <= #id
order by id desc
) sp on sp.id= sn.id
union
select sn.*
from table sn
inner join
(
select top 1 sn_prev.*
from table sn_prev
where id > #id
order by id
) sp on sp.id = sn.id
you can use MIN() and MAX() function in sql.

SQL. Is there any efficient way to find second lowest value?

I have the following table:
ItemID Price
1 10
2 20
3 12
4 10
5 11
I need to find the second lowest price. So far, I have a query that works, but i am not sure it is the most efficient query:
select min(price)
from table
where itemid not in
(select itemid
from table
where price=
(select min(price)
from table));
What if I have to find third OR fourth minimum price? I am not even mentioning other attributes and conditions... Is there any more efficient way to do this?
PS: note that minimum is not a unique value. For example, items 1 and 4 are both minimums. Simple ordering won't do.
SELECT MIN( price )
FROM table
WHERE price > ( SELECT MIN( price )
FROM table )
select price from table where price in (
select
distinct price
from
(select t.price,rownumber() over () as rownum from table t) as x
where x.rownum = 2 --or 3, 4, 5, etc
)
Not sure if this would be the fastest, but it would make it easier to select the second, third, etc... Just change the TOP value.
UPDATED
SELECT MIN(price)
FROM table
WHERE price NOT IN (SELECT DISTINCT TOP 1 price FROM table ORDER BY price)
To find out second minimum salary of an employee, you can use following:
select min(salary)
from table
where salary > (select min(salary) from table);
This is a good answer:
SELECT MIN( price )
FROM table
WHERE price > ( SELECT MIN( price )
FROM table )
Make sure when you do this that there is only 1 row in the subquery! (the part in brackets at the end).
For example if you want to use GROUP BY you will have to define even further using:
SELECT MIN( price )
FROM table te1
WHERE price > ( SELECT MIN( price )
FROM table te2 WHERE te1.brand = te2.brand)
GROUP BY brand
Because GROUP BY will give you multiple rows, otherwise you will get the error:
SQL Error [21000]: ERROR: more than one row returned by a subquery used as an expression
I guess a simplest way to do is using offset-fetch filter from standard sql, distinct is not necessary if you don't have repeat values in your column.
select distinct(price) from table
order by price
offset 1 row fetch first 1 row only;
no need to write complex subqueries....
In amazon redshift use limit-fetch instead for ex...
Select distinct(price) from table
order by price
limit 1
offset 1;
You can either use one of the following:-
select min(your_field) from your_table where your_field NOT IN (select distinct TOP 1 your_field from your_table ORDER BY your_field DESC)
OR
select top 1 ColumnName from TableName where ColumnName not in (select top 1 ColumnName from TableName order by ColumnName asc)
I think you can find the second minimum using LIMIT and ORDER BY
select max(price) as minimum from (select distinct(price) from tableName order by price asc limit 2 ) --or 3, 4, 5, etc
if you want to find third or fourth minimum and so on... you can find out by changing minimum number in limit. you can find using this statement.
You can use RANK functions,
it may seem complex query but similar results like other answers can be achieved with the same,
WITH Temp_table AS (SELECT ITEM_ID,PRICE,RANK() OVER (ORDER BY PRICE) AS
Rnk
FROM YOUR_TABLE_NAME)
SELECT ITEM_ID FROM Temp_table
WHERE Rnk=2;
Maybe u can check the min value first and then place a not or greater than the operator. This will eliminate the usage of a subquery but will require a two-step process
select min(price)
from table
where min(price) <> -- "the min price you previously got"