Extract Historical Status for list of dates - sql

I have two tables
TableA has a list of historical statuses for an entity. These can also be reversed, if reversed the row needs to be ignored.
EntityID | StatusDate | Status | IsReversed
-------------------------------------------------
1 | 2014-01-15 | A | NULL
1 | 2014-06-17 | B | Y
1 | 2015-01-19 | C | NULL
TableB has a list of maintenance dates for the entity
EntityID | MaintDate
-----------------------
1 | 2014-02-20
1 | 2014-03-30
1 | 2015-11-22
I would like to produce a list of the maintenance dates which also lists the status of the entity as at the maintenance date.
I've been shown how to retrieve an individual status for one individual date
SELECT TOP 1
Status
FROM
TableA
WHERE
StatusDate < '2014-03-30' AND IsReversed != 'Y'
ORDER BY
StatusDate DESC
But I can't work out how to integrate this into a query to retrieve the status for every date.
Thanks in advance for your help!
Here is the actual query #SITELIST is TableB and "LNSP_UAT.dbo.INSTALLSTATUS" is TableA:
SELECT DISTINCT
I.INSTALL [INSTALL]
,RD.GBR$CDATE [ENDDATE]
,RD.GBR$PDATE [STARTDATE]
INTO #SITELIST
FROM
LNSP_UAT.dbo.INSTALL I
LEFT JOIN LNSP_UAT.dbo.GBBILLREG RG ON RG.GBB$INSTALL = I.INSTALL
LEFT JOIN LNSP_UAT.dbo.GBREGISTER RD ON RD.GBR$BREGKEY = RG.GBB$REGKEY AND RD.GBR$STATUS = 25 AND RD.GBR$CTYPE IN ('N','D','A','G','S')
SELECT
SL.INSTALL
,SL.STARTDATE
,SL.ENDDATE
,ST.ISSTATUS
FROM
#SITELIST SL
OUTER APPLY (
SELECT TOP 1
ST.ISSTATUS
FROM
LNSP_UAT.dbo.INSTALLSTATUS ST
WHERE
ST.ISINSTALL = SL.INSTALL
AND ST.ISREVERSE != 'Y'
AND ST.ISEFFDATE <= SL.STARTDATE
ORDER BY
ISEFFDATE DESC
) A
ORDER BY ENDDATE DESC
DROP TABLE #SITELIST

What you're looking for is APPLY:
SELECT
b.*, a.Status
FROM TableB b
OUTER APPLY (
SELECT TOP 1 Status
FROM TableA
WHERE
EntityID = b.EntityID
AND IsReversed != 'Y'
AND StatusDate <= b.MaintDate
ORDER BY StatusDate DESC
) a

SELECT
b.*,
(SELECT TOP 1
Status
FROM
TableA
WHERE
EntityID = b.EntityID
AND IsReversed != 'Y' AND StatusDate < b.MaintDate
ORDER BY
StatusDate DESC) as status
FROM TableB b

Related

SQL Exclude Row if Specific Value Exists Within Joined Field

I am trying to write an SQL query that will allow me to exclude a record from TableA if it has at least one match against TableB.
I have written some code, as below, that almost gets me what I need -
SELECT a.ID,
a.OPEN_DT,
b.LINKCREATED,
b.RULE__ID
FROM TableA a
LEFT JOIN TableB b
ON a.ROW_WID = b.A_ROW_WID
WHERE EXTRACT(YEAR FROM a.OPEN_DT) >= '2013'
AND NOT EXISTS (SELECT *
FROM TableB
WHERE A_ROW_WID = a.ROW_WID
AND EXTRACT(YEAR FROM b.CREATED) >= '2017')
;
Table A
ROW_WID | ID | OPEN_DT
---------------------------------
1 | A | 2013-01-01
2 | B | 2014-01-01
3 | C | 2017-01-01
Table B
RULE_ID | A_ROW_WID | LINKCREATED
---------------------------------
1 | A | 2014-01-01
2 | A | 2017-01-01
3 | B | 2017-01-01
The query above would return 1 row for ROW_WID = 1, 1 row for ROW_WID = 2 and nothing for ROW_WID = 3.
I would like my query to exclude ROW_WID=1 altogether because there is one row in TableB that has the year 2017.
I hope this question is clear, but let me know if not.
-EDIT-
Expected result would look like this -
ID | OPEN_DT | LINKCREATED | RULE_ID
C | 2017-01-01 | NULL | NULL
As ID 'C' from TableA has no link in TableB.
If there were an entry in A that had any links in B prior to 2017, they would be returned. Just not any with a TableB entry >= 2017.
Your issue is that you aren't checking for the max created date in the NOT EXISTS:
SELECT a.ID,
a.OPEN_DT,
b.LINKCREATED,
b.RULE__ID
FROM TableA a
LEFT JOIN TableB b
ON a.ROW_WID = b.A_ROW_WID
WHERE EXTRACT(YEAR FROM a.OPEN_DT) >= '2013'
AND NOT EXISTS (SELECT 'NE'
FROM TableB B2
WHERE A_ROW_WID = a.ROW_WID
AND B2.LINKCREATED= (SELECT MAX(BE.LINKCREATED) FROM TableB BE WHERE B2.A_ROW_WID=BE.A_ROW_WID)
AND EXTRACT(YEAR FROM b2.CREATED) >= '2017')
Try using not in:
SELECT a.ID,
a.OPEN_DT,
b.LINKCREATED,
b.RULE__ID
FROM TableA a
LEFT JOIN TableB b
ON a.ROW_WID = b.A_ROW_WID
WHERE EXTRACT(YEAR FROM a.OPEN_DT) >= '2013'
AND b.rule_id not in (select rule_id from TableB where A_ROW_WID in (SELECT
A_ROW_WID
FROM TableB
WHERE EXTRACT(YEAR FROM b.CREATED) >= '2017')a)b

