Subtracting two columns from two different table with different data - sql-server

i had this problem where i got two tables with different values.
TABLE 1:
Description Qty Amount
Proc 1 1 100
Proc 2 1 50
Proc 3 1 60
TABLE 2:
Description Payment
Proc 1 60
Proc 1 30
Proc 2 20
Proc 3 60
Proc 2 20
So, the result should be like:
Description Balance
Proc 1 10
Proc 2 40
Proc 3 0
How will i do this with select query? thanks.
I Already tried this code but it should first get the distinct rows of TABLE1 and sum distinct values from TABLE2 before subtracting the two tables.
SELECT FEESList.[Fee Description], sum(StudentBILLING.Quantity* StudentBILLING.Total- isnull(StudReceipts.Amount,0)) as Balance
FROM StudentBILLING INNER JOIN FEESList ON StudentBILLING.FeeID = FEESList.FeeID INNER JOIN SREGStudentInformation ON StudentBILLING.StudentID = SREGStudentInformation.ID INNER JOIN
SemesterList ON StudentBILLING.SemesterID = SemesterList.SemID INNER JOIN SchoolYear ON StudentBILLING.SYid = SchoolYear.[SY ID] FULL JOIN StudReceipts ON FEESList.FeeID = StudReceipts.FeeID
WHERE (SREGStudentInformation.[Student ID] = #StudentNumber) AND (SemesterList.[Sem.] = #Sem) AND (SchoolYear.[School Year] = #SchoolYear) AND
(FEESList.[Type of Fee] = 2) AND (StudentBILLING.Quantity* StudentBILLING.Total- ISNULL(StudReceipts.Amount,0))>0
GROUP BY FEESList.[Fee Description]
The result is from this;
Description Amount Payments
Proc 1 100 60
Proc 1 100 30
Proc 2 50 20
Proc 2 50 20
Proc 3 60 60
To this result which is not supposed to be.
Description Amount
Proc 1 100
Proc 2 50
Can you help me with this?

You could try below with sample data which you have provided
First, it will generated row numbers by using row_number() function partition by Description and Payment column which has duplicate payment made & sum() if have duplicate payments made
;WITH cte AS (
SELECT *,
ROW_NUMBER() OVER(PARTITION BY [Description],
Payment ORDER BY [Description]) rn
FROM <TABLE 2>)
SELECT t.[Description],
CASE
WHEN COUNT(DISTINCT c.rn) > 1 THEN SUM(c.Payment)
ELSE MAX(t.amount) - SUM(c.Payment)
END [Balance]
FROM cte c
JOIN <TABLE 1> t ON t.[Description] = c.[Description]
GROUP BY t.[Description];
Result :
Description Balance
Proc 1 10
Proc 2 40
Proc 3 0
Note, But you could also check with your sample data what if one Description has both Payments made one is duplicate and other is unique ??

You're maths isn't correct
Proc 2 = Tabl1 50, Tab2 20+ 20 = balance = 10
DECLARE #tab1 TABLE ([Description] NVARCHAR(15), Qty INT, Amount INT )
INSERT INTO #tab1
([Description], Qty, Amount)
SELECT 'Proc 1',1,100 UNION ALL
SELECT 'Proc 2',1,50 UNION ALL
SELECT 'Proc 3',1,60
DECLARE #tab2 TABLE ([Description] NVARCHAR(15), Payment INT)
INSERT INTO #tab2
([Description], Payment)
SELECT 'Proc 1', 60 UNION ALL
SELECT 'Proc 1', 30 UNION ALL
SELECT 'Proc 2', 20 UNION ALL
SELECT 'Proc 3', 60 UNION ALL
SELECT 'Proc 2', 20
SELECT
T.[Description]
, Balance = (T.Qty * T.Amount) - X.Payment
FROM #tab1 T
INNER JOIN
(
SELECT
T.[Description]
, Payment = SUM(T.Payment)
FROM #tab2 T
GROUP BY T.[Description]
) X ON X.[Description] = T.[Description]

Left joining from a derived table will solve your issue. Alternatively you could have done it the slightly longer route by doing aggregation in a cte and then a join
select t1.description
,t1.amount - t2.sum as balance
from table1 t1
left join
(select description
,sum(payment) as sum
from table2
group by description) t2 on t1.description = t2.description
Just some food for thought:
You want to use a left join instead of an inner join. Left join says if it's in this first table and not second, show null. Inner join says show it only if it is in BOTH tables. And I'm guessing you want to show the balance even if a payment hasn't been made (just a guess but that's up to your report).
Here's the rextester sample you can play with.

