max latest null value sql - sql

I am experiencing the following problem. I have a table in this table I same history. Due to an error, I'm interested in finding the following information.
The latest record for a user where column1 value is null and the modifiedon date is the newest for this user. The problem is the table contains more records where the modifiedon is not null for this user and mutated after the date I'm looking for.
Can someone please point me in the right direction?
Sample data:
personid FreeField01 ModifiedOn
1 0004998 15-10-2019 11:48:19
1 NULL 20-10-2019 01:53:39
1 0004998 22-10-2019 14:58:44
1 0004998 22-10-2019 14:58:44
1 NULL 23-10-2019 07:52:46
1 0004998 23-10-2019 17:16:45
So for this user, I'm not interested in any record and should be excluded from the result because the modified on datetime should be before 29-10 and before that date the freefield01 value should be null and modifiedon should be the latest.

Three conditions:
There is no newer entry for the person.
The entry value is NULL.
The date is before 2019-10-29.
The query:
select *
from mytable
where not exists
(
select *
from mytable newer
where newer.personid = mytable.personid
and newer.modifiedon > mytable.modifiedon
)
and freefield01 is null
and modifiedon < date '2019-10-29'
order by personid;

You can use this below script-
WITH CTE
AS(
SELECT personid,MAX(ModifiedOn) MD
FROM your_table
GROUP BY personid
HAVING MAX(ModifiedOn) < '30-10-2019'
)
SELECT * FROM your_table A
INNER JOIN CTE B ON A.personid = B.personid
AND A.ModifiedOn = B.MD
AND A.FreeField01 IS NULL
DEMO HERE

If I understand correctly, you are looking for persons where the FreeField01 has a value of NULL as of a certain date.
Here is one method:
select t.*
from t
where t.ModifiedOn = (select max(t2.ModifiedOn)
from t t2
where t2.personid = t.personid and
t2.ModifiedOn <= '2019-10-29'
) and
t.FreeField01 is null;
EDIT:
Based on your comment, you might just want an aggregation and having:
select personid
from t
where t.ModifiedOn <= '2019-10-29'
group by person_id
having sum(case when t.FreeField01 is null then 1 else 0 end) = 0

The simplest query that I found might be the following if I understand your request well :
SELECT t.personid, t.FreeField01, MAX(ModifiedOn) FROM test t
GROUP BY personid
HAVING MAX(ModifiedOn) < '29-10-2019' AND FreeField01 IS NULL
SEE EXAMPLE HERE
EDIT : Following below suggestions you can use this query instead :
SELECT t1.personid, t1.FreeField01, t1.ModifiedOn
FROM test t1
JOIN (
SELECT t.personid, MAX(ModifiedOn) AS MaxModifiedOn FROM test t
GROUP BY personid
HAVING MAX(ModifiedOn) < STR_TO_DATE('29-10-2019','%d-%m-%Y')
) t2 ON (t1.personid = t2.personid AND t1.ModifiedOn = t2.MaxModifiedOn)
WHERE FreeField01 IS NULL
SEE NEW DEMO HERE

Related

SQL Server : find dates between two separate row entries

