Using a subquery in a where clause to find the second smallest value - sql

I'm trying to find the second smallest value for a list to put it into SSRS as a way to highlight that value. This issue is there are multiple minimum values for a given element. The data is presented such that there is an overarching group A that encompasses smaller groups B and I am wanting the second smallest value for each of the smaller groups.
I have a query set up right now that uses a subquery in the where clause to exclude the minimum value from the search so that the second smallest value will be considered the new minimum value. This seemed to work but the subquery only rules out the minimum value for the larger A group, which may or may not be the minimum value for each B group. Here is my query:
Select
BPosition,
Min(Value) as SecondMinimum
From Table
Where Value > (Select
Min(Value)
From Table
Where APosition = #AName)
and APosition = #AName
Group By BPosition
I was expecting a list of the second smallest values for each B group, but it is pulling in the smallest value in each B group that is greater than the smallest value of the A group. This is right for the one B group that contains the true smallest value but incorrect for the others.

If you want the second largest value, use dense_rank():
Select distinct BPosition, Value as SecondMinimum
From (select t.*,
dense_rank() over (partition by Aposition, Bposition order by value) as seqnum
from table
) t
where seqnum = 2;

Related

How to get first row of 3 specific values of a column using Oracle SQL?

I have a table which has ID, FAMILY, ENV_XML_PATH and CREATED_DATE columns.
ID
FAMILY
ENV_XML_PATH
CREATED_DATE
15826841
CRM
path1.xml
03-09-22 6:50:34AM
15826856
SCM
path3.xml
03-10-22 7:12:20AM
15826786
IC
path4.xml
02-10-22 12:50:52AM
15825965
CRM
path5.xml
02-10-22 1:50:52AM
15653951
null
path6.xml
04-10-22 12:50:52AM
15826840
FIN
path7.xml
03-10-22 2:34:09AM
15826841
SCM
path8.xml
02-10-22 8:40:52AM
15223450
IC
path9.xml
03-09-22 5:34:09AM
15026853
SCM
path10.xml
05-10-22 4:40:59AM
Now there are 18 DISTINCT values in FAMILY column and each value has multiple rows associated (as you can see from the above image).
What I want is to get the first row of 3 specific values (CRM, SCM and IC) in FAMILY column.
Something like this:
ID
FAMILY
ENV_XML_PATH
CREATED_DATE
15826841
CRM
path1.xml
date1
15826856
SCM
path3.xml
date2
15826786
IC
path4.xml
date3
I am new to this, though I understand the logic but I am not sure how to implement it. Kindly help. Thanks.
You can use RANK for that. Something like this:
WITH groupedData AS
(SELECT id, family, env_xml_path, created_date,
RANK () OVER (PARTITION BY family ORDER BY id) AS r_num
FROM yourtable
GROUP BY id, family, env_xml_path, created_date)
SELECT id, family, env_xml_path, created_date
FROM groupedData
WHERE r_num = 1
ORDER BY id;
Thus, within the first query, your data will be grouped by family and sorted by the column you want (in my example, it will be sorted by id).
After that, you will use the second query to only take the first row of each family.
Add a WHERE clause to the first query if you need to apply further restrictions on the result set.
See here a working example: db<>fiddle
You could use a window function to get to know the row number of each partition in family ordered by the created_date, and then filter by the the three families you are interested in:
with row_window as (
select
id,
family,
env_xml_path,
created_date,
row_number() over (partition by family order by created_date asc) as rn
from <your_table>
where family in ('CRM', 'SCM', 'IC')
)
select
id,
family,
env_xml_path,
created_date
from row_window
where rn = 1
Output:
ID
FAMILY
ENV_XML_PATH
CREATED_DATE
15826841
CRM
path1.xml
03-09-22 6:50:34
15826856
SCM
path3.xml
03-10-22 7:12:20
15826786
IC
path4.xml
02-10-22 12:50:52
The question doesn't really specify what 'first' means, but I assume it means the first to be added in the table, aka the person whose date is the oldest. Try this code:
SELECT DISTINCT * FROM (yourTable) WHERE Family = 'CRM' OR
Family = 'SCM' OR Family = 'IC' ORDER BY Created_Date ASC FETCH FIRST (number) ROWS ONLY;
What it does:
Distinct - It selects different rows, which means you won't get same type of rows at the top.
Where - checks if certain condition is true
OR - it means that the select should choose rows that match those requirements. In the current situation the distinct clause means that same rows won't repeat, so you won't be getting 2 different 'CRM' family names, so it will find the first 'CRM' then the first 'SCM' and so on.
ORDER BY - orders the column in specified order. In the current one, if first rows mean the oldest, then by ordering them by date and using ASC the oldest(aka smallest date) will be at the top.
FETCH FIRST (number) ROWS ONLY - It selects only the very first couple of rows you want. For example if you need 3 different 'first' rows you need to get FETCH FIRST 3 ROWS ONLY. Combined with the distinct word it will only show 3 different rows.

Oracle-Complex sql view creation

I have a table like below:
For each disinct combination of ID and VALUE, I have several steps. For example, For the combination of A and B, I have three steps QC, LC and DR and so on for C and D. Now, I want a view like below:
That is, I want a column "OUTPUT" in the view where i have to put the first step after QC for each combination of ID and VALUE. For example, For A and B, first step after QC is LC and so OUTPUT value is LC. For C and D, there is no QC and so OUTPUT value is NA.
Can anyone please help me on this issue.
Thanks in advance.
In SQL, tables are inherently unordered. So, you need a column to specify the ordering. Let me assume that you have such a column, say StepOrder in the table. If so, then you can do what you want using analytic functions.
The lead() in the inner subquery returns the next step. The max() in the next subquery returns the value after QA, and the output max() spreads the value over all rows with the same id and value:
select id, value, step,
coalesce(max(qa_next) over (partition by id, value), 'NA') as "Output"
from (select t.*,
max(case when step = 'QA' then nextstep end) over (partition by id, value) as qa_next
from (select t.*,
lead(step) over (partition by id, value order by StepOrder) as nextStep
from table t
) t
) t

SQL-using CASE in SELECT returns only first case value

I'm using the following query:
SELECT Policy_type_ID,Policy_Value Value,CASE Policy_Value WHEN max(Policy_Value) THEN 'Highest' WHEN min(Policy_Value) THEN 'Lowest' END AS Range
FROM Policy_Types
GROUP BY Policy_type_ID,Policy_Value
HAVING ((Policy_Value IN (SELECT max(Policy_Value)
FROM Policy_Types)) OR (Policy_Value IN(SELECT min(Policy_Value)
FROM Policy_Types)));
But the result has only one value 'Highest' in the column 'Range'.Its only regarding the first case,whichever it maybe, and ignoring the rest.
Policy_type_ID Value Range
501180 990000 Highest
690002 10 Highest
690006 10 Highest
690007 10 Highest
I've no idea where I'm going wrong. Its just that CASE statement that is the problem....any help??
The problem is that your MIN and MAX functions are being calculated within the GROUP BY groups, not across the entire table. You need to calculate them in a separate subquery that doesn't have GROUP BY.
SELECT DISTINCT Policy_type_ID, Policy_Value,
CASE Policy_Value
WHEN MaxPolicy THEN 'Highest'
ELSE 'Lowest'
END Range
FROM Policy_Types
JOIN (SELECT MIN(Policy_Value) MinPolicy, MAX(Policy_Value) MaxPolicy) MinMax
HAVING Policy_Value IN (MinPolicy, MaxPolicy)

Check Sequence in Max Min Values

I have a database table that Stores Maximum and Minimum Price Breaks for a Product.
Does anyone know of the SQL which say if I have a break from one Max to the Min of the next item. E.g. 1-10 12-20 I would like it to return me either the numbers that are missing or at the very least a count or bool if it can detect a break from the Absolute Min and the Absolute Max by going through each range.
SQL Server (MSSQL) 2008
For a database that supports window functions, like Oracle:
SELECT t.*
, CASE LAG(maxq+1, 1, minq) OVER (PARTITION BY prod ORDER BY minq)
WHEN minq
THEN 0
ELSE 1
END AS is_gap
FROM tbl t
;
This will produce is_gap = 1 for a row that forms a gap with the previous row (ordered by minq). If your quantity ranges can overlap, the required logic would need to be provided.
http://sqlfiddle.com/#!4/f609e/4
Something like this, giving max quantities that aren't the overall max for the product and don't have a min quantity following them:
select prev.tbProduct_Id,prev.MaxQuantity
from yourtable prev
left join (select tbProduct_ID, max(MaxQuantity) MaxQuantity from yourtable group by tbProduct_id) maxes
on maxes.tbProduct_ID=prev.tbProduct_Id and maxes.MaxQuantity=prev.MaxQuantity
left join yourtable next
on next.tbProduct_Id=prev.tbProduct_Id and next.MinQuantity=prev.MaxQuantity+1
where maxes.tbProduct_Id is null and next.tbProduct_Id is null;
This would fail on your sample data, though, because it would expect a row with MinQuantity 21, not 20.

from a joined table, select the max value of a column, but if there are multiples maxes, pick the one that has the max value from another column

I need to join another table to my query, and grab the max value of a particular column from that joined table. The problem is that sometimes a user will have multiples of that max (eg: if the max value is 5.1, there is another row that contains that max value as well, so it gives multiple results). I need to know how to have it grab the max, and when there are multiples of that max (and ONLY when there are multiples of that max, so that I still get the results from users that don't have multiple max values), grab the max value from another column, without forgetting the max value that the query initially got.
I've included my current query below that returns the multiple max values. The table I am joining and referring to is APPLICATION_VERSION. I need to grab the max value based off the USER_ACCOUNT_ID (which I get from the EMPLOYEE table) from column VERSION_NUMBER. If there are multiple maxes of VERSION_NUMBER, I want it to choose the max VERSION_NUMBER based on the max VERSION_CHANGE_DATE. Sometimes though there are multiples maxes of the VERSION_CHANGE_DATE also so I would then want it to pick the one with the the max VERSION_CHANGE_DATE and then the APPLICATION_VERSION_ID.
Btw, sorry if I made this more complicated than it needs to be. Just wanted to be thorough. I'd really appreciate any assistance :)
SELECT e.user_account_id,e.employee_id,e.external_id_1,e.external_id_2,e.last_name as LAST,e.first_name as FIRST,e.job_profile_type as rank,e.status_change_date,t.name as TEAM,a.alignment_name as TERRITORY,m.machine_node_id as NODE_ID,a.alignment_id,t.division,av.version_change_date,av.version_number as EI_Version,av.login_date as LAST_LOGIN,m.platform_version
FROM employee e, alignment a, machine_node m, team t, application_version av,
(SELECT av.user_account_id,MAX(av.version_change_date) as maxdate,max(av.application_version_id) as maxversionid
FROM application_version av
GROUP BY av.user_account_id) av2
where e.employee_id = a.employee_id
and av.version_change_date = av2.maxdate
and e.employee_id = m.employee_id
and t.team_id = a.team_id
and e.status = 'ACTV'
and m.status = 'ACTV'
and e.user_account_id=av.user_account_id
and m.machine_type = 'REMO'
and e.external_id_1= 'XM68823'
order by e.last_name asc
If I've understood you correctly, this should do it. I'm unclear why version_number doesn't appear at all in your sample query, but maybe that was a mistake.
Change the inline view to:
(SELECT av.user_account_id,
MAX(av.version_number) KEEP (DENSE_RANK LAST ORDER BY version_number,version_change_date, application_version_id) as maxversion,
MAX(av.version_change_date) KEEP (DENSE_RANK LAST ORDER BY version_number,version_change_date, application_version_id) as maxdate,
max(av.application_version_id) KEEP (DENSE_RANK LAST ORDER BY version_number,version_change_date, application_version_id) as maxversionid
FROM application_version av
GROUP BY av.user_account_id) av2