MS-SQL select rows that have no records as null

I have the following 3 tables:
A) Unit information [Unit]
+-----------+---------+-------+
| record_ID | SN | Data1 |
+-----------+---------+-------+
| 1 | 123 123 | info |
+-----------+---------+-------+
B) Test related information [TestingData]
+---------+------------------+-----------+
| SN | Info1 | Data1 |
+---------+------------------+-----------+
| 123 123 | Some information | Some data |
+---------+------------------+-----------+
C) Join table to more testing information [Link]
+--------+---------+
| LinkID | SN |
+--------+---------+
| 1 | 123 123 |
+--------+---------+
D) Testing details [Tests]
+--------+---------------------+-----------+
| LinkID | testdate | Testname |
+--------+---------------------+-----------+
| 1 | 10.05.2015 22:22:00 | AD1 |
+--------+---------------------+-----------+
Now the tricky part:
I need to get the last record for each LinkID where test name = AD1 for example.
I managed to create a query that does just this, except that it returns no rows in case there was no AD1 named test:
SELECT * FROM (
SELECT
ROW_NUMBER() OVER(PARTITION BY rf2.linkID ORDER BY rf2.testDate DESC) AS rn
,rf2.*, rs.*
FROM dbo.Unit rs
FULL OUTER JOIN dbo.TestingData rf ON ( rs.SN = rf.SN )
FULL OUTER JOIN dbo.Link rf1 ON ( rf.SN = rf1.SN)
FULL OUTER JOIN dbo.Tests rf2 ON ( rf1.linkID = rf2.linkID )
WHERE rf2.testType = 'AD1'
) T
WHERE rn = 1
ORDER BY SN ASC;
How can I extend (if possible at all) above query so that I will also get rows with null values in case there has not been a testType of AD1?
Reason behind this is that I will need to integrate this part to a larger report, which I will integrate through a join on SN.
I need to get the last record for each LinkID where test name = AD1 for example.
I can't figure out why your question has references to four tables, when one table has all the information you need. The full joins are even less explanable.
This gets the rows from tests that you want:
select top (1) with ties t.*
from tests t
where t.testName = 'AD1'
order by row_number() over (partition by t.linkid order by t.testdate desc)
union all
select t.*
from tests t
where not exists (select 1 from tests t2 where t2.linkid = t.linkid and t.testName = 'AD1');
You can also do this with window functions:
select t.*
from (select t.*,
row_number() over (partition by linkid, testname order by testdate desc) as seqnum,
sum(case when testName = 'AD1' then 1 else 0 end) over (partition by linkid) as num_ad1
from tests t
) t
where (l.num_ad1 > 0 and testName = 'AD1' and seqnum = 1) or
(l.num_ad1 = 0);
You can JOIN in the other tables, for additional columns you may want. There is no need for FULL JOIN, a LEFT JOIN should suffice.
You are saying where test name = AD1 so I think you should check Testname instead of testType in the WHERE clause as below:
SELECT * FROM (
SELECT
ROW_NUMBER() OVER(PARTITION BY rf2.linkID ORDER BY rf2.testDate DESC) AS rn
,rf2.*, rs.*
FROM dbo.Unit rs
FULL OUTER JOIN dbo.TestingData rf ON ( rs.SN = rf.SN )
FULL OUTER JOIN dbo.Link rf1 ON ( rf.SN = rf1.SN)
FULL OUTER JOIN dbo.Tests rf2 ON ( rf1.linkID = rf2.linkID )
WHERE rf2.Testname = 'AD1' -- You should use `Testname` instead of `testType`
) T
WHERE rn = 1
ORDER BY SN ASC;
OUTPUT:
rn LinkID testdate Testname record_ID SN Data1
1 1 2015-10-05 22:22:00.000 AD1 1 123 123 info

