SQL get first non value based on status - sql

I'm not sure if the title is correct but here's my question. I have a table like this:
+----+--------+--------------+---------+------------+
| id | city | province | status | date |
+----+--------+--------------+---------+------------+
| 1 | cainta | rizal | failed | 01/01/2020 |
| 1 | null | null | success | 02/01/2020 |
| 1 | cainta | rizal | failed | 03/01/2020 |
| 2 | pasig | metro manila | failed | 04/01/2020 |
| 2 | pasig | metro manila | failed | 05/01/2020 |
| 2 | null | null | success | 06/01/2020 |
| 3 | obando | bulacan | failed | 07/01/2020 |
| 3 | null | null | failed | 08/01/2020 |
| 3 | obando | bulacan | success | 09/01/2020 |
+----+--------+--------------+---------+------------+
Now I need to get all transactions with status='success'. If I do that the output will be like this:
| id | city | province | status | date |
|------|--------|------------|----------|------------|
| 1 | nan | nan | success | 02/01/2020 |
| 2 | nan | nan | success | 06/01/2020 |
| 3 | obando | bulacan | success | 09/01/2020 |
What I need is this:
| id | city | province | status | date |
|------|--------|--------------|----------|------------|
| 1 | cainta | rizal | success | 02/01/2020 |
| 2 | pasig | metro manila | success | 06/01/2020 |
| 3 | obando | bulacan | success | 09/01/2020 |
Hopefully someone can shed some light on how to tackle this kind of situation.

Try the following using lag()
with cte as
(
select
*,
lag(city) over (order by id) as ncity,
lag(province) over (order by id) as nprovince
from myTable
)
select
id,
coalesce(city, ncity) as city,
coalesce(province, nprovince) as province,
status,
date
from cte
where status = 'success';
output:
| id | city | province | status | date |
| --- | ------ | ------------ | ------- | ---------- |
| 1 | cainta | rizal | success | 02/01/2020 |
| 2 | pasig | metro manila | success | 06/01/2020 |
| 3 | obando | bulacan | success | 09/01/2020 |

Perhaps a window function can help:
SELECT id, city, province, status, date
FROM (SELECT id,
max(city) OVER w AS city,
max(province) OVER w AS province,
status,
date
FROM atable
WINDOW w AS (PARTITION BY id)) AS q
WHERE status = 'success';

You can use the analytical functions here.
SELECT * FROM
(SELECT T.ID, T.CITY, T.PROVINCE,
MAX(CASE WHEN STATUS = 'success' THEN DATE END)
OVER (PARTITION BY ID ORDER BY DATE) AS DATE,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY DATE) AS RN,
SUM(CASE WHEN STATUS = 'success' THEN 1 ELSE 0 END)
OVER (PARTITION BY ID) AS CNT
FROM YOUR_TABLE T)
WHERE RN = 1 AND CNT > 0
As you have changed the sample data, You can use the GROUP BY as follows:
SELECT ID, MAX(CITY) AS CITY, MAX(PROVINCE) AS PROVINCE,
MAX(CASE WHEN STATUS = 'success' THEN DATE END) AS DATE
FROM YOUR_TABLE
GROUP BY ID
HAVING SUM(CASE WHEN STATUS = 'success' THEN 1 END) > 0

If you want just one row per id, you can use aggregation:
select id, max(city) as city, max(province) as province,
max(date) filter (where status = 'success') as date
from t
group by id
having count(*) filter (where status = 'success') > 0;
Note that if you can have multiple success dates per id, you can put the on the same row using array_agg():
array_agg(date) filter (where status = 'success') as dates

Related

How to move all non-null values to the top of my column?