Related

How to find the cumulative sum in SubQuery? [duplicate]

declare #t table
(
id int,
SomeNumt int
)
insert into #t
select 1,10
union
select 2,12
union
select 3,3
union
select 4,15
union
select 5,23
select * from #t
the above select returns me the following.
id SomeNumt
1 10
2 12
3 3
4 15
5 23
How do I get the following:
id srome CumSrome
1 10 10
2 12 22
3 3 25
4 15 40
5 23 63
select t1.id, t1.SomeNumt, SUM(t2.SomeNumt) as sum
from #t t1
inner join #t t2 on t1.id >= t2.id
group by t1.id, t1.SomeNumt
order by t1.id
SQL Fiddle example
Output
| ID | SOMENUMT | SUM |
-----------------------
| 1 | 10 | 10 |
| 2 | 12 | 22 |
| 3 | 3 | 25 |
| 4 | 15 | 40 |
| 5 | 23 | 63 |
Edit: this is a generalized solution that will work across most db platforms. When there is a better solution available for your specific platform (e.g., gareth's), use it!
The latest version of SQL Server (2012) permits the following.
SELECT
RowID,
Col1,
SUM(Col1) OVER(ORDER BY RowId ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Col2
FROM tablehh
ORDER BY RowId
or
SELECT
GroupID,
RowID,
Col1,
SUM(Col1) OVER(PARTITION BY GroupID ORDER BY RowId ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Col2
FROM tablehh
ORDER BY RowId
This is even faster. Partitioned version completes in 34 seconds over 5 million rows for me.
Thanks to Peso, who commented on the SQL Team thread referred to in another answer.
For SQL Server 2012 onwards it could be easy:
SELECT id, SomeNumt, sum(SomeNumt) OVER (ORDER BY id) as CumSrome FROM #t
because ORDER BY clause for SUM by default means RANGE UNBOUNDED PRECEDING AND CURRENT ROW for window frame ("General Remarks" at https://msdn.microsoft.com/en-us/library/ms189461.aspx)
Let's first create a table with dummy data:
Create Table CUMULATIVESUM (id tinyint , SomeValue tinyint)
Now let's insert some data into the table;
Insert Into CUMULATIVESUM
Select 1, 10 union
Select 2, 2 union
Select 3, 6 union
Select 4, 10
Here I am joining same table (self joining)
Select c1.ID, c1.SomeValue, c2.SomeValue
From CumulativeSum c1, CumulativeSum c2
Where c1.id >= c2.ID
Order By c1.id Asc
Result:
ID SomeValue SomeValue
-------------------------
1 10 10
2 2 10
2 2 2
3 6 10
3 6 2
3 6 6
4 10 10
4 10 2
4 10 6
4 10 10
Here we go now just sum the Somevalue of t2 and we`ll get the answer:
Select c1.ID, c1.SomeValue, Sum(c2.SomeValue) CumulativeSumValue
From CumulativeSum c1, CumulativeSum c2
Where c1.id >= c2.ID
Group By c1.ID, c1.SomeValue
Order By c1.id Asc
For SQL Server 2012 and above (much better performance):
Select
c1.ID, c1.SomeValue,
Sum (SomeValue) Over (Order By c1.ID )
From CumulativeSum c1
Order By c1.id Asc
Desired result:
ID SomeValue CumlativeSumValue
---------------------------------
1 10 10
2 2 12
3 6 18
4 10 28
Drop Table CumulativeSum
A CTE version, just for fun:
;
WITH abcd
AS ( SELECT id
,SomeNumt
,SomeNumt AS MySum
FROM #t
WHERE id = 1
UNION ALL
SELECT t.id
,t.SomeNumt
,t.SomeNumt + a.MySum AS MySum
FROM #t AS t
JOIN abcd AS a ON a.id = t.id - 1
)
SELECT * FROM abcd
OPTION ( MAXRECURSION 1000 ) -- limit recursion here, or 0 for no limit.
Returns:
id SomeNumt MySum
----------- ----------- -----------
1 10 10
2 12 22
3 3 25
4 15 40
5 23 63
Late answer but showing one more possibility...
Cumulative Sum generation can be more optimized with the CROSS APPLY logic.
Works better than the INNER JOIN & OVER Clause when analyzed the actual query plan ...
/* Create table & populate data */
IF OBJECT_ID('tempdb..#TMP') IS NOT NULL
DROP TABLE #TMP
SELECT * INTO #TMP
FROM (
SELECT 1 AS id
UNION
SELECT 2 AS id
UNION
SELECT 3 AS id
UNION
SELECT 4 AS id
UNION
SELECT 5 AS id
) Tab
/* Using CROSS APPLY
Query cost relative to the batch 17%
*/
SELECT T1.id,
T2.CumSum
FROM #TMP T1
CROSS APPLY (
SELECT SUM(T2.id) AS CumSum
FROM #TMP T2
WHERE T1.id >= T2.id
) T2
/* Using INNER JOIN
Query cost relative to the batch 46%
*/
SELECT T1.id,
SUM(T2.id) CumSum
FROM #TMP T1
INNER JOIN #TMP T2
ON T1.id > = T2.id
GROUP BY T1.id
/* Using OVER clause
Query cost relative to the batch 37%
*/
SELECT T1.id,
SUM(T1.id) OVER( PARTITION BY id)
FROM #TMP T1
Output:-
id CumSum
------- -------
1 1
2 3
3 6
4 10
5 15
Select
*,
(Select Sum(SOMENUMT)
From #t S
Where S.id <= M.id)
From #t M
You can use this simple query for progressive calculation :
select
id
,SomeNumt
,sum(SomeNumt) over(order by id ROWS between UNBOUNDED PRECEDING and CURRENT ROW) as CumSrome
from #t
There is a much faster CTE implementation available in this excellent post:
http://weblogs.sqlteam.com/mladenp/archive/2009/07/28/SQL-Server-2005-Fast-Running-Totals.aspx
The problem in this thread can be expressed like this:
DECLARE #RT INT
SELECT #RT = 0
;
WITH abcd
AS ( SELECT TOP 100 percent
id
,SomeNumt
,MySum
order by id
)
update abcd
set #RT = MySum = #RT + SomeNumt
output inserted.*
For Ex: IF you have a table with two columns one is ID and second is number and wants to find out the cumulative sum.
SELECT ID,Number,SUM(Number)OVER(ORDER BY ID) FROM T
Once the table is created -
select
A.id, A.SomeNumt, SUM(B.SomeNumt) as sum
from #t A, #t B where A.id >= B.id
group by A.id, A.SomeNumt
order by A.id
The SQL solution wich combines "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW" and "SUM" did exactly what i wanted to achieve.
Thank you so much!
If it can help anyone, here was my case. I wanted to cumulate +1 in a column whenever a maker is found as "Some Maker" (example). If not, no increment but show previous increment result.
So this piece of SQL:
SUM( CASE [rmaker] WHEN 'Some Maker' THEN 1 ELSE 0 END)
OVER
(PARTITION BY UserID ORDER BY UserID,[rrank] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Cumul_CNT
Allowed me to get something like this:
User 1 Rank1 MakerA 0
User 1 Rank2 MakerB 0
User 1 Rank3 Some Maker 1
User 1 Rank4 Some Maker 2
User 1 Rank5 MakerC 2
User 1 Rank6 Some Maker 3
User 2 Rank1 MakerA 0
User 2 Rank2 SomeMaker 1
Explanation of above: It starts the count of "some maker" with 0, Some Maker is found and we do +1. For User 1, MakerC is found so we dont do +1 but instead vertical count of Some Maker is stuck to 2 until next row.
Partitioning is by User so when we change user, cumulative count is back to zero.
I am at work, I dont want any merit on this answer, just say thank you and show my example in case someone is in the same situation. I was trying to combine SUM and PARTITION but the amazing syntax "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW" completed the task.
Thanks!
Groaker
Above (Pre-SQL12) we see examples like this:-
SELECT
T1.id, SUM(T2.id) AS CumSum
FROM
#TMP T1
JOIN #TMP T2 ON T2.id < = T1.id
GROUP BY
T1.id
More efficient...
SELECT
T1.id, SUM(T2.id) + T1.id AS CumSum
FROM
#TMP T1
JOIN #TMP T2 ON T2.id < T1.id
GROUP BY
T1.id
Try this
select
t.id,
t.SomeNumt,
sum(t.SomeNumt) Over (Order by t.id asc Rows Between Unbounded Preceding and Current Row) as cum
from
#t t
group by
t.id,
t.SomeNumt
order by
t.id asc;
Try this:
CREATE TABLE #t(
[name] varchar NULL,
[val] [int] NULL,
[ID] [int] NULL
) ON [PRIMARY]
insert into #t (id,name,val) values
(1,'A',10), (2,'B',20), (3,'C',30)
select t1.id, t1.val, SUM(t2.val) as cumSum
from #t t1 inner join #t t2 on t1.id >= t2.id
group by t1.id, t1.val order by t1.id
Without using any type of JOIN cumulative salary for a person fetch by using follow query:
SELECT * , (
SELECT SUM( salary )
FROM `abc` AS table1
WHERE table1.ID <= `abc`.ID
AND table1.name = `abc`.Name
) AS cum
FROM `abc`
ORDER BY Name

How make possible Joins with group by clause

I have two tables, table1 has 2 columns as
id name
1 Amal
2 Varun
3 Sari
table2 has 3 columns as
id Subject marks
1 Maths 80
1 Malayalam 75
1 History 45
2 Maths 90
2 Malayalam 85
2 History 50
3 Maths 88
3 Malayalam 75
3 History 80
My question is to find the names who has the maximum mark for each subject (Subject wisw topper) the resultant table have to includes the fields name subject and marks
I tested with the following query
SELECT
table1.Student_Name, (table2.subject), max(table2.Marks_obt)
FROM
table2
INNER JOIN
table1 ON table2.stud_id = table1.Student_ID
GROUP BY
[Student_Name], table2.Subject
HAVING
MAX(Marks_obt) IN (SELECT MAX(Marks_obt) AS total_marks
FROM table2
GROUP BY subject)
In SQL Server 2008, but I got the result as
name subject
Sari History 80
Varun Malayalam 85
Amal Maths 80
Varun Maths 90
how I get the topper of three subject with these manner?
You can use ROW_NUMBER() :
SELECT s.subject,s.name,s.marks
FROM(
SELECT t1.*,t2.subject,t2.marks,
ROW_NUMBER() OVER(PARTITION BY t2.subject ORDER BY t2.marks DESC) as rnk
FROM Table1
JOIN Table2
ON table2.stud_id = table1.Student_ID) s
WHERE s.rnk = 1
Use Rank You will not miss any people for example like two people will get same highest marks in same subject. And if you want query to find 2nd highest marks or 3rd highest use Dense_Rank() function even dense_rank() also works for finding 1st highest. For More cilck here
SELECT NAME,
SUBJECT,
MARKS
FROM (SELECT NAME,
SUBJECT,
MARKS,
rank()
OVER(
PARTITION BY [SUBJECT]
ORDER BY MARKS DESC) RNO
FROM #TABLE1 T
JOIN #TABLE2 T2
ON T.ID = T2.ID) A
WHERE RNO = 1
CREATE TABLE #table1
(Student_ID INT,
Student_Name VARCHAR(20))
INSERT INTO #table1
SELECT 1,'Amal'
UNION
SELECT 2,'Varun'
UNION
SELECT 3,'Sari'
CREATE TABLE #table2
(
stud_id INT,
[subject] VARCHAR(20),
Marks_obt INT
)
INSERT INTO #table2
SELECT 1,'Maths',80
UNION
SELECT 1,'Malayalam',75
UNION
SELECT 1,'History',45
UNION
SELECT 2,'Maths',90
UNION
SELECT 2,'Malayalam',85
UNION
SELECT 2,'History',80
UNION
SELECT 3,'Maths',88
UNION
SELECT 3,'Malayalam',75
UNION
SELECT 3,'History',80
/*Table 1*/
SELECT * FROM #table1
/*Table 2*/
SELECT * FROM #table2
/*Top Mark*/
SELECT [subject],
Student_Name,
Marks_obt
FROM(SELECT Student_Name,
[subject],
Marks_obt,
RANK()
OVER(
PARTITION BY [subject]
ORDER BY Marks_obt DESC) RowNum
FROM #table1 T1
JOIN #table2 T2
ON T1.Student_ID= T2.stud_id) AS data
WHERE data.RowNum = 1
DROP TABLE #table1,#table2
you can use a cross apply too like this
with maxi as (
select Subject, max(marks) maximark from table2
group by Subject
)
select * from maxi f1
cross apply
(
select top 1 f2.name from table1 f2 inner join table2 f3 on f2.id=f3.id
where f1.maximark=f3.marks and f1.subject=f3.subject
) f3
if multiple users are possible for a maxi mark, remove "top 1"
other solution with imbication:
with maxi as (
select Subject, max(marks) maximark from table2
group by Subject
)
select (select top 1 f2.name from table1 f2 inner join table2 f3 on f2.id=f3.id where f1.maximark=f3.marks and f1.Subject=f3.Subject) as Name, f1.*
from maxi f1

Left join with Sum Clause with more than 1 table gives incorrect Sum

I am trying to get the Sum of rows while applying a left join with more than 1 table. It seems it is creating a matrix of result which results in wrong sum function.
Example:
First Table: Customer
Second Table: TotalAssets
Third Table: TotalLiability
Table Structure:
Customer
CustID(int) CustomerName(varchar)
1 Abc
2 Def
3 Ghi
TotalAssets
CustID Amount
1 2000
1 1000
2 600
TotalLiability
CustID Amount
1 1000
1 1000
2 800
Output Expected
CustID TotalAssets TotalLiability
1 3000 2000
2 600 800
Current Query
Select c.CustID , Sum(a.Amount) , Sum(l.Amount) From Customer c
left join TotalAssests a on a.CustID = c.CustID
left join TotalLiability l on l.CustID = c.CustID
Group by c.CustID
The problem with this current query is the sum is not correct as i think the first left join create a first set with multiple records and then second one is applied.
Any help is appreciated
UPDATE:
I find some luck by following method but it seems a bad/hacky option as in my case i have over 7-8 elements in group by and adding more left clauses results in query difficult to manage.
New Query which is resulting correct result but looks very bad to maintains
Select Set1.CustID , Set1.TotalAssets, Sum(l.Amount) from (Select c.CustID , Sum(a.Amount) as TotalAssets From Customer c
left join TotalAssests a on a.CustID = c.CustID
Group by c.CustID)Set1
left join TotalLiability l on l.CustID = Set1.CustID.
Group by Set1.CustID , Set1.TotalAssets
I think this gets you what you want with minimum complexity:
select c.CustId, isnull(a.Amount, 0) as TotalAssets, isnull(l.Amount, 0) as TotalLiability
from Customers c
left join (
select CustId, sum(Amount) as Amount from TotalAssets group by CustId
) a on a.CustId = c.CustId
left join (
select CustId, sum(Amount) as Amount from TotalLiability group by CustId
) l on l.CustId = c.CustId
You need to group/sum the two tables separately, since the data in them is independent. Left-joining both to the customers table ensures that customers with no entries in either/both tables are still reported.
This should work:
Select c.CustID
, (select sum(a.amount) from TotalAssests a where a.CustId = c.CustID) as SumAsset
, (select Sum(l.Amount) TotalLiability l where l.CustID = c.CustID) as SumLiability
From Customer c
Hope the below works with less maintenance,
DECLARE #Customer TABLE (CustID int, CustomerName varchar(50)) DECLARE #TotalAssets TABLE (CustID int, Amount INT) DECLARE #TotalLiability TABLE (CustID int, Amount INT)
INSERT INTO #Customer
SELECT 1,
'ABC'
UNION
SELECT 2,
'DEF'
UNION
SELECT 3,
'GHI'
--Select * From #Customer
INSERT INTO #TotalAssets
SELECT 1,
2000
UNION
SELECT 1,
1000
UNION
SELECT 2,
600
--Select * From #TotalAssets
INSERT INTO #TotalLiability
SELECT 1,
1000
UNION
SELECT 1,
1000
UNION
SELECT 2,
800
--Select * From #TotalLiability
SELECT *
FROM #Customer
SELECT C.CustID,
C.CustomerName,
Sum(A.Amount) TotalAssets,
Sum(L.Amount) TotalLiability
FROM #Customer C
JOIN #TotalAssets A ON C.CustID = A.CustID
JOIN #TotalLiability L ON C.CustId = L.CustID
GROUP BY C.CustID,
C.CustomerName

Select rows with column with min value

I need to select the rows with the minimum distance by grouping on the OrganisationID. Here is my data in a single table:
ID OrganisationID Distance
0 10 100
1 10 200
3 10 50
4 20 80
5 20 300
This is the result I want:
ID OrganisationID Distance
3 10 50
4 20 80
This will accomplish that:
SELECT t1.*
FROM yourTable t1
LEFT JOIN yourTable t2
ON (t1.OrganisationID = t2.OrganisationID AND t1.Distance > t2.Distance)
WHERE t2.OrganisationID IS NULL;
sqlfiddle demo
Note that if there are multiple rows with the lowest distance duplicate, this returns them both
EDIT:
If, as you say in the comments, only want one column and the MIN distance you can do it easily with MIN and GROUP BY:
SELECT city, MIN(distance)
FROM table2
GROUP BY city;
sqlfiddle demo
p.s. i saw your previous question that you deleted, and was answering it with a different thing than this (was going to tell you that since you had the organisationID in the WHERE clause, you could just do: SELECT TOP 1 ... order by Distance DESC), but if you need more it for more than one organisationID, this is something that can get you there)
This is the solution:
SELECT ID ,D.*
FROM <TABLE> INNER JOIN( SELECT OrganisationID 'OR',MIN(Distance) DI
FROM <TABLE>
GROUP BY OrganisationID) D
ON D.DI=<TABLE>.Distance
Test :
CREATE TABLE #T
(
ID INT,
OrganisationID INT,
Distance INT
)
INSERT INTO #T
SELECT 0,10,100
UNION ALL
SELECT 1,10,200
UNION ALL
SELECT 3,10,50
UNION ALL
SELECT 4,20,80
UNION ALL
SELECT 5,20,300
SELECT ID ,D.*
FROM #T INNER JOIN( SELECT OrganisationID 'OR',MIN(Distance) DI
FROM #T
GROUP BY OrganisationID) D
ON D.DI=#T.Distance
DROP TABLE #T

Sql query - how to get when a row first got a certain value

I have a table with rows like this:
ID StatusId Date
1 1 2001-01-01
2 1 2001-01-02
3 2 2001-01-03
4 3 2001-01-04
5 1 2001-01-05
6 2 2001-01-06
7 2 2001-01-07
8 1 2001-01-08
9 1 2001-01-09
I need to get the date when the current value of the status was originally changed. For the above example, the last value is 1, and it's changed in row 8, so the result would be 2001-01-08.
How would you do this?
If you need a table to test with, here it is:
DECLARE #Tbl AS TABLE (ID INT, StatusId INT, Date DATETIME)
INSERT INTO #Tbl(ID, StatusId, Date)
SELECT 1,1,'2001-01-01' UNION
SELECT 2,1,'2001-01-02' UNION
SELECT 3,2,'2001-01-03' UNION
SELECT 4,3,'2001-01-04' UNION
SELECT 5,1,'2001-01-05' UNION
SELECT 6,2,'2001-01-06' UNION
SELECT 7,2,'2001-01-07' UNION
SELECT 8,1,'2001-01-08' UNION
SELECT 9,1,'2001-01-09'
SELECT * FROM #Tbl
This one should get you what you're after:
declare #LastStatusID int
declare #LastDate datetime
declare #LastID int
declare #LastChangeID int
/* get last record */
select top 1 #LastStatusID = StatusID, #LastDate = Date, LastID = ID
from #Tbl
order by ID desc
/* get last record with a different status */
select top 1 #LastChangeID = ID
from #Tbl
where ID < #LastID and StatusID <> #LastStatusID
order by ID desc
/* get the first next record - this would get you the last record as well whe it's just been set */
select top 1 Date
from #Tbl
where ID > #LastChangeID
order by ID asc
I haven't included any checking for margin examples when there'd be just one record in the table or multiple of them but all with the same status. You can figure those out yourself.
As a single query
This query requires IDs without gaps and it will get you the last record after a status change and it will also work when there's just one record in the table or multiple of them with the same status (isnull provides the required functionality)
select top 1 Date
from #tbl t1
left join #tbl t2
on (t2.ID = t1.ID - 1)
where (isnull(t2.StatusID, -1) <> t1.StatusID)
order by ID desc
Last where clause changes a null value (when there's no upper record) to -1. If you do have a status with this value, you should change this number to some non-existing status value.
Something like this:
DECLARE #CurrentID INT, #CurrentDate Date
SELECT TOP 1 #CurrentID = ID, #CurrentDate = Date FROM TABLE
ORDER BY Date DESC
SELECT TOP 1 ID, StatusID, Date
FROM Table
WHERE Date < #CurrentDate
AND ID <> #CurrentID
ORDER BY Date DESC
try
select Date
from #Tbl
where StatusId = (
select StatusId
from #Tbl
order by ID desc limit 1)
order by ID desc
limit 1,1
Please check if your database supports limit or not. If not use equivalent of it (e.g. Top).
I have written this as per mysql.
If the table is guaranteed to have one entry per day (as per your sample data), then the following may work
select MAX(t1.Date)
from
#Tbl t1
inner join
#Tbl t2
on
t1.Date = DATEADD(day,1,t2.Date) and
t1.StatusId <> t2.StatusID
Of course, it's possible to further refine this if there are other columns/criteria, of if the value may never have changed at all. Difficult to tell with the small sample size/output example.
Edit 1 If my one entry per day assumption is wrong, then the from clause can be:
from
#Tbl t1
inner join
#Tbl t2
on
t1.Date > t2.Date and
t1.StatusId <> t2.StatusID
left join
#Tbl t_successive
on
t1.Date > t_successive.Date and
t2.Date < t_successive.Date
where
t_successive.ID is null
(Which uses the left join to ensures rows in t1 and t2 don't have any other rows between them)
This is what I came up with finally:
SELECT T1.ID, T1.StatusId, MIN(T3.Date)
FROM #Tbl T1 INNER JOIN #Tbl T3 ON T1.StatusId = T3.StatusId
WHERE T3.Date > (SELECT MAX(Date) FROM #Tbl T2 WHERE T2.StatusId <> T1.StatusId)
AND T1.ID = (SELECT MAX(ID) FROM #Tbl)
GROUP BY T1.ID, T1.StatusId
and it's doing what I needed it to... thanks everyone

Resources