How to determine an Increase in Employee Salary from consecutive Contract Rows? - sql

I got a problem in my query :
My table store data like this
ContractID | Staff_ID | EffectDate | End Date | Salary | active
-------------------------------------------------------------------------
1 | 1 | 2013-01-01 | 2013-12-30 | 100 | 0
2 | 1 | 2014-01-01 | 2014-12-30 | 150 | 0
3 | 1 | 2015-01-01 | 2015-12-30 | 200 | 1
4 | 2 | 2014-05-01 | 2015-04-30 | 500 | 0
5 | 2 | 2015-05-01 | 2016-04-30 | 700 | 1
I would like to write a query like below:
ContractID | Staff_ID | EffectDate | End Date | Salary | Increase
-------------------------------------------------------------------------
1 | 1 | 2013-01-01 | 2013-12-30 | 100 | 0
2 | 1 | 2014-01-01 | 2014-12-30 | 150 | 50
3 | 1 | 2015-01-01 | 2015-12-30 | 200 | 50
4 | 2 | 2014-05-01 | 2015-04-30 | 500 | 0
5 | 2 | 2015-05-01 | 2016-04-30 | 700 | 200
-------------------------------------------------------------------------
Increase column is calculated by current contract minus previous contract
I use sql server 2008 R2

Unfortunately 2008R2 doesn't have access to LAG, but you can simulate the effect of obtaining the previous row (prev) in the scope of a current row (cur), with a RANKing and a self join to the previous ranked row, in the same partition by Staff_ID):
With CTE AS
(
SELECT [ContractID], [Staff_ID], [EffectDate], [End Date], [Salary],[active],
ROW_NUMBER() OVER (Partition BY Staff_ID ORDER BY ContractID) AS Rnk
FROM Table1
)
SELECT cur.[ContractID], cur.[Staff_ID], cur.[EffectDate], cur.[End Date],
cur.[Salary], cur.Rnk,
CASE WHEN (cur.Rnk = 1) THEN 0 -- i.e. baseline salary
ELSE cur.Salary - prev.Salary END AS Increase
FROM CTE cur
LEFT OUTER JOIN CTE prev
ON cur.[Staff_ID] = prev.Staff_ID and cur.Rnk - 1 = prev.Rnk;
(If ContractId is always perfectly incrementing, we wouldn't need the ROW_NUMBER and could join on incrementing ContractIds, I didn't want to make this assumption).
SqlFiddle here
Edit
If you have Sql 2012 and later, the LEAD and LAG Analytic Functions make this kind of query much simpler:
SELECT [ContractID], [Staff_ID], [EffectDate], [End Date], [Salary],
Salary - LAG(Salary, 1, Salary) OVER (Partition BY Staff_ID ORDER BY ContractID) AS Incr
FROM Table1
Updated SqlFiddle
One trick here is that we are calculating delta increments in salary, so for the first employee contract we need to return the current salary so that Salary - Salary = 0 for the first increase.

Related

SQL Server - Counting total number of days user had active contracts

I want to count the number of days while user had active contract based on table with start and end dates for each service contract. I want to count the time of any activity, no matter if the customer had 1 or 5 contracts active at same time.
+---------+-------------+------------+------------+
| USER_ID | CONTRACT_ID | START_DATE | END_DATE |
+---------+-------------+------------+------------+
| 1 | 14 | 18.02.2021 | 18.04.2022 |
| 1 | 13 | 02.01.2019 | 02.01.2020 |
| 1 | 12 | 01.01.2018 | 01.01.2019 |
| 1 | 11 | 13.02.2017 | 13.02.2019 |
| 2 | 23 | 19.06.2021 | 18.04.2022 |
| 2 | 22 | 01.07.2019 | 01.07.2020 |
| 2 | 21 | 19.01.2019 | 19.01.2020 |
+---------+-------------+------------+------------+
In result I want a table:
+---------+--------------------+
| USER_ID | DAYS_BEEING_ACTIVE |
+---------+--------------------+
| 1 | 1477 |
| 2 | 832 |
+---------+--------------------+
Where
1477 stands by 1053 (days from 13.02.2017 to 02.01.2020 - user had active contracts during this time) + 424 (days from 18.02.2021 to 18.04.2022)
832 stands by 529 (days from 19.01.2019 to 01.07.2020) + 303 (days from 19.06.2021 to 18.04.2022).
I tried some queries with joins, datediff's, case when conditions but nothing worked. I'll be grateful for any help.
If you don't have a Tally/Numbers table (highly recommended), you can use an ad-hoc tally/numbers table
Example or dbFiddle
Select User_ID
,Days = count(DISTINCT dateadd(DAY,N,Start_Date))
from YourTable A
Join ( Select Top 10000 N=Row_Number() Over (Order By (Select NULL))
From master..spt_values n1, master..spt_values n2
) B
On N<=DateDiff(DAY,Start_Date,End_Date)
Group By User_ID
Results
User_ID Days
1 1477
2 832

