Select Last Record Based on few criteria - sql

Before
+--------+--------+---------+-------+------+
| RowNum | Status | Remarks | SetNo | |
+--------+--------+---------+-------+------+
| 1 | Q | | Set 1 | Want |
| 2 | Q | | Set 1 | Want |
| 3 | Q | | Set 1 | Want |
| 4 | Q | | Set 1 | Want |
| 5 | W | | Set 1 | Want |
| 1 | W | abc | Set 2 | |
| 2 | W | abc | Set 2 | |
| 3 | W | abc | Set 2 | |
| 4 | W | abc | Set 2 | Want |
| 1 | Q | | Set 3 | Want |
| 2 | w | abc | Set 3 | |
| 3 | w | abc | Set 3 | Want |
+--------+--------+---------+-------+------+
How to select Status=Q and Status=W based on Rownum=lastnumber and setno? Expectation result is the row with "want" is what i need. Those empty, will be remove
Tried:
select *
from mytable
where (RowNum != (select max(RowNum) from mytable) and status = 'W')

I understand that for each setno, you want all "Q"s and the latest "W". If so, you can use window functions like that:
select *
from (
select t.*,
row_number() over(partition by setno, status order by rownum desc) rn
from mytable t
) t
where rn = 1 or status = 'Q'

You might want to look into Window Functions. I don't fully understand what you need to do but I would suggest something like:
with rowNumberedData as
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY SetNo ORDER BY RowNum DESC) as RowOrder
FROM mytable
)
SELECT *
FROM rowNumberedData
WHERE (Status = 'Q' OR Status = 'W') AND RowOrder = 1
What this will do is add RowOrder column to your data and its value will be 1, for the max RowNum in every set. You can read more here and here to check what the with syntax is if you are unfamiliar.

This query should return the correct rows
with t_cte as (
select t.*,
row_number() over(partition by setno order by rownum desc) rn
from testTable t)
select *
from t_cte
where [status] = 'Q'
or ([status] = 'W'
and rn = 1);

I understand the question and wanting the last row for each set and then all rows with q. One method uses row_number():
select t.*
from (select t.*,
row_number() over (partition by setno order by rownum desc) as seqnum
from mytable t
) t
where seqnum = 1 or status = 'Q';
There are other ways to express this:
select t.*
from mytable t
where t.status = 'Q' or
t.rownum = (select max(t2.rownum)
from mytable t2
where t2.setno = t.setno
);
This is similar to the approach you are trying.

Related

Get some values from the table by selecting

I have a table:
| id | Number |Address
| -----| ------------|-----------
| 1 | 0 | NULL
| 1 | 1 | NULL
| 1 | 2 | 50
| 1 | 3 | NULL
| 2 | 0 | 10
| 3 | 1 | 30
| 3 | 2 | 20
| 3 | 3 | 20
| 4 | 0 | 75
| 4 | 1 | 22
| 4 | 2 | 30
| 5 | 0 | NULL
I need to get: the NUMBER of the last ADDRESS change for each ID.
I wrote this select:
select dh.id, dh.number from table dh where dh =
(select max(min(t.history)) from table t where t.id = dh.id group by t.address)
But this select not correctly handling the case when the address first changed, and then changed to the previous value. For example id=1: group by return:
| Number |
| -------- |
| NULL |
| 50 |
I have been thinking about this select for several days, and I will be happy to receive any help.
You can do this using row_number() -- twice:
select t.id, min(number)
from (select t.*,
row_number() over (partition by id order by number desc) as seqnum1,
row_number() over (partition by id, address order by number desc) as seqnum2
from t
) t
where seqnum1 = seqnum2
group by id;
What this does is enumerate the rows by number in descending order:
Once per id.
Once per id and address.
These values are the same only when the value is 1, which is the most recent address in the data. Then aggregation pulls back the earliest row in this group.
I answered my question myself, if anyone needs it, my solution:
select * from table dh1 where dh1.number = (
select max(x.number)
from (
select
dh2.id, dh2.number, dh2.address, lag(dh2.address) over(order by dh2.number asc) as prev
from table dh2 where dh1.id=dh2.id
) x
where NVL(x.address, 0) <> NVL(x.prev, 0)
);

