Have a table that's basically the following:
ID FileDate Reg# Value
1 01012022 ABC 100.00
2 01012022 CDE 51.20
3 02052022 ABC 101.25
4 02082022 CDE 51.20
(Note - the dates noted above will be properly formatted. I'm just using the example above.)
I want to write a query that will return the rows where the VALUE field has changed for any REG# over a given period of time (example, show me all results where the value of a reg has changed between Jan 1 2022 and March 1 2022).
Ideally, the results would show:
01012022 ABC 100.00
02052022 ABC 101.25
Use can use the LAG() function
--Declaring and populating a Temp table to store the values you mentioned above [added another two entries to test]
DECLARE #tbl TABLE (
ID INT,
FileDate DATE ,
Reg# VARCHAR(3) ,
Value Float)
insert into #tbl (ID, FileDate, Reg#, Value)
values (1, '2022-01-01', 'ABC' , 100) , (2, '2022-01-01', 'CDE' , 51.20), (3, '2022-02-05', 'ABC' , 101.25), (4, '2022-02-08', 'CDE' , 51.20), (5, '2022-03-01', 'ABC' , 101.25), (6, '2022-03-02', 'CDE' , 52);
--Query
with lagtbl as (
SELECT ID, Reg#, FileDate, Value, LAG(t.Value, 1) OVER(PARTITION BY Reg# order by Reg#, ID) Lag
from #tbl t)
select lagtbl.ID, lagtbl.Reg#, lagtbl.FileDate, lagtbl.Value
from lagtbl
where Value != Lag
ORDER BY Reg#, ID
Related
Here is a table...
ID QTY DATE CURRENT_STOCK
----------------------------------
1 1 Jan 30
2 1 Feb 30
3 2 Mar 30
4 6 Apr 30
5 8 May 30
6 21 Jun 30
I need to return the newest rows whose summed qty equal or exceed the current stock level, excluding any additional rows once this total has been reached, so I am expecting to see just these rows...
ID QTY DATE CURRENT_STOCK
----------------------------------
4 6 Apr 30
5 8 May 30
6 21 Jun 30
I am assuming I need a CTE (Common Table Expression) and have looked at this question but cannot see how to translate that to my requirement.
Help!?
Declare #YourTable table (ID int,QTY int,DATE varchar(25), CURRENT_STOCK int)
Insert Into #YourTable values
(1 ,1 ,'Jan' ,30),
(2 ,1 ,'Feb' ,30),
(3 ,2 ,'Mar' ,30),
(4 ,6 ,'Apr' ,30),
(5 ,8 ,'May' ,30),
(6 ,21 ,'Jun' ,30)
Select A.*
From #YourTable A
Where ID>= (
Select LastID=max(ID)
From #YourTable A
Cross Apply (Select RT = sum(Qty) from #YourTable where ID>=A.ID) B
Where B.RT>=CURRENT_STOCK
)
Returns
ID QTY DATE CURRENT_STOCK
4 6 Apr 30
5 8 May 30
6 21 Jun 30
One way to do it with your provided data set
if object_id('tempdb..#Test') is not null drop table #Test
create table #Test (ID int, QTY int, Date_Month nvarchar(5), CURRENT_STOCK int)
insert into #Test (ID, QTY, Date_Month, CURRENT_STOCK)
values
(1, 1, 'Jan', 30),
(2, 1, 'Feb', 30),
(3, 2, 'Mar', 30),
(4, 6, 'Apr', 30),
(5, 8, 'May', 30),
(6, 21, 'Jun', 30)
if object_id('tempdb..#Finish') is not null drop table #Finish
create table #Finish (ID int, QTY int, Date_Month nvarchar(5), CURRENT_STOCK int)
declare #rows int = (select MAX(ID) from #Test)
declare #stock int = (select MAX(CURRENT_STOCK) from #Test)
declare #i int = 1
declare #Sum int = 0
while #rows > #i
BEGIN
select #Sum = #Sum + QTY from #Test where ID = #rows
IF (#SUM >= #stock)
BEGIN
set #i = #rows + 1 -- to exit loop
END
insert into #Finish (ID, QTY, Date_Month, CURRENT_STOCK)
select ID, QTY, Date_Month, CURRENT_STOCK from #Test where ID = #rows
set #rows = #rows - 1
END
select * from #Finish
Setup Test Data
-- Setup test data
CREATE TABLE #Stock
([ID] int, [QTY] int, [DATE] varchar(3), [CURRENT_STOCK] int)
;
INSERT INTO #Stock
([ID], [QTY], [DATE], [CURRENT_STOCK])
VALUES
(1, 1, 'Jan', 30),
(2, 1, 'Feb', 30),
(3, 2, 'Mar', 30),
(4, 6, 'Apr', 30),
(5, 8, 'May', 30),
(6, 21, 'Jun', 30)
;
Solution for SQL Server 2012+
If you have a more recent version of SQL server which supports full window function syntax, you can do it look this:
-- Calculate a running total of qty by Id descending
;WITH stock AS (
SELECT *
-- This calculates the SUM over a 'window' of rows based on the first
-- row in the result set through the current row, as specified by the
-- ORDER BY clause
,SUM(qty) OVER(ORDER BY Id DESC
ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW) AS TotalQty
FROM #Stock
),
-- Identify first row in mininum set that matches or exceeds CURRENT_STOCK
first_in_set AS (
SELECT TOP 1 *
FROM stock
WHERE TotalQty >= CURRENT_STOCK
)
-- Fetch matching set
SELECT *
FROM #stock
WHERE Id >= (SELECT Id FROM first_in_set)
Solution for SQL Server 2008
For SQL Server 2008, which only has basic support for window functions, you can calculate the running total using CROSS APPLY:
-- Calculate a running total of qty by Id descending
;WITH stock AS (
SELECT *
-- This window function causes the results of this query
-- to be sorted in descending order by Id
,ROW_NUMBER() OVER(ORDER BY Id DESC) AS sort_order
FROM #Stock s1
-- CROSS APPLY 'applies' the query (or UDF) to every row in a result set
-- This CROSS APPLY query produces a 'running total'
CROSS APPLY (
SELECT SUM(Qty) AS TotalQty
FROM #Stock s2
WHERE s2.Id >= s1.id
) total_calc
WHERE TotalQty >= s1.CURRENT_STOCK
),
-- Identify first row in mininum set that matches or exceeds CURRENT_STOCK
first_in_set AS (
SELECT TOP 1 Id
FROM stock
WHERE sort_order = 1
)
-- Fetch matching set
SELECT *
FROM #stock
WHERE Id >= (SELECT Id
FROM first_in_set)
I'm trying to create result set with 3 columns. Each column coming from the summation of 1 Column of Table A but grouped by different ID's. Here's an overview of what I wanted to do..
Table A
ID Val.1
1 4
1 5
1 6
2 7
2 8
2 9
3 10
3 11
3 12
I wanted to create something like..
ROW SUM.VAL.1 SUM.VAL.2 SUM.VAL.3
1 15 21 33
I understand that I can not get this using UNION, I was thinking of using CTE but not quite sure with the logic.
You need conditional Aggregation
select 1 as Row,
sum(case when ID = 1 then Val.1 end),
sum(case when ID = 2 then Val.1 end),
sum(case when ID = 3 then Val.1 end)
From yourtable
You may need dynamic cross tab or pivot if number of ID's are not static
DECLARE #col_list VARCHAR(8000)= Stuff((SELECT ',sum(case when ID = '+ Cast(ID AS VARCHAR(20))+ ' then [Val.1] end) as [val.'+Cast(ID AS VARCHAR(20))+']'
FROM Yourtable
GROUP BY ID
FOR xml path('')), 1, 1, ''),
#sql VARCHAR(8000)
exec('select 1 as Row,'+#col_list +'from Yourtable')
Live Demo
I think pivoting the data table will yield the desired result.
IF OBJECT_ID('tempdb..#TableA') IS NOT NULL
DROP TABLE #TableA
CREATE TABLE #TableA
(
RowNumber INT,
ID INT,
Value INT
)
INSERT #TableA VALUES (1, 1, 4)
INSERT #TableA VALUES (1, 1, 5)
INSERT #TableA VALUES (1, 1, 6)
INSERT #TableA VALUES (1, 2, 7)
INSERT #TableA VALUES (1, 2, 8)
INSERT #TableA VALUES (1, 2, 9)
INSERT #TableA VALUES (1, 3, 10)
INSERT #TableA VALUES (1, 3, 11)
INSERT #TableA VALUES (1, 3, 12)
-- https://msdn.microsoft.com/en-us/library/ms177410.aspx
SELECT RowNumber, [1] AS Sum1, [2] AS Sum2, [3] AS Sum3
FROM
(
SELECT RowNumber, ID, Value
FROM #TableA
) a
PIVOT
(
SUM(Value)
FOR ID IN ([1], [2], [3])
) AS p
This technique works if the ids you are seeking are constant, otherwise I imagine some dyanmic-sql would work as well if changing ids are needed.
https://msdn.microsoft.com/en-us/library/ms177410.aspx
I need show sum col(item) under col with SQL code ? it's possible
Code item
---- ----
1 30
3 40
4 50
9 80
---- ----
Total 200
Use Rollup to get the summary row
SELECT CASE
WHEN Grouping(code) = 1 THEN 'Total'
ELSE Cast(code AS VARCHAR(50))
END,
Sum(item)
FROM Yourtable
GROUP BY code WITH rollup
DECLARE #Table1 TABLE
(Code int, item int)
;
INSERT INTO #Table1
(Code, item)
VALUES
(1, 30),
(3, 40),
(4, 50),
(9, 80)
;
Script :
select Code , sum(item)item
from #Table1
group by GROUPING SETS((Code) , ())
order by Code DESC
select * from (select * from #Table1
union
select null, sum(item) item from #Table1)a
order by item
Select
Code,
item
from
# table_name
Union All
select
Null,
sum(item)item
from
# table_name
As we are using union all so distinct and order by operation will be saved.
I'm trying to retrieve the latest set of rows from a source table containing a foreign key, a date and other fields present. A sample set of data could be:
create table #tmp (primaryId int, foreignKeyId int, startDate datetime,
otherfield varchar(50))
insert into #tmp values (1, 1, '1 jan 2010', 'test 1')
insert into #tmp values (2, 1, '1 jan 2011', 'test 2')
insert into #tmp values (3, 2, '1 jan 2013', 'test 3')
insert into #tmp values (4, 2, '1 jan 2012', 'test 4')
The form of data that I'm hoping to retrieve is:
foreignKeyId maxStartDate otherfield
------------ ----------------------- -------------------------------------------
1 2011-01-01 00:00:00.000 test 2
2 2013-01-01 00:00:00.000 test 3
That is, just one row per foreignKeyId showing the latest start date and associated other fields - the primaryId is irrelevant.
I've managed to come up with:
select t.foreignKeyId, t.startDate, t.otherField from #tmp t
inner join (
select foreignKeyId, max(startDate) as maxStartDate
from #tmp
group by foreignKeyId
) s
on t.foreignKeyId = s.foreignKeyId and s.maxStartDate = t.startDate
but (a) this uses inner queries, which I suspect may lead to performance issues, and (b) it gives repeated rows if two rows in the original table have the same foreignKeyId and startDate.
Is there a query that will return just the first match for each foreign key and start date?
Depending on your sql server version, try the following:
select *
from (
select *, rnum = ROW_NUMBER() over (
partition by #tmp.foreignKeyId
order by #tmp.startDate desc)
from #tmp
) t
where t.rnum = 1
If you wanted to fix your attempt as opposed to re-engineering it then
select t.foreignKeyId, t.startDate, t.otherField from #tmp t
inner join (
select foreignKeyId, max(startDate) as maxStartDate, max(PrimaryId) as Latest
from #tmp
group by foreignKeyId
) s
on t.primaryId = s.latest
would have done the job, assuming PrimaryID increases over time.
Qualms about inner query would have been laid to rest as well assuming some indexes.
Given a table with employee statuses and effective dates, how can I retrieve just the data that reflects a change in status?
For example, given the following structure:
DECLARE #STATUSES TABLE(
EMPLOYEE_ID INT NOT NULL,
EFFECTIVE_DATE DATE NOT NULL,
STATUS_CODE CHAR(1) NOT NULL
)
INSERT #STATUSES VALUES (1, '2012-01-01', 'A')
INSERT #STATUSES VALUES (1, '2012-02-28', 'A')
INSERT #STATUSES VALUES (1, '2012-03-01', 'T')
INSERT #STATUSES VALUES (2, '2012-01-01', 'A')
INSERT #STATUSES VALUES (2, '2012-02-14', 'A')
INSERT #STATUSES VALUES (2, '2012-03-10', 'A')
INSERT #STATUSES VALUES (3, '2012-02-01', 'A')
INSERT #STATUSES VALUES (3, '2012-03-17', 'A')
INSERT #STATUSES VALUES (3, '2012-03-18', 'T')
INSERT #STATUSES VALUES (3, '2012-04-01', 'A')
INSERT #STATUSES VALUES (4, '2012-03-01', 'A')
What query can be used to result in the following?
EMPLOYEE_ID EFFECTIVE_DATE STATUS_CODE
1 2012-01-01 A
1 2012-03-01 T
2 2012-01-01 A
3 2012-02-01 A
3 2012-03-18 T
3 2012-04-01 A
4 2012-03-01 A
In other words, I want to leave out those records that have the same employee id and status code as the one before it, if one exists with an earlier effective date. Notice that employee 1 is listed only twice because there were only two actual changes in status--the one on 2012-02-28 is inconsequential since the status didn't change from the earlier date. Also notice that employee 2 is listed just once since his status never changed despite there being three records. Only the earliest date is shown for each change.
With some further experimenting, it looks like this will do what I want.
;WITH cte
AS (SELECT ROW_NUMBER() OVER (PARTITION BY EMPLOYEE_ID ORDER BY EFFECTIVE_DATE) AS rownum
,EMPLOYEE_ID
,EFFECTIVE_DATE
,STATUS_CODE
FROM #STATUSES)
SELECT t2.EMPLOYEE_ID
,t2.EFFECTIVE_DATE
,t2.STATUS_CODE
FROM cte t2
LEFT JOIN cte t1
ON t2.EMPLOYEE_ID = t1.EMPLOYEE_ID
AND t2.STATUS_CODE = t1.STATUS_CODE
AND t2.rownum = t1.rownum + 1
WHERE t1.EMPLOYEE_ID IS NULL
You could use a CURSOR
You'd need two sets of variables: #PreviousRecord and #CurrentRecord
Declare the cursor for table sorted by employeeid and date
Fetch the first record from the cursor into the #PreviousRecord variables - depending on your requirement register this as a significant change or not (write the record to a temp table)
Then set up a loop that:
Fetches the next record into the #CurrentRecord variables
Compares it with the previous record and if it matches your requirement for a significant change write it to the temp table
Move the #CurrentRecord values into the #PreviousRecord variables
I'd be interested to know if the CTE method was more efficient
SELECT
EMPLOYEE_ID, MIN(EFFECTIVE_DATE) AS EFFECTIVE_DATE, STATUS_CODE
FROM
(
SELECT
T1.EMPLOYEE_ID, T1.EFFECTIVE_DATE, T1.STATUS_CODE,
MAX(T2.EFFECTIVE_DATE) AS MOST_RECENT_PREVIOUS_STATUS_DATE
FROM
#STATUSES T1
LEFT JOIN
#STATUSES T2
ON
T1.EMPLOYEE_ID = T2.EMPLOYEE_ID
AND
T1.EFFECTIVE_DATE > T2.EFFECTIVE_DATE
AND
T1.STATUS_CODE <> T2.STATUS_CODE
GROUP BY
T1.EMPLOYEE_ID, T1.EFFECTIVE_DATE, T1.STATUS_CODE
) SubQuery
GROUP BY
EMPLOYEE_ID, STATUS_CODE, MOST_RECENT_PREVIOUS_STATUS_DATE