Find the first order of a supplier in a day using SQL

I am trying to write a query to return supplier ID (sup_id), order date and the order ID of the first order (based on earliest time).
+--------+--------+------------+--------+-----------------+
|orderid | sup_id | items | sales | order_ts |
+--------+--------+------------+--------+-----------------+
|1111132 | 3 | 1 | 27,0 | 24/04/17 13:00 |
|1111137 | 3 | 2 | 69,0 | 02/02/17 16:30 |
|1111147 | 1 | 1 | 87,0 | 25/04/17 08:25 |
|1111153 | 1 | 3 | 82,0 | 05/11/17 10:30 |
|1111155 | 2 | 1 | 29,0 | 03/07/17 02:30 |
|1111160 | 2 | 2 | 44,0 | 30/01/17 20:45 |
|....... | ... | ... | ... | ... ... |
+--------+--------+------------+--------+-----------------+
Output I am looking for:
+--------+--------+------------+
| sup_id | date | order_id |
+--------+--------+------------+
|....... | ... | ... |
+--------+--------+------------+
I tried using a subquery in the join clause as below but didn't know how to join it without having selected order_id.
SELECT sup_id, date(order_ts), order_id
FROM sales s
JOIN
(
SELECT sup_id, date(order_ts) as date, min(time(order_date))
FROM sales
GROUP BY merchant_id, date
) m
on ...
Kindly assist.
You can use not exists:
select *
from sales
where not exists (
-- find sales for same supplier, earlier date, same day
select *
from sales as older
where older.sup_id = sales.sup_id
and older.order_ts < sales.order_ts
and older.order_ts >= cast(sales.order_ts as date)
)
The query below might not be the fastest in the world, but it should give you all information you need.
select order_id, sup_id, items, sales, order_ts
from sales s
where order_ts <= (
select min(order_ts)
from sales m
where m.sup_id = s.sup_id
)
select sup_id, min(order_ts), min(order_id) from sales
where order_ts = '2022-15-03'
group by sup_id
Assumed orderid is an identity / auto increment column

Return dates on which a team member was called back in more than once