Joining Table A and B to get elements of both

I have two tables:
Table 'bookings':
id | date | hours
--------------------------
1 | 06/01/2016 | 2
1 | 06/02/2016 | 1
2 | 06/03/2016 | 2
3 | 06/03/2016 | 4
Table 'lookupCalendar':
date
-----
06/01/2016
06/02/2016
06/03/2016
I want to join them together so that I have a date for each booking so that the results look like this:
Table 'results':
id | date | hours
--------------------------
1 | 06/01/2016 | 2
1 | 06/02/2016 | 1
1 | 06/03/2016 | 0 <-- Added by query
2 | 06/01/2016 | 0 <-- Added by query
2 | 06/02/2016 | 0 <-- Added by query
2 | 06/03/2016 | 2
3 | 06/01/2016 | 0 <-- Added by query
3 | 06/02/2016 | 0 <-- Added by query
3 | 06/03/2016 | 4
I have tried doing a cross-apply, but that doesn't get me there, neither does a full join. The FULL JOIN just gives me nulls in the id column and the cross-apply gives me too much data.
Is there a query that can give me the results table above?
More Information
It might be beneficial to note that I am doing this so that I can calculate an average hours booked over a period of time, not just the number of records in the table.
Ideally, I'd be able to do
SELECT AVG(hours) AS my_average, id
FROM bookings
GROUP BY id
But since that would just give me a count of the records instead of the count of the days I want to cross apply it with the dates. Then I think I can just do the query above with the results table.
select i.id, c.date, coalesce(b.hours, 0) as hours
from lookupCalendar c
cross join (select distinct id from bookings) i
left join bookings b
on b.id = i.id
and b.date = c.date
order by i.id, c.date
Try this:
select c.date, b.id, isnull(b.hours, 0)
from lookupCalendar c
left join bookings b on b.date = c.date
LookupCalendar is your main table because you want the bookings against each date, irrespective of whether there was a booking on that date or not, so a left join is required.
I am not sure if you need to include b.id to solve your actual problem though. Wouldn't you just want to get the total number of hours booked against each date like this, to then calculate the average?:
select c.date, sum(isnull(b.hours, 0))
from lookupCalendar c
left join bookings b on b.date = c.date
group by c.date
You can try joining all the combinations of IDs and dates and left joining the data;
WITH Booking AS (SELECT *
FROM (VALUES
( 1 , '06/01/2016', 2 )
, ( 1 , '06/02/2016', 1 )
, ( 2 , '06/03/2016', 2 )
, ( 3 , '06/03/2016', 4 )
) x (id, date, hours)
)
, lookupid AS (
SELECT DISTINCT id FROM Booking
)
, lookupCalender AS (
SELECT DISTINCT date FROM Booking
)
SELECT ID.id, Cal.Date, ISNULL(B.Hours,0) AS hours
FROM lookupid id
INNER JOIN lookupCalender Cal
ON 1 = 1
LEFT JOIN Booking B
ON id.id = B.id
AND Cal.date = B.Date
ORDER BY ID.id, Cal.Date

SQL Joins . One to many relationship

