How to get the first non-NULL value in SQL? - sql

I have three columns of data. One column has ID and the second column has a date and the third column has BMI value. I want to create a fourth column that has the first_value based on date(ascending order) from the third column which is not null or avoiding the null.
So far, I have tried first_value in plain form which didn't work. I tried subsetting first_value inside the case when statement as
CASE
WHEN BMI IS NOT NULL THEN (FIRST_VALUE(BMI) OVER PARTITION BY PID ORDER BY DATE))
ELSE 0
END AS FIRSTNOTNULLVALUE_BMI
gave me 0s.
id date BMI
1 2000-01-01 NULL
1 2003-05-01 18.1
1 2002-07-15 25.8
2 2009-09-25 NULL
2 2015-04-18 23.5
Any suggestions??

You can put that CASE in the ORDER BY of the FIRST_VALUE.
Then the null's will be sorted last for that function.
create table test
(
pid int,
pdate date,
BMI decimal(4,1)
)
insert into test (pid, pdate, BMI) values
(1, '2000-01-01', NULL)
, (1, '2003-05-01', 18.5)
, (1, '2002-07-15', 24.9)
, (2, '2009-09-25', NULL)
, (2, '2015-04-18', 21.7)
;
select *
, first_value(BMI) over (partition by pid order by case when BMI is not null then 1 else 2 end, date(pdate)) as firstBMI
from test
order by pid, pdate
pid | pdate | BMI | firstBMI
:-- | :--------- | :--- | :-------
1 | 2000-01-01 | null | 24.9
1 | 2002-07-15 | 24.9 | 24.9
1 | 2003-05-01 | 18.5 | 24.9
2 | 2009-09-25 | null | 21.7
2 | 2015-04-18 | 21.7 | 21.7
db<>fiddle here

You could join your table with a subquery that recovers the first non-null BMI, date-wise:
select
t.*,
x.bmi first_non_null_bmi
from mytable t
cross join (select bmi from mytable where bmi is not null order by date limit 1) x

I think you can do something like this.. maybe will work.. or gives you any idea
THEN (select BIM = row_number() over (partition by BIM order by DATE desc/asc)
from Products)
ELSE..

You could just use a subquery:
select bmi.*,
(select bmi2.bmi
from bmi bmi2
where bmi2.id = bmi.id and bmi2.bmi is not null
order by bmi2.date
limit 1
) as first_bmi
from bmi;

Related

How to partition and find the most latest value in SQL

I have a table as follows:
ID | col1 | Date Time
1 | WA | 2/11/20
1 | CI | 1/11/20
2 | CI | 2/11/20
2 | WA | 3/11/20
3 | WA | 2/10/20
3 | WA | 1/11/20
3 | WA | 2/11/20
4 | WA | 1/10/20
4 | CI | 2/10/20
4 | SA | 3/10/20
I want to find all ID values for which col1 had some other value in addition to WA as well and the most latest value in col1 should be 'WA'. i.e. from the sample data above , only ID values 1 & 2 should be returned. Because both of those have an additional value (i.e., CI) in additon to WA, but still the most latest value for them is WA.
How do I get that??
FYI, there could be some IDs that don't have WA value at all. I want to eliminate them. Also those that only have WA value, I want to eliminate those as well.
Thanks for the help.
You can use window functions for this:
select distinct id
from (
select
t.*,
last_value(col1) over(partition by id oder by datetime) last_col1,
min(col1) over(partition by id) min_col1,
max(col1) over(partition by id) max_col1
from mytable t
) t
where last_col1 = 'WA' and min_col1 <> max_col1
The inner query uses last_value() to recover the last value of col1 for the given id, and computes the min and max values in the same partition.
Then, the outer query filters on ids whose last value is 'WA' and that have at least two distinct values (which is phrased as the inequality of the min and max value).
You can do this with aggregation:
select id
from t
group by id
having min(col1) <> max(col1) and -- at least two different values
max(case when col1 = 'WA' then datetime end) = max(datetime) -- last is WA

How to select rows and nearby rows with specific conditions

