SQL - carry previous Application's value onto next Application - sql-server

I have a table in SQL server, returning the results below, I am having trouble carrying over my SQNCarriedOver Value into the ThisAppOFFICIAL column on the next application.
I cannot do it based on the row number because the Project Names do not list in that order:
I was wondering if there was a way to carry the QSNCarried over value to the ThisAppOFFICIAL column in the next application with that Project Name,
So for Example:
The thisAppOfficial value on "12/06/2016" would be "10" (from in the QSCarriedOver on "12/05/2016")
I may have overlooked something, I have searched the web but I feel my question is quite specific.
Any help or advice is appreciated, Thank you in advance.
ps:
the next application is the next Application Date with the Same Project Name

Query:
SQLFIDDLEExample
SELECT t.Project,
t.AppDate,
t.thisAppOfficial,
COALESCE((SELECT TOP 1 t2.QSCarriedOver
FROM X t2
WHERE t2.Project = t.Project
AND t2.AppDate < t.AppDate
ORDER BY t2.AppDate desc), 0) as NewColumn,
t.QSCarriedOver
FROM X t
Result:
| Project | AppDate | thisAppOfficial | NewColumn | QSCarriedOver |
|---------|------------|-----------------|-----------|---------------|
| A | 2016-04-13 | 30 | 0 | 0 |
| A | 2016-05-12 | 30 | 0 | 10 |
| A | 2016-06-12 | 30 | 10 | 0 |
| A | 2016-07-12 | 30 | 0 | 0 |

I think I've got what you want and I'm sure that there is a better way of doing it. I've created a simpler version of your table for my needs. I then use ROW_NUMBER to put the rows into a sequence so I can do a sort of self-join to the previous row to get the carry forward figure
CREATE TABLE X (Project varchar(50), AppDate date, thisAppOfficial int, QSCarriedOver int)
inserT INTO X VALUES('A', '13 Apr 2016', 30,0)
inserT INTO X VALUES('A', '12 May 2016', 30,10)
inserT INTO X VALUES('A', '12 Jun 2016', 30,0)
inserT INTO X VALUES('A', '12 Jul 2016', 30,0)
SELECT X0.Project, X0.AppDate, X0.thisAppOfficial, X0.QSCarriedOver, ISNULL(X2.QSCarriedOver,0) as 'Brought forward from previous row' FROM X X0
JOIN (select ROW_NUMBER () OVER (ORDER BY Project, AppDate) as MainRow, * from X) X1 ON X1.Project = X0.Project AND X1.AppDate = X0.AppDate
LEFT OUTER JOIN (select ROW_NUMBER () OVER (ORDER BY Project, AppDate) as PrevRow, * FROM X) X2 ON X2.Project = X1.Project and X2.PrevRow = MainRow -1
order by Project, AppDate
Have a try with this and see if it is doing what you need, I'm not 100% sure I've understood your requirements so no problem if it isn't what you want.

Related

select / update SQL records that has a timestamp difference of more then 30 days

I need to select or update records from badge-records that have a date difference of more than 30 days after the last visit. A select query to find them is ok, so I can update them.
Difficult to explain in detail but I'll try with an example:
(This is an access system where people scan a badge and the timestamp is recorded.)
I only need to know the records when a badge has entered the system more than 30 days after the previous scan, + the very first scan.
The example table is showing the records I need from the table (i need 5 records)
Only records of the same badge number must be compared and updated.
Is this possible using TSQL ?
Example:
+------------------+--------------+
| TimeStamp | Badge |
+------------------+--------------+
| 19-10-2022 10:18 | Badge1 | <--- **select** (more the 30 days after previous scan)
| 01-01-2022 12:18 | Badge1 | <--- ok (less then 30 days)
| 08-12-2021 13:23 | Badge1 | <--- ok (less then 30 days)
| 20-11-2021 11:18 | Badge1 | <--- ok (less then 30 days)
| 22-10-2021 13:18 | Badge1 | <--- **select** (more the 30 days after previous scan)
| 23-08-2020 14:18 | Badge1 | <--- **select** (first entrance)
| 01-01-2022 09:18 | Badge12 | <--- ok (less then 30 days)
| 02-12-2021 10:18 | Badge12 | <--- **select** (more the 30 days after previous scan)
| 29-10-2021 23:18 | Badge12 | <--- ok (less then 30 days)
| 25-10-2021 12:18 | Badge12 | <--- **select** (first entrance)
+------------------+---------+----+
use this fiddle to have the example db and my wrong answer https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=c1528618004f0fe6bb6319e8e638abae
Help others help you. Post a script that contains DDL and sample data that can be used as the basis for writing code.
with cte as (
select *, ROW_NUMBER() over (partition by Badge order by Timestamp) as rno
from #x
)
select cte.*, prior.rno as prno, datediff(day, prior.TimeStamp, cte.Timestamp) as ddif
from cte
left join cte as prior on cte.badge = prior.badge and cte.rno - 1 = prior.rno
where cte.rno = 1 or datediff(day, prior.TimeStamp, cte.Timestamp) > 30
order by cte.Badge, cte.TimeStamp;
This should work but I have no way of testing on 2008. fiddle to demonstrate. Comment out the WHERE clause to see the all the rows and the columns that are computed for the query logic. This uses ROW_NUMBER to generate a sequence number and then simply self joins using that value to simulate LAG.
updated fiddle: https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=a24d23f54030d7aadd8f889819cd4512
;WITH Ordered AS (
SELECT *
, ROW_NUMBER() OVER (PARTITION BY Badge ORDER BY CONVERT(DATETIME, [scandate] ,103) DESC) rn
FROM History
)
SELECT M.*, DATEDIFF(dd, p.[scandate],m.[scandate]) DaysGap
FROM Ordered M
LEFT JOIN Ordered P
ON M.rn = P.rn-1
AND M.Badge = P.Badge
WHERE P.rn IS NULL -- first entrance
OR DATEDIFF(dd, p.[scandate],m.[scandate]) > 30