How to list the latest series with no gaps of a given clause?

Given the following example table:
+-----------+
| Id | Name |
+----+------+
| 1 | A |
| 2 | B |
| 3 | B |
| 4 | C |
| 5 | A |
| 6 | B |
| 7 | B |
| 8 | B |
| 9 | B |
| 10 | X |
+----+------+
I would like a query to get the following result:
+----+------+
| 6 | B |
| 7 | B |
| 8 | B |
| 9 | B |
+----+------+
The best query I could do was:
SELECT * FROM
(SELECT id, name, LEAD(id) OVER (ORDER BY id) t
FROM test WHERE name = 'B' ORDER BY id)
WHERE ID <> t-1;
sqlfiddle here
If you want the length and where it starts:
select min(id), max(id)
from (select t.*,
row_number() over (order by id) as seqnum,
row_number() over (partition by name order by id) as seqnum_1
from test t
) t
where name = 'B'
group by (seqnum - seqnum_1)
order by min(id) desc
fetch first 1 row only;
You can join back to the table to get the original rows.
Another method using window functions to count the number of non-Bs after a given row . . . and then choose the first:
select t.*
from (select t.*,
dense_rank() over (order by nonbs_after asc) as grp
from (select t.*,
sum(case when name <> 'B' then 1 else 0 end) over (order by id desc) as nonbs_after
from test t
) t
where name = 'B'
) t
where grp = 1;
Here is a db<>fiddle.

Delete first rows after qualified ones