I have a table (Trans) of values like
OrderID (unique) | CustID | OrderDate| TimeSinceLast|
------------------------------------------------------
123a | A01 | 20.06.18 | 20 |
123y | B05 | 20.06.18 | 31 |
113k | A01 | 18.05.18 | NULL | <------- need this
168x | C01 | 17.04.18 | 8 |
999y | B05 | 15.04.18 | NULL | <------- need this
188k | A01 | 15.04.18 | 123 |
678a | B05 | 16.03.18 | 45 |
What I need is to select the rows where TimeSinceLast is null, as well as a row preceding and following where TimeSinceLast is not null, grouped by custID
I'd need my final table to look like:
OrderID (unique) | CustID | OrderDate| TimeSinceLast|
------------------------------------------------------
123a | A01 | 20.06.18 | 20 |
113k | A01 | 18.05.18 | NULL |
188k | A01 | 15.04.18 | 123 |
123y | B05 | 20.06.18 | 31 |
999y | B05 | 15.04.18 | NULL |
678a | B05 | 16.03.18 | 45 |
The main problem is that TimeSinceLast is not reliable and for whatsoever reason does not calculate well the days since last order, so I cannot use it in a query for preceding or following row.
I have tried to look for codes and found something like this on this forum
with dt as
(select distinct custID, OrderID,
max (case when timeSinceLast is null then OrderID end)
over(partition by custID order by OrderDate
rows between 1 preceding and 1 following) as NullID
from Trans)
select *
from dt
where request_id between NullID -1 and NullID+1
But does not work well for my purposes. Also it looks like max function cannot work with missing values.
Many thanks
Use lead() and lag().
What I need is to select the rows where TimeSinceLast is null, as well as a row preceding and following where TimeSinceLast is not null.
First, the ordering is a little unclear. Your sample data and code do not match. The following assumes some combination of the date and orderid, but there may be other columns that better capture what you mean by "preceding" and "following".
This is a little tricky, because you don't want to always include the first and last rows -- unless necessary. So, look at two columns:
select t.*
from (select t.*,
lead(TimeSinceLast) over (partition by custid order by orderdate, orderid) as next_tsl,
lag(TimeSinceLast) over (partition by custid order by orderdate, orderid) as prev_tsl,
lead(orderid) over (partition by custid order by orderdate, orderid) as next_orderid,
lag(orderid) over (partition by custid order by orderdate, orderid) as prev_orderid
from t
) t
where TimeSinceLast is not null or
(next_tsl is null and next_orderid is not null) or
(prev_tsl is null and prev_orderid is not null);
USE APPLY
DECLARE #TransTable TABLE (OrderID char(4), CustID char(3), OrderDate date, TimeSinceLast int)
INSERT #TransTable VALUES
('123a', 'A01', '06.20.2018', 20),
('123y', 'B05', '06.20.2018' ,31),
('113k', 'A01', '05.18.2018' ,NULL), ------- need this
('168x', 'C01', '04.17.2018' ,8),
('999y', 'B05', '04.15.2018' ,NULL), ------- need this
('188k', 'A01', '04.15.2018' ,123),
('678a', 'B05', '03.16.2018' ,45)
SELECT B.OrderID, B.CustID, B.OrderDate, B.TimeSinceLast
FROM #TransTable A
CROSS APPLY (
SELECT 0 AS rn, A.OrderID, A.CustID, A.OrderDate, A.TimeSinceLast
UNION ALL
SELECT TOP 2 ROW_NUMBER() OVER (PARTITION BY CASE WHEN T.OrderDate > A.OrderDate THEN 1 ELSE 0 END ORDER BY ABS(DATEDIFF(day, T.OrderDate, A.OrderDate))) rn,
T.OrderID, T.CustID, T.OrderDate, T.TimeSinceLast
FROM #TransTable T
WHERE T.CustID = A.CustID AND T.OrderID <> A.OrderID
ORDER BY rn
) B
WHERE A.TimeSinceLast IS NULL
ORDER BY B.CustID, B.OrderDate DESC

Filter table : Keep N row after each row with special value

I have a table with a huge amount of data with this structure (simplidied) :
+--------+-------------------------+-------+
| id | datetime | type |
+--------+-------------------------+-------+
| 1 | 2015-08-13 17:50:41 | 1 |
| 2 | 2015-08-13 17:50:45 | 0 |
| 3 | 2015-08-14 17:50:56 | 0 |
| 4 | 2015-08-14 17:50:59 | 0 |
+--------+-------------------------+-------+
Row with type=1 are followed by a lots of rows with type=0
I need to do an intelligent clean :
I want to keep rows with type=0 following rows with type=1 only during one hour (After the type 1 row timestamp)
And at least one row with type=0 per hour
I don't know if its possible to do that with a query, or if I will have to loop through all rows with a script.
I use PostgreSQL
I dont have postgres here to test, but this should return all of the data you want to keep:
SELECT ID FROM (
SELECT ID FROM (SELECT
id,
datetime,
type,
LAG(type) OVER (ORDER BY id asc) AS prev_type,
LAG(datetime) OVER (ORDER BY id asc) AS prev_date
FROM employees
WHERE
type=1 AND
prev_type=0 AND
EXTRACT(EPOCH FROM (datetime - prev_date)) < 3601
)
UNION
SELECT MAX(ID) FROM employees GROUP BY TO_CHAR(datetime, 'DDMMYYYHH24'))

Can row_number() ignore null in oracle