I am trying to pull a list of AccountNumbers that have 2 specific charges (by code) that are at least 2 days apart. These are the columns of my table:
AccountNumber
ServiceDate
Code
Example: if there is AccountNumber for Code = 33967 on ServiceDate 12/11/2018 and an AccountNumber for Code = 33968 on ServiceDate 12/15/2018, the AccountNumber will be output to the results window because these two instances show up on DIFFERENT ServiceDates and are at least 2 days apart.
Example 2: if there is an AccountNumber for Code = 33967 on ServiceDate 12/11/2018 and an AccountNumber for Code = 33968 on ServiceDate 12/11/2018, the AccountNumber will NOT be output to the results window because these two instances show up on the same ServiceDate.
Example 3: if there is an AccountNumber for Code = 33967 on ServiceDate 12/11/2018 and an AccountNumber for Code = 33968 on ServiceDate 12/12/2018, the AccountNumber will NOT be output to the results window because there are no dates between the two ServiceDate's. However if it were 12/11 and 12/13 it would be acceptable because there is a day in-between.
I am only concerned about Code 33967 and 33968, all other codes should not be considered. Right now, I am able to pull all Accounts with both these codes on file but cannot figure out how to go further. Any ideas?
My code is as follows:
SELECT AccountNumber, ServiceDate
FROM dbo.table
WHERE Code = '33968'
INTERSECT
SELECT AccountNumber, ServiceDate
FROM dbo.table
WHERE Code = '33967'
here you go with some sample data. Feel free to add some more rows and test it up
create table #Temp_table
(
AccountNumber int null
, ServiceDate date null
,Code int null
)
insert into #Temp_table values
(1,'12/11/2018',33967)
,(2,'12/15/2018',33968)
,(3,'12/11/2018',33967)
,(4,'12/12/2018',33968)
,(5,'12/17/2018',33968)
,(6,'12/16/2018',33967)
;with CTE_MinDate as (
select --Code ,
MinServiceDate = min(Servicedate)
from #Temp_table
--group by Code
)
--select * from CTE_MinDate
select *
from (
select *
,Days_Diff = datediff(day,MinServiceDate, Servicedate)
from (
select a.*
,MinServiceDate = (select MinServiceDate from CTE_MinDate)
from #Temp_table a
where a.Code in ( 33967,33968)
) a
) b where Days_Diff >= 2
Does this do what you want?
SELECT DISTINCT t.AccountNumber
FROM dbo.table t
WHERE t.Code = '33968' AND
EXISTS (SELECT 1
FROM dbo.table t2
WHERE t2.AccountNumber = t.AccountNumber AND
t2.Code = '33967' AND
t2.ServiceDate <> t.ServiceDate
);
If it's only the Account Number you want then how about:
SELECT AccountNumber
FROM dbo.[Table]
GROUP BY AccountNumber
HAVING COUNT(CASE CODE WHEN 33968 THEN 1 END) > 0
AND COUNT(CASE CODE WHEN 33967 THEN 1 END) > 0;

How to use max in with clause to get a single value and use this value in main query with null check

I am unable to write this, please help. Below will give an idea of what I'm trying to achieve.
WITH monthly_data AS
(SELECT MAX(some_date) latest_dt FROM monthly_data
)
SELECT SUM(data)
FROM daily_data
WHERE (monthly_data.latest_dt IS NULL
OR daily_data.some_date > monthly_data.latest_dt)
table: monthly_data
id some_date
007 08-MAY-2018
table: daily_data
some_date data
07-MAY-2018 1
08-MAY-2018 1
09-MAY-2018 1
Expected result
Case 1: 1 row exist in table monthly_data.
Query should return 1.
Case 2: No rows exist in table montly_data.
Query should return 3.
The joins in the above query is incorrect but basically written to give you an idea of what I'm trying to do. Also, when I say no rows exist in table monthly_data, it is simplified explanation. There are other conditions in the actual query that filter out the data.
This has to go in a procedure
Edit
Thanks to #D-Shih I'm in a much better position where I started by using the exist clause query that he has provided.
On performance terms, can we write it in a faster way? Something that can evaluate to below would be fastest I believe
WITH CTE AS
( SELECT MAX(some_date) latest_dt FROM monthly_data
)
SELECT SUM(d.some_data)
FROM daily_data d
WHERE (d.some_date > '08-MAY-2018'
OR '08-MAY-2018' IS NULL)
If I understand correct.I think this will be work.
Due to you didn't provide some sample data and expect result.If that didn't your expect result,you can provide some sample data and expect result,I will edit my answer.
WITH CTE AS (
SELECT Max(some_date) latest_dt
FROM monthly_data
)
SELECT Sum(d.data)
FROM daily_data d
WHERE Exists (
SELECT 1
FROM CTE c
WHERE
d.some_date > c.latest_dt
OR
c.latest_dt IS NULL
)
Edit
You can try use CTE table JOIN on daily_data table
WITH CTE AS (
SELECT Max(some_date) latest_dt
FROM monthly_data
)
SELECT SUM(d.data)
FROM CTE c JOIN daily_data d
ON d.some_date > c.latest_dt OR c.latest_dt IS NULL;
sqlfiddle: http://sqlfiddle.com/#!4/33c64e/28
TRY THIS:
SELECT CASE WHEN SUM(CASE WHEN md.Sdate IS NOT NULL THEN 1 ELSE 0 END) > 0 THEN
SUM(CASE WHEN md.Sdate IS NOT NULL THEN 1 ELSE 0 END)
ELSE
SUM(CASE WHEN md.Sdate IS NULL THEN 1 ELSE 0 END)
END cnt
FROM daily_data dd
LEFT JOIN monthly_data md ON md.Sdate = dd.Sdate
....... {other conditions}