I am working in Azure SQL DB (SQL Server) and having trouble with a query that has grown more complex since I began working on the problem. I am working with timekeeping data that unfortunately, is not the cleanest.
Problem Statement:
Return results showing team members who were called in to work (a specific call-in PaycodeID - "CB"), then sent home and transferred to an on-call status (this creates a new row with a different on-call PaycodeID - "OC"), and were later called back into work (a third row with the call-in PaycodeID - "CB"). In plain English, the requirement is to find instances were an on-call employee was called into work more than once during a shift.
The Dataset:
| RowID | EmployeeID | Shift Date | StartDT | EndDT | PaycodeID |
|-------|------------|------------|---------------------|---------------------|-----------|
| 1 | 123 | 2020-02-13 | 2020-02-13 17:30:00 | 2020-02-13 19:00:00 | CB |
| 2 | 123 | 2020-02-13 | 2020-02-13 19:00:00 | 2020-02-13 23:00:00 | OC |
| 3 | 123 | 2020-02-13 | 2020-02-13 23:00:00 | 2020-02-14 03:00:00 | CB |
| 4 | 456 | 2020-01-01 | 2020-01-01 06:00:00 | 2020-01-01 09:30:00 | OC |
| 5 | 456 | 2020-01-01 | 2020-01-01 09:30:00 | 2020-01-01 12:00:00 | CB |
| 6 | 456 | 2020-01-01 | 2020-01-01 12:30:00 | 2020-01-01 16:45:00 | CB |
| 7 | 456 | 2020-01-01 | 2020-01-01 16:45:00 | 2020-01-01 18:00:00 | OC |
T-SQL Query I tried:
SELECT
,[EmployeeID]
,[Shift Date]
,SUM(
CASE WHEN [PaycodeID] = "OC"
THEN 1
ELSE 0
END
)
AS [On-Call Count]
,SUM(
CASE WHEN [PaycodeID] = "CB"
THEN 1
ELSE 0
END
)
AS [Call Back Count]
FROM
#OnCallTable
GROUP BY [Employee ID], [Shift Date]
ORDER BY [Employee ID], [Shift Date]
Results of this query:
| EmployeeID | Shift Date | On-Call Count | Call Back Count |
|------------|------------|---------------|-----------------|
| 123 | 2020-02-13 | 1 | 2 |
| 456 | 2020-01-01 | 2 | 2 |
I was then planning on selecting EmployeeID and Shift Date where the [Call Back Count] > 1. However, this would return both records in the above result set whereas only the first row should be returned. If you look back at my original dataset, employee 456 was only called into work once and ended up with two "CB" rows because they clocked out for a break at 12:00:00. I am trying to design a query that will only return shifts in which an employee has an entry of "OC" where the timestamp is between two or more "CB" entries.
Any ideas on how to approach this problem would be greatly appreciated.
If I followed you correctly, you can solve this with lead() and lag():
select employeeID, shiftDate
from (
select
oc.*,
lead(startDT) over(partition by employeeID, shiftDate order by rowID) leadStartDT,
lead(paycodeID) over(partition by employeeID, shiftDate order by rowID) leadPaycodeID,
lag(endDT) over(partition by employeeID, shiftDate order by rowID) lagEndDT,
lag(paycodeID) over(partition by employeeID, shiftDate order by rowID) lagPaycodeID
from #onCallTable oc
) t
where
paycodeID = 'OC'
and lagPaycodeID = 'CB'
and leadPaycodeID = 'CB'
and lagEndDT = startDT
and leadStartDT = endDT
This brings rows with paycode OC, surrounded with paycodes CB, and whose dates are contiguous with the surrounding records.

SQL server delete all rows that have a duplicate (inclusive)

I have a table named Sales:
+----------+-----------------+------------+
| Salesman | Sales Portfolio | Month |
+----------+-----------------+------------+
| Kavi | 12500 | 2018-01-05 |
| Kavi | 12500 | 2018-02-28 |
| Kavi | 12500 | 2018-03-20 |
| Raj | 21055 | 2018-01-05 |
| Raj | 32015 | 2018-02-28 |
| Raj | 12000 | 2018-03-20 |
+----------+-----------------+------------+
If a Sales Portfolio value is duplicated, remove all rows including itself from the table. In the example above, 12500 is duplicated, so remove all rows where Sales Portfolio = 12500.
Example expected output (only Raj displayed):
If you just want to display your expected output, then try the following:
WITH cte AS (
SELECT *,
COUNT(*) OVER (PARTITION BY Salesman, [Sales Portfolio]) cnt
FROM yourTable
)
SELECT
Salesman, [Sales Portfolio], Month
FROM cte
WHERE cnt = 1;
If you want to delete the non displaying records as well, then we can use the same CTE:
DELETE FROM cte WHERE cnt > 1;

SQL Query to Join Two Tables Based On Closest Timestamp