I have data like this
---------------------------
| code | other column
---------------------------
| C | a
| null | a
| A | a
| null | a
| null | a
----------------------------
How can i write query to get row_number without counting null column.
----------------------------------
| id | code | other column |
----------------------------------
| 1 | C | a
| | null | a
| 2 | A | a
| | null | a
| | null | a
----------------------------------
Well, not specifically. But you can get what you want by using conditional logic:
select (case when code is not null
then row_number() over (partition by (case when code is not null then 1 else 0 end)
order by . . .
)
end) as id
It is not clear to me what the order by is for the row_number() which is what the . . . means.
If you need to order on code (descendent in your example) with NULLs last:
select
decode(code,null,null,row_number() over (order by code DESC NULLS LAST)) rn,
code
from test;
If you need to order on OTHER column:
select
decode(code,null,null,row_number() over (order by decode(code,null,null,'x') NULLS LAST, other DESC)) rn,
code, other
from test;
You can use row_number on the desired subset and then union all the other records:
select row_number() over (order by sortkey) as id, code, other_column
from mytable
where code is not null
union all
select null as id, code, other_column
from mytable
where code is null
order by sortkey;
Another easy way would be:
Select
CASE WHEN code IS NOT NULL THEN ROW_NUMBER() OVER (PARTITION BY code order by code)
ELSE NULL END id,
code,
other_column
FROM table;
Example, in my case using IS NOT NULL did not work for me, I had to change it to an expression:
SELECT A.ITEMNAME,
CASE
WHEN (SELECT T1.MAXSTOCK
FROM DATOS T1
WHERE T1.MAXSTOCK) > 5 THEN
ROW_NUMBER() OVER(PARTITION BY CASE
WHEN (SELECT T1.MAXSTOCK
FROM DATOS T1
WHERE T1.MAXSTOCK) <= 5 /*here should go IS NOT NULL*/
THEN
1
END ORDER BY A.ITEMNAME)
ELSE
NULL
END AS #ROW
FROM TABLE A

Mysql4: SQL for selecting one or zero record

Table layout:
CREATE TABLE t_order (id INT, custId INT, order DATE)
I'm looking for a SQL command to select a maximum of one row per order (the customer who owns the order is identified by a field named custId).
I want to select ONE of the customer's orders (doesn't matter which one, say sorted by id) if there is no order date given for any of the rows.
I want to retrieve an empty Resultset for the customerId, if there is already a record with given order date.
Here is an example. Per customer there should be one order at most (one without a date given). Orders that have already a date value should not appear at all.
+---------------------------------------------------------+
|id | custId | date |
+---------------------------------------------------------+
| 1 10 NULL |
| 2 11 2008-11-11 |
| 3 12 2008-10-23 |
| 4 11 NULL |
| 5 13 NULL |
| 6 13 NULL |
+---------------------------------------------------------+
|
|
| Result
\ | /
\ /
+---------------------------------------------------------+
|id | custId | date |
+---------------------------------------------------------+
| 1 10 NULL |
| |
| |
| |
| 5 13 NULL |
| |
+---------------------------------------------------------+
powered be JavE
Edit:
I've choosen glavić's answer as the correct one, because it provides
the correct result with slightly modified data:
+---------------------------------------------------------+
|id | custId | date |
+---------------------------------------------------------+
| 1 10 NULL |
| 2 11 2008-11-11 |
| 3 12 2008-10-23 |
| 4 11 NULL |
| 5 13 NULL |
| 6 13 NULL |
| 7 11 NULL |
+---------------------------------------------------------+
Sfossen's answer will not work when customers appear more than twice because of its where clause constraint a.id != b.id.
Quassnoi's answer does not work for me, as I run server version 4.0.24 which yields the following error:
alt text http://img25.imageshack.us/img25/8186/picture1vyj.png
For a specific customer it's:
SELECT *
FROM t_order
WHERE date IS NULL AND custId=? LIMIT 1
For all customers its:
SELECT a.*
FROM t_order a
LEFT JOIN t_order b ON a.custId=b.custID and a.id != b.id
WHERE a.date IS NULL AND b.date IS NULL
GROUP BY custId;
Try this:
SELECT to1.*
FROM t_order AS to1
WHERE
to1.date IS NULL AND
to1.custId NOT IN (
SELECT to2.custId
FROM t_order AS to2
WHERE to2.date IS NOT NULL
GROUP BY to2.custId
)
GROUP BY to1.custId
For MySQL 4:
SELECT to1.*
FROM t_order AS to1
LEFT JOIN t_order AS to2 ON
to2.custId = to1.custId AND
to2.date IS NOT NULL
WHERE
to1.date IS NULL AND
to2.id IS NULL
GROUP BY to1.custId
This query will use one pass over index on custId.
For each distinct custId it will use one subquery over same index.
No GROUP BY, no TEMPORARY and no FILESORT — efficient, if your table is large.
SELECT VERSION()
--------
'4.1.22-standard'
CREATE INDEX ix_order_cust_id ON t_order(custId)
SELECT id, custId, order_date
FROM (
SELECT o.*,
CASE
WHEN custId <> #c THEN
(
SELECT 1
FROM t_order oi
WHERE oi.custId = o.custId
AND order_date IS NOT NULL
LIMIT 1
)
END AS n,
#c <> custId AS f,
#c := custId
FROM
(
SELECT #c := -1
) r,
t_order o
ORDER BY custId
) oo
WHERE n IS NULL AND f
---------
1, 10, ''
5, 13, ''
First filter out rows with dates, then filter out any row that has a similar row with a lower id. This should work because the matching record with the least id is unique if id is unique.
select * from t_order o1
where date is null
and not exists (select * from t_order o2
where o2.date is null
and o1.custId = o2.custId
and o1.id > o2.id)