Check whether an employee is present on three consecutive days

I have a table called tbl_A with the following schema:
After insert, I have the following data in tbl_A:
Now the question is how to write a query for the following scenario:
Put (1) in front of any employee who was present three days consecutively
Put (0) in front of employee who was not present three days consecutively
The output screen shoot:
I think we should use case statement, but I am not able to check three consecutive days from date. I hope I am helped in this
Thank you
select name, case when max(cons_days) >= 3 then 1 else 0 end as presence
from (
select name, count(*) as cons_days
from tbl_A, (values (0),(1),(2)) as a(dd)
group by name, adate + dd
)x
group by name
With a self-join on name and available = 'Y', we create an inner table with different combinations of dates for a given name and take a count of those entries in which the dates of the two instances of the table are less than 2 units apart i.e. for each value of a date adate, it will check for entries with its own value adate as well as adate + 1 and adate + 2. If all 3 entries are present, the count will be 3 and you will have a flag with value 1 for such names(this is done in the outer query). Try the below query:
SELECT Z.NAME,
CASE WHEN Z.CONSEQ_AVAIL >= 3 THEN 1 ELSE 0 END AS YOUR_FLAG
FROM
(
SELECT A.NAME,
SUM(CASE WHEN B.ADATE >= A.ADATE AND B.ADATE <= A.ADATE + 2 THEN 1 ELSE 0 END) AS CONSEQ_AVAIL
FROM
TABL_A A INNER JOIN TABL_A B
ON A.NAME = B.NAME AND A.AVAILABLE = 'Y' AND B.AVAILABLE = 'Y'
GROUP BY A.NAME
) Z;
Due to the complexity of the problem, I have not been able to test it out. If something is really wrong, please let me know and I will be happy to take down my answer.
--Below is My Approch
select Name,
Case WHen Max_Count>=3 Then 1 else 0 end as Presence
from
(
Select Name,MAx(Coun) as Max_Count
from
(
select Name, (count(*) over (partition by Name,Ref_Date)) as Coun from
(
select Name,adate + row_number() over (partition by Name order by Adate desc) as Ref_Date
from temp
where available='Y'
)
) group by Name
);
select name as employee , case when sum(diff) > =3 then 1 else 0 end as presence
from
(select id, name, Available,Adate, lead(Adate,1) over(order by name) as lead,
case when datediff(day, Adate,lead(Adate,1) over(order by name)) = 1 then 1 else 0 end as diff
from table_A
where Available = 'Y') A
group by name;

Replace NULL with values

