How to compare rows with each other and keep only one row according to a condition - sql

I have a requirement as per below:
IF More than 1 comment exist (group of name, lastname and door) and one of them includes NULL then keep only the record with the Null comment and discard the others.
IF Null IS NOT one of them and the comment includes NOT AVAILABLE and REQUIRES. Keep Not available - discard REQUIRES
IF all of them have only REQUIRES choose the record with the lowest amount or value.
Name Lastname Comment Amount Door
John R. NULL 250 1
John R. NULL 250 1
John R. New design is available 250 1
John W. Not available 250 2
John W. Requires additional comment 450 2
John S. Requires further explanation 200 3
John S. Requires more information 300 3
Result should come like below:
Name Lastname Comment Amount Door
John R. NULL 250 1
John W. Not available 250 2
John S. Requires further explanation 200 3
I am trying to write a CTE to get the result but not sure how to compare the comment section something like below:
WITH RNs AS(
SELECT name,
lastname,
door,
package,
DENSE_RANK() OVER (PARTITION BY name ORDER BY door ASC) AS RN
FROM test)
SELECT distinct name,
lastname,
door,package,
CASE when package IS NULL THEN 'PASS'
when package like 'Not available%' then 'PASS'
when package like 'requires%' then 'PASS' else 'fail' END AS to_keep_or_not
FROM RNs
GROUP BY RN,
name,
lastname,
door,package;
What would be the best approach to solve this kind of problem statement?

Please try below query:
with cte as (
select name ,lastname,comment,amount,door ,row_number()over(partition by name ,lastname order by comment ,(case when comment like '%Requires%' then amount else 0 end)) rn from testreq
where comment is null or comment like '%Not available%' or comment like '%Requires%')
select Name,lastname,comment,amount,door from cte where rn=1
order by door
Output:
|name|lastname|comment |amount|door|
|----|--------|----------------------------|------|----|
|John|R. |NULL |250 |1 |
|John|W. |Not available |250 |2 |
|John|S. |Requires further explanation|200 |3 |

You want an order by with a case expression . . . along with ROW_NUMBER():
SELECT t.*
FROM (SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY name, lastname
ORDER BY (CASE WHEN comment IS NULL THEN 1
WHEN comment LIKE '%NOT AVAILABLE%' THEN 2
ELSE 3
END),
amount
ORDER BY door ASC
) as seqnum
FROM test t
) t
WHERE seqnum = 1;

Related

Return based on values selected

I really don't know how to phrase the title correctly, so please excuse me if the title is confusing.
Here's the scenario I am facing:
I have a table...assuming it contains the follow rows.
Name | Value
----- | ----
John | 1
Mary | 2
Jack | 3
Jim | 4
Here's the PL/SQL requirement:
If John exists, return John and his value.
If John does not exist, but Mary does, return Mary and her value.
If neither John nor Mary exist, return either Jack or Jim whichever
has the higher value.
I was able to use cursor to traverse the table and test each row, but am wondering if there are other more efficient way.
Thanks!
No need for a cursor and a loop. You can do this in a single query, using a conditional sort and a fetch clause:
select *
from mytable
order by
case name when 'John' then 1 when 'Mary' then 2 else 3 end,
value desc
fetch first row only
Or if you are a pre-12c version of Oracle, where the fetch clause is not available:
select name, value
from (
select t.*,
row_number() over(order by
case name when 'John' then 1 when 'Mary' then 2 else 3 end,
value desc
) rn
from mytable t
) t
where rn = 1

How to query: "for which do these values apply"?

I'm trying to match and align data, or resaid, count occurrences and then list for which values those occurrences occur.
Or, in a question: "How many times does each ID value occur, and for what names?"
For example, with this input
Name ID
-------------
jim 123
jim 234
jim 345
john 123
john 345
jane 234
jane 345
jan 45678
I want the output to be:
count ID name name name
------------------------------------
3 345 jim john jane
2 123 jim john
2 234 jim jane
1 45678 jan
Or similarly, the input could be (noticing that the ID values are not aligned),
jim john jane jan
----------------------------
123 345 234 45678
234 123 345
345
but that seems to complicate things.
As close as I am to the desired results is in SQL, as
for ID, count(ID)
from table
group by (ID)
order by count desc
which outputs
ID count
------------
345 3
123 2
234 2
45678 1
I'll appreciate help.
You seem to want a pivot. In SQL, you have to specify the number of columns in advance (unless you construct the query as a string).
But the idea is:
select ID, count(*) as cnt,
max(case when seqnum = 1 then name end) as name_1,
max(case when seqnum = 2 then name end) as name_2,
max(case when seqnum = 3 then name end) as name_3
from (select t.*,
row_number() over (partition by id order by id) as seqnum -- arbitrary ordering
from table t
) t
group by ID
order by count desc;
If you have an unknown number of columns, you can aggregate the values into an array:
select ID, count(*) as cnt,
array_agg(name order by name) as names
from table t
group by ID
order by count desc
the query would look similar to this if that's what you're looking for.
SELECT
name,
id,
COUNT(id) as count
FROM
dataSet
WHERE
dataSet.name = 'input'
AND dataSet.id = 'input'
GROUP BY
name,
id