I have the following data in my table:
| Id | lIST_1 |
----------------------
| 1 | NULL |
| 2 | JASON |
| 3 | NULL |
| 4 | BANDORAN |
| 5 | NULL |
| 6 | NULL |
| 7 | SMITH |
| 8 | NULL |
How can I write a query to get the output below?
| Id | lIST_1
-----------------------
| 1 | JASON |
| 2 | BANDORAN |
| 3 | SMITH |
| 4 | NULL |
| 5 | NULL |
| 6 | NULL |
| 7 | NULL |
| 8 | NULL |
You can use order by:
select row_number() over (order by (select null)) as id, t.list_1
from t
order by (case when list_1 is not null then 1 else 2 end)
It is unclear why you would want id to change values, but you can use row_number() for that.
EDIT:
If you want to change the id, then you can do:
with toupdate as (
select row_number() over (order by (case when list_id is not null then 1 else 2 end), id
) as new_id,
t.*
from t
)
update toupdate
set id = new_id
where id <> new_id; -- no need to update if the value remains the same

How to get starting value and end value of particular transcation

input :
+------+------------+--------------+------------+
| NAME | Date | Amount_start | Amount_END |
+------+------------+--------------+------------+
| AAA | 2016-10-06 | 20 | 4 |
| AAA | 2016-10-07 | 30 | 6 |
| AAA | 2016-10-08 | 7 | 8 |
| AAA | 2016-10-09 | 380 | 9 |
| ... | ... | ... | ... |
| ZZZ | 2016-10-06 | 10 | 20 |
| ZZZ | 2016-10-07 | 11 | 6 |
+------+------------+--------------+------------+
output:
+------+--------------+------------+
| NAME | Amount_start | Amount_END |
+------+--------------+------------+
| AAA | 20 | 9 |
| ... | ... | ... |
| ZZZ | 10 | 6 |
+------+--------------+------------+
output : In one row need to take Amount_start of starting date and
Amount_end of End date of that employee
Just use row_number() and conditional aggregation:
select t.name,
max(case when seqnum_a = 1 then amount_start end) as amount_start,
max(case when seqnum_d = 1 then amount_end end) as amount_end
from (select t.*,
row_number() over (partition by name order by date asc) as seqnum_a,
row_number() over (partition by name order by date desc) as seqnum_d
from t
) t
group by name;

SQL Select Day IN and Day OUT grouped by ID's

How to GROUP EIDs by dates where Date between 2014-01-15 and 2014-03-18
| ID |EID | DATE | Status | |
|----------|--------------|---------|-----|
| 9 |9991 | 2014-03-16 | OUT | |
| 8 |9997 | 2014-03-18 | IN | |
| 7 |9997 | 2014-03-16 | OUT | |
| 6 |9999 | 2014-02-16 | IN | |
| 5 |9999 | 2014-02-16 | OUT | |
| 4 |9996 | 2014-03-18 | IN | |
| 3 |9996 | 2014-03-16 | OUT | |
| 2 |9997 | 2014-01-18 | IN | |
| 1 |9997 | 2014-01-15 | OUT | |
Output should be like:
|
|EID |in date | OUT date| DAYS OUT |
|------|--------------|--------- |------ ----|
| 9997 | 2014-03-18 | 2014-03-16| 2 |
| 9997 | 2014-01-18 | 2014-01-15| 3 |
| 9999 | 2014-02-16 | 2014-02-16| 0 |
| 9996 | 2014-03-18 | 2014-03-16| 2 |
| 9991 | | 2014-03-16| |
Thank you
Here is one method that assumes that they are interleaved, so no two ins or outs are together:
select eid,
max(case when status = 'in' then date end) as in_date,
max(case when status = 'out' then date end) as out_date,
datediff(day,
max(case when status = 'in' then date end),
max(case when status = 'out' then date end)
) as days_diff
from (select t.*, row_number() over (partition by eid, status order by date) as seqnum
from t
) t
group by eid, seqnum;
I think that you have already done it but, have you tried to do the sentence like:
SELECT [here you format as you wish] FROM [your table] WHERE date BETWEEN '2014-01-15' AND '2014-03-18' GROUP BY date
or
SELECT [here you format as you wish] FROM [your table] WHERE dateIn >= '2014-01-15' AND dateOut <= '2014-03-18' GROUP BY dateIn
Can you share your full table?