I need to retrieve the records from dbo.transaction (transaction of all users-more than one transaction for each user) that having timestamp which is closest to the time in dbo.bal (current balance details of each user-only one record for each user)
ie, the resultant records should equal to the no of records in the dbo.bal
Here i tried the below query, am getting only the records less than the time in dbo.bal. But there are some record having timestamp greater than and closest to dbo.bal.time
SELECT dbo.bal.uid,
dbo.bal.userId,
dbo.bal.balance,
dbo.bal.time,
(SELECT TOP 1 transactionBal
FROM dbo.transaction
WHERE TIMESTAMP <= dbo.bal.time
ORDER BY TIMESTAMP DESC) AS newBal
FROM dbo.bal
WHERE dbo.bal.time IS NOT NULL
ORDER BY dbo.bal.time DESC
here is my table structure,
dbo.transaction
---------------
| uid| userId | description| timestamp | credit | transactionBal
-------------------------------------------------------------------------
| 1 | 101 | buy credit1| 2012-01-25 03:23:31.624 | 100 | 500
| 2 | 102 | buy credit5| 2012-01-18 03:13:12.657 | 500 | 700
| 3 | 103 | buy credit3| 2012-01-15 02:16:34.667 | 300 | 300
| 4 | 101 | buy credit2| 2012-01-13 05:34:45.637 | 200 | 300
| 5 | 101 | buy credit1| 2012-01-12 07:45:21.457 | 100 | 100
| 6 | 102 | buy credit2| 2012-01-01 08:18:34.677 | 200 | 200
dbo.bal
-------
| uid| userId | balance | time |
-----------------------------------------------------
| 1 | 101 | 500 | 2012-01-13 05:34:45.645 |
| 2 | 102 | 700 | 2012-01-01 08:18:34.685 |
| 3 | 103 | 300 | 2012-01-15 02:16:34.672 |
And the result should be like,
| Id | userId | balance | time | credit | transactionBal
-----------------------------------------------------------------------------
| 1 | 101 | 500 | 2012-01-13 05:34:45.645 | 200 | 300
| 2 | 102 | 700 | 2012-01-01 08:18:34.685 | 200 | 200
| 3 | 103 | 300 | 2012-01-15 02:16:34.672 | 300 | 300
Please help me.. Any help is must appreciated...Thankyou
It would be helpful if you posted your table structures, but ...
I think your inner query needs a join condition. (That is not actually in your question)
Your ORDER BY clause in the inner query could be ABS(TIMESTAMP - DB0.BAL.TIME). That should give you the smallest difference between the 2.
Does that help ?
Based on the follwing Sql Fiddle http://sqlfiddle.com/#!3/7a900/15 I came up with ...
SELECT
bal.uid,
bal.userId,
bal.balance,
bal.time,
trn.timestamp,
trn.description,
datediff(ms, bal.time, trn.timestamp)
FROM
money_balances bal
JOIN money_transaction trn on
trn.userid = bal.userid and
trn.uid =
(
select top 1 uid
from money_transaction trn2
where trn2.userid = trn.userid
order by abs(datediff(ms, bal.time, trn2.timestamp))
)
WHERE
bal.time IS NOT NULL
ORDER BY
bal.time DESC
I cannot vouch for its performance because I know nothing of your data, but I believe it works.
I have simplified my answer - I believe what you need is
SELECT
bal.uid as baluid,
(
select top 1 uid
from money_transaction trn2
where trn2.userid = bal.userid
order by abs(datediff(ms, bal.time, trn2.timestamp))
) as tranuid
FROM
money_balances bal
and from that you can derive all the datasets you need.
for example :
with matched_credits as
(
SELECT
bal.uid as baluid,
(
select top 1 uid
from money_transaction trn2
where trn2.userid = bal.userid
order by abs(datediff(ms, bal.time, trn2.timestamp))
) as tranuid
FROM
money_balances bal
)
select
*
from
matched_credits mc
join money_balances mb on
mb.uid = mc.baluid
join money_transaction trn on
trn.uid = mc.tranuid
Try:
SELECT dbo.bal.uid,
dbo.bal.userId,
dbo.bal.balance,
dbo.bal.time,
(SELECT TOP 1 transactionBal
FROM dbo.transaction
ORDER BY abs(datediff(ms, dbo.bal.time, TIMESTAMP))) AS newBal
FROM dbo.bal
WHERE dbo.bal.time IS NOT NULL
ORDER BY dbo.bal.time DESC