Delete first rows after qualified ones - sql

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.)

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)
);

Select Last Record Based on few criteria

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.

SQL SERVER How to select the latest record in each group? [duplicate]

This question already has answers here:
Get top 1 row of each group
(19 answers)
Closed 2 years ago.
| ID | TimeStamp | Item |
|----|-----------|------|
| 1 | 0:00:20 | 0 |
| 1 | 0:00:40 | 1 |
| 1 | 0:01:00 | 1 |
| 2 | 0:01:20 | 1 |
| 2 | 0:01:40 | 0 |
| 2 | 0:02:00 | 1 |
| 3 | 0:02:20 | 1 |
| 3 | 0:02:40 | 1 |
| 3 | 0:03:00 | 0 |
I have this and I would like to turn it into
| ID | TimeStamp | Item |
|----|-----------|------|
| 1 | 0:01:00 | 1 |
| 2 | 0:02:00 | 1 |
| 3 | 0:03:00 | 0 |
Please advise, thank you!
A correlated subquery is often the fastest method:
select t.*
from t
where t.timestamp = (select max(t2.timestamp)
from t t2
where t2.id = t.id
);
For this, you want an index on (id, timestamp).
You can also use row_number():
select t.*
from (select t.*,
row_number() over (partition by id order by timestamp desc) as seqnum
from t
) t
where seqnum = 1;
This is typically a wee bit slower because it needs to assign the row number to every row, even those not being returned.
You need to group by id, and filter out through timestamp values descending in order to have all the records returning as first(with value 1) in the subquery with contribution of an analytic function :
SELECT *
FROM
(
SELECT *,
DENSE_RANK() OVER (PARTITION BY ID ORDER BY TimeStamp DESC) AS dr
FROM t
) t
WHERE t.dr = 1
where DENSE_RANK() analytic function is used in order to include records with ties also.

SQL how to remove duplicate records

How can I clean up a table by removing the duplicate records?
+----------+--------+------------+
| clientID | status | Insertdate |
+----------+--------+------------+
| 1 | new | 20191206 |
| 1 | new | 20191206 |
| 2 | old | 20191206 |
| 2 | old | 20191206 |
| 3 | new | 20191205 |
| 3 | new | 20191205 |
+----------+--------+------------+
I don't have any identity field.
Please find the below query. You can use Row Number.
;WITH cte as (
select clientid
, status, Insertdate
, ROW_NUMBER() over (partition by clientid, status, Insertdate order by clientid) RowNumber
from Yourtable
)
delete from cte where RowNumber > 1
Hope this will help if you are running MySQL database
SELECT clientID, status, Insertdate, count(*)
FROM table_name
GROUP BY clientID, status, Insertdate
having count(*) > 1

Rolling up remaining rows into one called "Other"

I have written a query which selects lets say 10 rows for this example.
+-----------+------------+
| STORENAME | COMPLAINTS |
+-----------+------------+
| Store1 | 4 |
| Store7 | 2 |
| Store8 | 1 |
| Store9 | 1 |
| Store2 | 1 |
| Store3 | 1 |
| Store4 | 1 |
| Store5 | 0 |
| Store6 | 0 |
| Store10 | 0 |
+-----------+------------+
How would I go about displaying the TOP 3 rows BUT Having the remaining rows roll up into a row called "other", and it adds all of their Complaints together?
So like this for example:
+-----------+------------+
| STORENAME | COMPLAINTS |
+-----------+------------+
| Store1 | 4 |
| Store7 | 2 |
| Store8 | 1 |
| Other | 4 |
+-----------+------------+
So what has happened above, is it displays the top3 then adds the complaints of the remaining rows into a row called other
I have exhausted all my resources and cannot find a solution. Please let me know if this makes sense.
I have created a SQLfiddle of the above tables that you can edit if it is possible :)
Here's hoping this is possible :)
Thanks,
Mike
Something like this may work
select *, row_number() over (order by complaints desc) as sno
into #temp
from
(
SELECT
a.StoreName
,COUNT(b.StoreID) AS [Complaints]
FROM Stores a
LEFT JOIN
(
SELECT
StoreName
,Complaint
,StoreID
FROM Complaints
WHERE Complaint = 'yes') b on b.StoreID = a.StoreID
GROUP BY a.StoreName
) as t ORDER BY [Complaints] DESC
select storename,complaints from #temp where sno<4
union all
select 'other',sum(complaints) as complaints from #temp where sno>=4
I do this with double aggregation and row_number():
select (case when seqnum <= 3 then storename else 'Other' end) as StoreName,
sum(numcomplaints) as numcomplaints
from (select c.storename, count(*) as numcomplaints,
row_number() over (order by count(*) desc) as seqnum
from complaints c
where c.complaint = 'Yes'
group by c.storename
) s
group by (case when seqnum <= 3 then storename else 'Other' end) ;
From what I can see, you don't really need any additional information from stores, so this version just leaves that table out.