select the highest record between two table

I have two table. One table contains graduation records and the second table contains post graduation records. A candidate must have graduation, but it is not necessarily to have post graduation.
My question is to select the post graduation record if the candidate has post graduation else only graduation.
table 1 graduation_table
rollno | degree | division
--------------------------
001 | B.tech | 1st
002 | B.sc | 1st
003 | BA | 1st
table 2 postgraduation_table
rollno | degree | division
--------------------------
002 | M.sc | 1st
the result must be
rollno | degree | division
--------------------------
001 | B.tech | 1st
002 | M.sc | 1st
003 | BA | 1st
You want all rows from graduation_table which do not have a row in postgraduation_table plus those in postgraduation_table. This can be expressed with a not exists and union query:
select gt.rollno, gt.degree, gt.division
from graduation_table gt
where not exists (select *
from postgraduation_table pg
where pg.rollno = gt.rollno)
union all
select rollno, degree, division
from postgraduation_table
order by rollno;
Online example: http://rextester.com/IFCQR67320
select
rollno,
case when p.degree is null then g.degree else p.degree end as degree,
case when p.division is null then g.division else p.division end as division
from
grad g
left join
post p using (rollno)
Or better as suggested in the comments:
select
rollno,
coalesce (p.degree, g.degree) as degree,
coalesce (p.division, g.division) as division
from
grad g
left join
post p using (rollno)
Take a union of both tables, and introduce a position column, to rank the relative importance of the two tables. The postgraduate table has a pos value of 1, and the graduate table has a value of 2. Then, apply ROW_NUMBER() over this union query and assign a row number to each rollno group of records (presumed to be either one or at most two records). Finally, perform one more outer subquery to retain the most important record, postgraduate first, graduate second.
SELECT rollno, degree, division
FROM
(
SELECT
rollno, degree, division,
ROW_NUMBER() OVER (PARTITION BY rollno ORDER BY pos) rn
FROM
(
SELECT p.*, 1 AS pos p FROM postgraduation_table
UNION ALL
SELECT p.*, 2 FROM graduation_table p
) t
) t
WHERE t.rn = 1;
This should make your needs :
SELECT dg.rollno, CASE WHEN pg IS NOT NULL THEN pg.degree ELSE gd.degree END AS degree, dg.division
FROM graduation_table AS dg
LEFT OUTER JOIN postgraduation_table AS pg USING (rollno)
GROUP BY dg.rollno, dg.division;
Hope this help.

Issue with returning distinct records based on single column (Oracle)

If I have the table "members" (shown below), how would I go about getting the record of the first occurrence of a membership_id (Oracle).
Expected results
123 John Doe A P
313 Michael Casey A A
113 Luke Skywalker A P
Table - members
membership_id first_name last_name status type
123 John Doe A P
313 Michael Casey A A
113 Luke Skywalker A P
123 Bob Dole A A
313 Lucas Smith A A
SELECT membership_id,
first_name,
last_name,
status,
type
FROM( SELECT membership_id,
first_name,
last_name,
status,
type,
rank() over (partition by membership_id
order by type desc) rnk
FROM members )
WHERE rnk = 1
will work for your sample data set. If you can have ties-- that is, multiple rows with the same membership_id and the same maximum type-- this query will return all those rows. If you only want to return one of the rows where there is a tie, you would either need to add additional criteria to the order by to ensure that all ties are broken or you would need to use the row_number function rather than rank which will arbitrarily break ties.
Select A.*
FROM Members AS A inner join
(Select membership_id, first(first_name) AS FN, first(last_name) AS LN
From Members
Group by membership_id) AS B
ON A.membership_id=B.membership_id and A.first_name=B.FN and A.last_name=B.LN
Hope that helps!
select *
from members
where rowid in (
select min(rowid)
from members
group by membership_id
)

SQL Server select first instance of ranked data

I have a query that creates a result set like this:
Rank Name
1 Fred
1 John
2 Mary
2 Fred
2 Betty
3 John
4 Betty
4 Frank
I need to then select the lowest rank for each name, e.g.:
Rank Name
1 Fred
1 John
2 Mary
2 Betty
4 Frank
Can this be done within TSQL?
SELECT MIN(Rank) AS Rank, Name
FROM TableName
GROUP BY Name
yes
select name, min(rank)
from nameTable
group by name
As Paul + Kevin have pointed out, simple cases of returning a value from an aggregate can be extracted using MIN / MAX etc (just note that RANK is a reserved word)
In a more general / complicated case, e.g. where you need to find the second / Nth highest rank, you can use PARTITIONs with ROW_NUMBER() to do ranking and then filter by the rank.
SELECT [Rank], [Name]
FROM
(
SELECT [RANK], [Name],
ROW_NUMBER() OVER (PARTITION BY [Name] ORDER BY [Rank]) as [RowRank]
FROM [MyTable]
) AS [MyTableReRanked]
WHERE [RowRank] = #N
ORDER BY [Rank];