Let's suppose that I have the following table called Orders:
---------------------------------
| OrderId | Status | CustomerId |
---------------------------------
| 1 | + | 2 |
---------------------------------
| 2 | - | 1 |
---------------------------------
| 3 | + | 2 |
---------------------------------
| 4 | + | 1 |
---------------------------------
| 5 | - | 3 |
---------------------------------
| 6 | + | 4 |
---------------------------------
| 7 | + | 3 |
---------------------------------
The question is how I can delete the next order after cancelled one for each customer? I basically want to delete order with id = 4, 7.
So the result should be:
---------------------------------
| OrderId | Status | CustomerId |
---------------------------------
| 1 | + | 2 |
---------------------------------
| 2 | - | 1 |
---------------------------------
| 3 | + | 2 |
---------------------------------
| 5 | - | 3 |
---------------------------------
| 6 | + | 4 |
---------------------------------
I use SQL Server, but I'm realy curious about writing it using ANSI SQL.
You can get the last cancelled order for each customer. Then delete the orders after that:
with todelete as (
select t.*,
min(case when status = '-' then orderid end) over
(partition by customerid) as deleted_orderid
from table t
)
delete from todelete
where orderid > deleted_orderid;
EDIT:
To delete just the next one, let's use row_number():
with todelete as (
select t.*, min(case when orderid > deleted_orderid then orderid end) over
(partition by customerid) as orderid_to_delete
from (select t.*,
min(case when status = '-' then orderid end) over
(partition by customerid) as deleted_orderid
from table t
) t
)
delete from todelete
where orderid = orderid_to_delete;
EDIT II:
If you want to delete the next order after any delete, the query is a bit simpler:
with todelete as (
select t.*, lag(status) over (partition by customerid order by orderid) as prev_status
from table t
)
delete from todelete
where prev_status = '-';
This is ANSI SQL. If you are using SQL Server 2008, you need to use a correlated subquery or maybe cross apply (I'm not 100% sure that cross apply will work in a delete CTE, but it should.)

SELECT only latest record of an ID from given rows

I have this table shown below...How do I select only the latest data of the id based on changeno?
+----+--------------+------------+--------+
| id | data | changeno | |
+----+--------------+------------+--------+
| 1 | Yes | 1 | |
| 2 | Yes | 2 | |
| 2 | Maybe | 3 | |
| 3 | Yes | 4 | |
| 3 | Yes | 5 | |
| 3 | No | 6 | |
| 4 | No | 7 | |
| 5 | Maybe | 8 | |
| 5 | Yes | 9 | |
+----+---------+------------+-------------+
I would want this result...
+----+--------------+------------+--------+
| id | data | changeno | |
+----+--------------+------------+--------+
| 1 | Yes | 1 | |
| 2 | Maybe | 3 | |
| 3 | No | 6 | |
| 4 | No | 7 | |
| 5 | Yes | 9 | |
+----+---------+------------+-------------+
I currently have this SQL statement...
SELECT id, data, MAX(changeno) as changeno FROM Table1 GROUP BY id;
and clearly it doesn't return what I want. This should return an error because of the aggrerate function. If I added fields under the GROUP BY clause it works but it doesn't return what I want. The SQL statement is by far the closest I could think of. I'd appreciate it if anybody could help me on this. Thank you in advance :)
This is typically referred to as the "greatest-n-per-group" problem. One way to solve this in SQL Server 2005 and higher is to use a CTE with a calculated ROW_NUMBER() based on the grouping of the id column, and sorting those by largest changeno first:
;WITH cte AS
(
SELECT id, data, changeno,
rn = ROW_NUMBER() OVER (PARTITION BY id ORDER BY changeno DESC)
FROM dbo.Table1
)
SELECT id, data, changeno
FROM cte
WHERE rn = 1
ORDER BY id;
You want to use row_number() for this:
select id, data, changeno
from (SELECT t.*,
row_number() over (partition by id order by changeno desc) as seqnum
FROM Table1 t
) t
where seqnum = 1;
Not a well formed or performance optimized query but for small tasks it works fine.
SELECT * FROM TEST
WHERE changeno IN (SELECT MAX(changeno)
FROM TEST
GROUP BY id)
for other alternatives :
DECLARE #Table1 TABLE
(
id INT, data VARCHAR(5), changeno INT
);
INSERT INTO #Table1
SELECT 1,'Yes',1
UNION ALL
SELECT 2,'Yes',2
UNION ALL
SELECT 2,'Maybe',3
UNION ALL
SELECT 3,'Yes',4
UNION ALL
SELECT 3,'Yes',5
UNION ALL
SELECT 3,'No',6
UNION ALL
SELECT 4,'No',7
UNION ALL
SELECT 5,'Maybe',8
UNION ALL
SELECT 5,'Yes',9
SELECT Y.id, Y.data, Y.changeno
FROM #Table1 Y
INNER JOIN (
SELECT id, changeno = MAX(changeno)
FROM #Table1
GROUP BY id
) X ON X.id = Y.id
WHERE X.changeno = Y.changeno
ORDER BY Y.id

2 listagg in one SQL Select in Oracle

I have a table in the form of :
| ID | COURSE | PASS |
---------------------------
| 1 | 1 | 1 |
| 1 | 2 | 1 |
| 1 | 3 | 1 |
| 1 | 4 | 0 |
| 1 | 5 | 0 |
and I want row in the form:
| ID | FAILED | PASSED |
---------------------------
| 1 | 4,5 | 1,2,3 |
the only i figured is something like this:
select NVL(passed.id, failed.id), passed.test, failed.test from
(select id, listagg(course, ',') within group (order by course) test from table1 where pass = 1 group by id ) passed
full outer join
(select id, listagg(course, ',') within group (order by course) test from table1 where pass = 0 group by id ) failed
on passed.id = failed.id
is there a way to do it in a single query ?
Try
select id,
listagg(case when pass = 1 then course end, ',') within group (order by course) passed,
listagg(case when pass = 0 then course end, ',') within group (order by course) failed
from table1
group by id
Here is a sqlfiddle demo