Compare value between current date and yesterday on the same table POSTGRESQL

First of all, i hope you guys understand my poor english :))
I have a table like this
product | value | trx_date
apple | 100 | 2020-06-01
apple | 300 | 2020-06-02
apple | 500 | 2020-06-03
and i need create a report like this (lets say today is 2020-06-03)
product | yesterday | current_date | delta
apple | 300 | 500 | 200
im confused how to create a query (postgre), comparing those value.. fyi, i always update this table everyday.. i tried with ('1 day'::interval) query but it always show all date before 2020-06-03 which is 2020-06-01 and 2020-06-02..
i appreciate for your help..
Use the Window Function lead or lag to 'combine' data to the current row from following rows (lead) or previous rows (lag). In this case the I use the lag function to get "yesterdays" value.
select product, yesterday, today, today-yesterday delta
from ( select p.product, p.value today
, lag(value) over (partition by p.product
order by p.trx_date) yesterday
, p.trx_date
from products p
) d
where trx_date = '2020-06-03'::date ;
Using CTE:
https://www.postgresql.org/docs/12/queries-with.html
An example:
CREATE TABLE product_table (product varchar, value integer, trx_date date);
INSERT INTO product_table values ('apple', 100, '06/01/2020'), ('apple', 300, '06/02/2020'), ('apple', 500, '06/03/2020');
WITH prev AS (
SELECT
product,
value
FROM
product_table
WHERE
trx_date = '06/03/2020'::date - '1 day'::interval
)
SELECT
pt.product,
prev.value AS yesterday,
pt.value AS CURRENT_DATE,
pt.value - prev.value AS delta
FROM
product_table AS pt,
prev
WHERE
trx_date = '06/03/2020';
product | yesterday | current_date | delta
---------+-----------+--------------+-------
apple | 300 | 500 | 200

SQL Server find sum of values based on criteria within another table