Here is my challenge:
I have a log table which every time a record is changed adds a new record but puts a NULL value for each non-changed value in each record. In other words only the changed value is set, the rest unchanged fields in each row simply has a NULL value.
Now I would like to replace each NULL value with the value above it that is NOT a NULL value like below:
Source table: Task_log
ID Owner Status Flag
1 Bob Registrar T
2 Sue NULL NULL
3 NULL NULL F
4 Frank Admission T
5 NULL NULL F
6 NULL NULL T
Desired output table: Task_log
ID Owner Status Flag
1 Bob Registrar T
2 Sue Registrar T
3 Sue Registrar F
4 Frank Admission T
5 Frank Admission F
6 Frank Admission T
How do I write a query which will generate the desired output table?
One the new windowed function of SQLServer 2012 is FIRST_VALUE, wich have quite a direct name, it can be partitioned through the OVER clause, before using it is necessary to divide every column in data block, a block for a column begin when a value is found.
With Block As (
Select ID
, Owner
, OBlockID = SUM(Case When Owner Is Null Then 0 Else 1 End)
OVER (ORDER BY ID)
, Status
, SBlockID = SUM(Case When Status Is Null Then 0 Else 1 End)
OVER (ORDER BY ID)
, Flag
, FBlockID = SUM(Case When Flag Is Null Then 0 Else 1 End)
OVER (ORDER BY ID)
From Task_log
)
Select ID
, Owner = FIRST_VALUE(Owner) OVER (PARTITION BY OBlockID ORDER BY ID)
, Status = FIRST_VALUE(Status) OVER (PARTITION BY SBlockID ORDER BY ID)
, Flag = FIRST_VALUE(Flag) OVER (PARTITION BY FBlockID ORDER BY ID)
FROM Block
SQLFiddle demo
The UPDATE query is easily derived
As I mentioned in my comment, I would try to fix the process that is creating the records rather than fixing the junk data. If that is not an option, the code below should get you pointed in the right direction.
UPDATE t1
set t1.owner = COALESCE(t1.owner, t2.owner),
t1.Status = COALESCE(t1.status, t2.status),
t1.Flag = COALESCE(t1.flag, t2.flag)
FROM Task_log as t1
INNER JOIN Task_log as t2
ON t1.id = (t1.id + 1)
where t1.owner is null
OR t1.status is null
OR t1.flag is null
I can think of several approaches.
You could use a combination of COALESCE with an array aggregate function. Unfortunately it doesn't look like SQL Server supports array_agg natively (although some nice people have developed some workarounds).
You could also use a subselect for each column.
SELECT id,
(SELECT TOP 1 FROM (SELECT owner FROM ... WHERE id = outer_id AND owner IS NOT NULL order by ID desc )) AS owner,
-- other columns
You could probably do something with window functions, too.
A vanilla solution would be:
select id
, owner
, coalesce(owner, ( select owner from t t2
where id = (select max(id) from t t3
where id < t1.id and owner is not null))
) as new_owner
, flag
, coalesce(flag, ( select flag from t t2
where id = (select max(id) from t t3
where id < t1.id and flag is not null))
) as new_flag
from t t1
Rather inefficient, but should work on most DBMS

PostgreSQL query with conditional empty values depending on preceding rows

I am working on a postgresql query that i am not sure how to produce the output.
Lets say i have a sql query whose output i want is
name date visit_number visit
x 2011-01-01 123 ?? (value i want=1)
y 2011-01-01 123 ?? (value i want=empty)
a 2011-02-02 345 ?? (value i want=1)
b 2011-02-02 345 ?? (empty)
c 2011-02-02 345 ?? (empty)
currently my sql query contains all the values except the last column visit. I want the visit column to work this way...if visit_number contains same value for multiple rows, i want the column visit to show the value 1 for the first row and just null or empty for the remaining rows where the visit_number is the same. How do i do that???
i could write the sample query in any way.it could simply be :
select name,date,visit_number from sometable order by date;
I am using postgres 8.1 version.
Thanks
The first thing you should do is upgrade to a modern day version of PostgreSQL. Version 8.1 has reached end of life in November 2010.
In a more recent version you can conveniently solve this with window functions:
SELECT name, date, visit_number
, CASE WHEN row_number() OVER (PARTITION BY visit_number
ORDER BY date, name) = 1
THEN 1
ELSE NULL
END AS visit
FROM tbl
ORDER BY date, name;
I ordered by name additionally to break ties.
For versions before PostgreSQL 8.4, this query should work (untested):
SELECT name, date, visit_number
, CASE WHEN EXISTS (
SELECT *
FROM tbl t1
WHERE t1.visit_number = tbl.visit_number -- more to make it unique?
AND t1.date <= tbl.date -- or more columns to make order unambiguous
AND t1.name < tbl.name
)
THEN NULL ELSE 1 END AS visit
FROM tbl
ORDER BY date, name;
This is the query:
select *,
case when row_number() over (partition by visit_number) = 1
then 1
else null
end
from t
Here is an example
Edit:
Without window function:
select t4.*, case when t3.name is not null then 1 end as visit from t t4
left join (
select t1.* from t t1
left join t t2 on t1.name > t2.name and t1.date = t2.date and
t1.visit_number = t2.visit_number
where t2.name is null
) as t3
on t3.name = t4.name and t3.date = t4.date and t3.visit_number = t4.visit_number
Here is an example
NOTE: If name is a key then the last comparison t3.date = t4.date and t3.visit_number = t4.visit_number can be removed