I have two tables as below
Table 1
-----------------------------------
UserID | UserName | Age | Salary
-----------------------------------
1 | foo | 22 | 33000
-----------------------------------
Table 2
------------------------------------------------
UserID | Age | Salary | CreatedDate
------------------------------------------------
1 | NULL | 35000 | 2015-01-01
------------------------------------------------
1 | 28 | NULL | 2015-02-01
------------------------------------------------
1 | NULL | 28000 | 2015-03-01
------------------------------------------------
I need the result like this.
Result
-----------------------------------
UserID | UserName | Age | Salary
-----------------------------------
1 | foo | 28 | 28000
-----------------------------------
This is just an example. In my real project I have around 6 columns like Age and Salary in above tables.
In table 2 , each record will have only have one value i.e if Age has value then Salary will be NULL and viceversa.
UPDATE :
Table 2 has CreatedDate Column. So i want to get latest "NOTNULL" CELL Value instead of maximum value.
You can get this done using a simple MAX() and GROUP BY:
select t1.userid,t1.username, MAX(t2.Age) as Age, MAX(t2.Salary) as Salary
from table1 t1 join
table2 t2 on t1.userid=t2.userid
group by t1.userid,t1.username
Result:
userid username Age Salary
--------------------------------
1 foo 28 35000
Sample result in SQL Fiddle
Note: I'm giving you the benefit of the doubt that you know what you're doing, and you just haven't told us everything about your schema.
It looks like Table 2 is actually an "updates" table, in which each row contains a delta of changes to apply to the base entity in Table 1. In which case you can retrieve each column's data with a correlated join (technically an outer-apply) and put the results together. Something like the following:
select a.UserID, a.UserName,
coalesce(aAge.Age, a.Age),
coalesce(aSalary.Salary, a.Salary)
from [Table 1] a
outer apply (
select Age
from [Table 2] x
where x.UserID = a.UserID
and x.Age is not null
and not exists (
select 1
from [Table 2] y
where x.UserID = y.UserID
and y.Id > x.Id
and y.Age is not null
)
) aAge,
outer apply (
select Salary
from [Table 2] x
where x.UserID = a.UserID
and x.Salary is not null
and not exists (
select 1
from [Table 2] y
where x.UserID = y.UserID
and y.Id > x.Id
and y.Salary is not null
)
) aSalary
Do note I am assuming you have at minimum an Id column in Table 2 which is monotonically increasing with each insert. If you have a "change time" column, use this instead to get the latest row, as it is better.
To get the latest value based on CreatedDate, you can use ROW_NUMBER to filter for latest rows. Here the partition is based UserID and the other columns, Age and Salary.
SQL Fiddle
;WITH Cte AS(
SELECT
UserID,
Age = MAX(Age),
Salary = MAX(Salary)
FROM(
SELECT *, Rn = ROW_NUMBER() OVER(
PARTITION BY
UserID,
CASE
WHEN Age IS NOT NULL THEN 1
WHEN Salary IS NOT NULL THEN 2
END
ORDER BY CreatedDate DESC
)
FROM Table2
)t
WHERE Rn = 1
GROUP BY UserID
)
SELECT
t.UserID,
t.UserName,
Age = ISNULL(c.Age, t.Age),
Salary = ISNULL(c.Salary, t.Salary)
FROM Table1 t
LEFT JOIN Cte c
ON t.UserID = c.UserID
following query should work(working fine in MSSQL) :
select a.userID,a.username,b.age,b.sal from <table1> a
inner join
(select userID,MAX(age) age,MAX(sal) sal from <table2> group by userID) b
on a.userID=b.userID

How to create view that combine multiple row from 2 tables?

I want to create view that combine data from two tables, sample data in each table is like below.
SELECT Command for TableA
SELECT [ID], [Date], [SUM]
FROM TableA
Result
ID | Date | SUM
1 | 1/1/2010 | 2
1 | 1/2/2010 | 4
3 | 1/3/2010 | 6
SELECT Command for TableB
SELECT [ID], [Date], [SUM]
FROM TableB
Result
ID | Date | SUM
1 | 1/1/2010 | 5
1 | 2/1/2010 | 3
1 | 31/1/2010 | 2
2 | 1/2/2010 | 20
I want output like below
ID | Date | SUMA | SUMB
1 | 1/1/2010 | 2 | 10
1 | 1/2/2010 | 4 | 0
2 | 1/2/2010 | 0 | 20
3 | 1/3/2010 | 6 | 0
How can I do that on SQL Server 2005?
Date information be vary, as modify in table.
Try this...
SELECT
ISNULL(TableA.ID, TableB.ID) ID,
ISNULL(TableA.Date, TableB.Date),
ISNULL(TableA.Sum,0) SUMA,
ISNULL(TableB.Sum, 0) SUMB
FROM
TableA FULL OUTER JOIN TableB
ON TableA.ID = TableB.ID AND TableA.Date = TableB.Date
ORDER BY
ID
A full outer join is what you need because you want to include results from both tables regardless of whether there is a match or not.
I usually union the two queries together and then group them like so:
SELECT ID, [Date], SUM(SUMA) As SUMA, SUM(SUMB) AS SUMB
FROM (
SELECT ID, [Date], SUMA, 0 AS SUMB
FROM TableA
UNION ALL
SELECT ID, [Date], 0 As SUMA, SUMB
FROM TableB
)
GROUP BY ID, [Date]
SELECT
ISNULL(a.ID, b.ID) AS ID,
ISNULL(a.Date, b.Date) AS Date,
ISNULL(a.SUM, 0) AS SUMA,
ISNULL(b.SUM, 0) AS SUMB,
FROM
TableA AS a
FULL JOIN
TableB AS b
ON a.ID = b.ID
AND a.Date = b.Date;
It's not obvious how you want to combine the two tables. I think this is what you're after, but can you confirm please?
TableA.Date is the most important field; if a given date occurs in TableA then it will be included in the view, but not if it only occurs in TableB.
If a date has records in TableA and TableB and the records have a matching ID, they are combined into one row in the view with SUMA being taken from TableA.Sum and SUMB being TableA.Sum * TableB.Sum (e.g. Date: 01/01/2010, ID: 1) (e.g. Date: 01/03/2010 ID: 3).
If a date has records in TableA and TableB with different IDs, the view include these records separately without multiplying the Sum values at all (e.g. Date 02/01/2010, ID: 1 and ID: 2)