how to select max of row number data in a table in sql?

I have data like this,
| ID | Client | Some_Value | Row_No |
| 1 | HP | 123 | 1 |
| 1 | HP | 1245 | 2 |
| 1 | Dell | 123445 | 3 |
| 2 | HP | 111 | 1 |
| 2 | HP | 223 | 2 |
| 3 | Dell | 34 | 1 |
| 3 | Dell | 5563 | 2 |
And i need output like this ,
| ID | Client | Some_Value | Row_No |
| 1 | Dell | 123445 | 3 |
| 2 | HP | 223 | 2 |
| 3 | Dell | 5563 | 2 |
Please consider that I'm a beginner and explain me the logic.
USE Row_NUMBER() and Partition BY:
;With T AS
(
SELECT
ID,
Client,
Some_Value,
Row_No,
Row_NUMBER() OVER(Partition BY ID Order BY Row_No Desc) AS PartNo
FROM TableName
)
SELECT
ID,
Client,
Some_Value,
Row_No
FROM T
WHERE T.PartNo=1
Update Statement Example:
;With T AS
(
SELECT
ID,
Client,
Some_Value,
Row_No,
Row_NUMBER() OVER(Partition BY ID Order BY Row_No Desc) AS PartNo
FROM TableName
)
Update TableName
SET Name=T.Name
FROM T
WHERE T.PartNo=1
AND TableName.Id=T.Id

Show only one record, if value same in another column SQL

I have a table with 5 columns like this:
| ID | NAME | PO_NUMBER | DATE | STATS |
| 1 | Jhon | 160101-001 | 2016-01-01 | 7 |
| 2 | Jhon | 160101-002 | 2016-01-01 | 7 |
| 3 | Jhon | 160102-001 | 2016-01-02 | 7 |
| 4 | Jane | 160101-001 | 2016-01-01 | 7 |
| 5 | Jane | 160102-001 | 2016-01-02 | 7 |
| 6 | Jane | 160102-002 | 2016-01-02 | 7 |
| 7 | Jane | 160102-003 | 2016-01-02 | 7 |
I need to display all values, but stats fields without duplicate according from date field.
Like this
| ID | NAME | PO_NUMBER | DATE | STATS |
| 1 | Jhon | 160101-001 | 2016-01-01 | 7 |
| 2 | Jhon | 160101-002 | 2016-01-01 | null |
| 3 | Jhon | 160102-001 | 2016-01-02 | 7 |
| 4 | Jane | 160101-001 | 2016-01-01 | 7 |
| 5 | Jane | 160102-001 | 2016-01-02 | 7 |
| 6 | Jane | 160102-002 | 2016-01-02 | null |
| 7 | Jane | 160102-003 | 2016-01-02 | null |
I've had trouble getting the hoped. Thanks
From your sample data, it appears you only want to show the stats for po_number ending with 001. If so, this should be the easiest approach:
select id, name, po_number, date,
case when right(po_number, 3) = '001' then stats else null end as stats
from yourtable
If instead you want to order by the po_number, then here's one option using row_number:
select id, name, po_number, date,
case when rn = 1 then stats else null end as stats
from (
select *, row_number() over (partition by name, date order by po_number) as rn
from yourtable
) t
SQL Fiddle Demo
since you are using SQL 2012, you can use the LEAD() or LAG() window function to compare the DATE value
select *,
STATS = case when t.DATE = LAG(DATE) OVER(ORDER BY ID)
then NULL
else STATS
end
from yourtable t
Use below code
;with temp as (
select id,name ,PO_NUMBER ,DATE, STATS,
LAG (STATS, 1, 0)
OVER (PARTITION BY name ,PO_NUMBER ,DATE ORDER BY id) AS PrevSTATS
from tableName
)
select id,name ,PO_NUMBER ,DATE,
case when STATS = PrevSTATS then null
else STATS end as STATS
from temp