I have a table consisting of ID, Year, Value
---------------------------------------
| ID | Year | Value |
---------------------------------------
| 1 | 2006 | 100 |
| 1 | 2007 | 200 |
| 1 | 2008 | 150 |
| 1 | 2009 | 250 |
| 2 | 2005 | 50 |
| 2 | 2006 | 75 |
| 2 | 2007 | 65 |
---------------------------------------
I then create a derived, aggregated table consisting of an ID, MinYear, and MaxYear
---------------------------------------
| ID | MinYear | MaxYear |
---------------------------------------
| 1 | 2006 | 2009 |
| 2 | 2005 | 2007 |
---------------------------------------
I then want to find the sum of Values between the MinYear and MaxYear foreach ID in the aggregated table, but I am having trouble determining a proper query.
The final table should look something like this
----------------------------------------------------
| ID | MinYear | MaxYear | SumVal |
----------------------------------------------------
| 1 | 2006 | 2009 | 700 |
| 2 | 2005 | 2007 | 190 |
----------------------------------------------------
Right now I can perform all the joins to create the second table. But then I use a fast forward cursor to iterate through each record of the second table with the code inside the for loop looking like the following
DECLARE #curMin int
DECLARE #curMax int
DECLARE #curID int
FETCH Next FROM fastCursor INTo #curISIN, #curMin , #curMax
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT Sum(Value) FROM ValTable WHERE Year >= #curMin and Year <= #curMax and ID = #curID
Group By ID
FETCH Next FROM fastCursor INTo #curISIN, #curMin , #curMax
Having found the sum of values between specified years, I can connect it back to the second table and I wind up the desired result (the third table).
However, the second table in reality is roughly 4 million rows, so this iteration is extremely time consuming (~generating 300 results a minute) and presumably not the best solution.
My question is, is there a way to generate the third table's results without having to use a cursor/for loop?
During a group by the sum will only be for the ID in question -- since the min year and max year is for the ID itself then you don't need to double query. The query below should give you exactly what you need. If you have a different requirement let me know.
SELECT ID, MIN(YEAR) as MinYear, MAX(YEAR) as MaxYear, SUM(VALUE) as SUMVALUE
FROM tablenameyoudidnotsay
GROUP BY ID
You could use query as bellow
TableA is your first table, and TableB is the second one
SELECT *,
(select SUM(Value) FROM TableA where tablea.ID=TableB.ID AND tableA.Year BETWEEN
TableB.MinYear AND TableB.MaxYear) AS SumValue
from TableB
You can put your criteria into a join and obtain the result all as one set which should be faster:
SELECT b.Id, b.MinYear, b.MaxYear, sum(a.Value)
FROM Table2 b
JOIN Table1 a ON a.Id=b.Id AND b.MinYear <= a.Year AND b.MaxYear >= a.Year
GROUP BY b.Id, b.MinYear, b.MaxYear

Update record with previous row

I have a situation where I need to update the records with previous row value.
Source:
|MatId | BaseId |Flag|Pkg1| CS1
--------------------------------
|3001 | 3001 | 1 | 20 | 2 |
|3002 | 3001 | 0 | 15 | 3 |
|3003 | 3001 | 0 | 10 | 4 |
Here both 3001 (MatID) and 3001(BaseID) are same so FLAG =1, in the next record only BASEID is same. The output should be only PKG1 field updated with the current row value.
Target or output:
|MatId | BaseId|Flag|Pkg1|CS1
------------------------------
|3001 | 3001 | 1 | 20 | 2|
|3002 | 3001 | 0 | 20 | 3|
|3003 | 3001 | 0 | 20 | 4|
As seen in the target above i have to update the two values in PKG1 with the value from first record 20. Also there are many columns with Pkg1, how to update all the columns with a single query?
Any help is very much appreciated.
Thanks.
To get Previous and Next value with the help of LEAD and LAG Function in SQL Server is very simple. If you are using an earlier version of SQL Server than 2012 which does not support LEAD and LAG function we can use ROW_NUMBER().
Try to use something like this:
;WITH t AS
(
select LAG(MatId) OVER (ORDER BY MatId) AS previousMatId
, BaseId
, MatId
from TABLE
)
update tab
set tab.Pkg1 = p.Pkg1
from TABLE tab
inner join t on tab.MatId = t.MatId and t.BaseId = t.previousMatId
left join (select MatId AS MatId
, ISNULL(LAG(Pkg1) OVER (ORDER BY MatId), Pkg1) AS Pkg1
from TABLE) p on t.MatId = p.MatId
Are you saying the newer mats need to be updated with the Pkg1 belonging to the original mat? If so it would be:
update NewMats
set NewMats.Pkg1 = Base.Pkg1
from MyTabe as NewMats
inner join (select BaseId, Pkg1
from MyTable
where BaseId = MatId) as Base
on Base.BaseId = NewMats.BaseId
where NewMats.BaseId < NewMats.MatId
But if this is the case, then your data model needs to be changed. The rule is that a given piece of information should live in only one place. So maybe break this out into 2 tables that are related.

SQL Query for time attendance for a month

Anyone can help with this one please? Our attendance system generates the following data:
User Department Date Time Reader
A1 IT 1/3/2014 11:12:00 1
B1 IT 1/3/2014 12:28:06 1
B1 IT 1/3/2014 12:28:07 1
A1 IT 1/3/2014 13:12:00 2
B1 IT 1/3/2014 13:28:06 2
A1 IT 2/3/2014 07:42:15 1
A1 IT 2/3/2014 16:16:15 2
Where Reader value,
1 = Entry
2 = Exit
I'm looking for SQL query to run on MS SQL 2005 that summarize attendance time for each employee on monthly basis, for instance
User Department Month Time
A1 IT 3/2014 10.34
B1 IT 3/2014 01:00
This is a fairly difficult problem to solve with SQL due to the need to find transitions and ranges in the data, which is not trivial. I've broken the problem down into a series of steps made of successive cte's that build on one another and lead up to a final working solution:
First, I add a row index to the data to provide a simple PK for identifying a unique row:
with NumberedAtt as (
select
row_number() over (partition by [user] order by date, time, reader) as ix,
att.[user],
att.[department],
att.[date] + att.[time] as dt,
att.[reader]
from att
)
Then I grab the first and last index value per user which will be used for the outermost boundaries of each entry/exit range:
, MinMax as (
select [user], min(ix) ixMin, max(ix) ixMax
from NumberedAtt N group by [user]
)
Next I put these together to generate a list of all exit/entry ranges, which are the points where the value of Reader changes from 2 to 1. These are the specific points that exactly identify when a previous time range ends, and the next time range begins (and cleanly handles successive duplicate entry/exit reads). By combining this with the first entry and last exit time for each user, a list of all entry/exit transitions is generated:
, Transitions as (
select N.[User], 0 as exitIx, M.ixMin as entryIx
from NumberedAtt N
join MinMax M on N.[User] = M.[User]
where N.ix = M.ixMin
union
select N.[User], M.ixMax as exitIx, 0 as entryIx
from NumberedAtt N
join MinMax M on N.[User] = M.[User]
where N.ix = M.ixMax
union
select A1.[User], A1.ix as exitIx, A2.ix as entryIx
from NumberedAtt A1
join NumberedAtt A2 on A1.ix + 1 = A2.ix and A1.[user] = A2.[user]
where A1.[reader] = 2 and A2.[reader] = 1
)
Here is the output at this point:
| USER | EXITIX | ENTRYIX |
|------|--------|---------|
| A1 | 0 | 1 |
| A1 | 2 | 3 |
| A1 | 4 | 0 |
| B1 | 0 | 1 |
| B1 | 3 | 0 |
Notice that we've neatly captured all of the row indexes where a range of time begins and ends. However, they are offset by one - that is the entry time in one row corresponds to the exit time in the next row. So we need one more transformation to bring the ranges back together by adding a row index to this table and joining each row with the following row:
, NumberedTransitions as (
select
row_number() over (partition by [User] order by exitIx) tix,
T.*
from Transitions T
), EntryExit as (
select
aEntry.ix as ixEntry,
aExit.ix as ixExit,
aEntry.[user],
aEntry.[department],
aEntry.[dt] as entryDT,
aExit.[dt] as exitDT
from NumberedTransitions tEntry
join NumberedAtt aEntry on tEntry.entryIx = aEntry.ix and tEntry.[user] = aEntry.[user]
join NumberedTransitions tExit on tEntry.tix + 1 = tExit.tix and tEntry.[user] = tExit.[user]
join NumberedAtt aExit on tExit.exitIx = aExit.ix and tExit.[user] = aExit.[user]
)
After joining the successive ranges together, I also pull in the original detail data back in, since I've been working only with the row index values so far. At the end of this subquery, we have identified all the entry/exit ranges per user and "swallowed up" any multiple reads:
| IXENTRY | IXEXIT | USER | DEPARTMENT | ENTRYDT | EXITDT |
|---------|--------|------|------------|------------------------------|------------------------------|
| 1 | 2 | A1 | IT | March, 01 2014 11:12:00+0000 | March, 01 2014 13:12:00+0000 |
| 3 | 4 | A1 | IT | March, 02 2014 07:42:15+0000 | March, 02 2014 16:16:15+0000 |
| 1 | 3 | B1 | IT | March, 01 2014 12:28:06+0000 | March, 01 2014 13:28:06+0000 |
Now the only thing left to do is group the data together to report on total hours per user, per month. It is a little tricky to calculate the total hours, but it can be done by taking the sum of minutes between the ranges and then converting the result back into a time value:
, Hours as (
select
[User],
[Department],
Year(EntryDT) Year,
Month(EntryDT) Month,
RIGHT('0' + CAST(SUM(DATEDIFF(Minute, EntryDT, ExitDT)) / 60 as varchar(10)), 2) + ':' +
RIGHT('0' + CAST(SUM(DATEDIFF(Minute, EntryDT, ExitDT)) % 60 as varchar(2)), 2) as TotalHours
from EntryExit EE
group by [User], [Department], Year(EntryDT), Month(EntryDT)
)
This gives a final result which is very close to the desired result:
| USER | DEPARTMENT | YEAR | MONTH | TOTALHOURS |
|------|------------|------|-------|------------|
| A1 | IT | 2014 | 3 | 10:34:00 |
| B1 | IT | 2014 | 3 | 01:00:00 |
A few tweaks could be made to the formatting as desired, but that should be easy to build on top of this framework.
Here is a working demo: http://www.sqlfiddle.com/#!3/f3f37/7

Resources