PostgreSQL Update with date range - sql

I have this statement that works, but it's unbridled, as in it SUMs up the whole column, I want introduce date range but I'm getting an error.
HERE IS WHAT IS WORKING
UPDATE payroll_employee e
SET hours = l.total
FROM (SELECT employee, SUM(end_date - start_date) AS total
FROM payroll_timelog
GROUP BY employee) l
WHERE e.id = l.employee
I want to introduce a date filter.
Then I tried this CTE,
WITH cte AS (
SELECT employee_id, end_date SUM(end_date - start_date) AS total
FROM payroll_timelog
WHERE employee_id = 1, AND end_date > 2020-09-01
)
UPDATE payroll_employee e
SET hours = total
FROM cte
WHERE e.id = 1;
Tried casting date to 2020-09-01::date, 2020-09-01::timestamp etc, still won't work. any help will be appreciated.

I think you just have syntax errors:
WITH cte AS (
SELECT employee_id, SUM(end_date - start_date) AS total
FROM payroll_timelog
WHERE employee_id = 1 AND end_date > '2020-09-01'
GROUP BY employee_id
)
UPDATE payroll_employee e
SET hours = cte.total
FROM cte
WHERE e.id = 1;

Related

Find the max date to last one year transaction for each group

I have to query in sql server where I have to find for each id it's volume such that we have last 1 year date for each id with it's volume.
for example below is my data ,
for each id I need to query the last 1 year transaction from when we have the entry for that id as you can see from the snippet for id 1 we have the latest date as 7/31/2020 so I need the last 1 year entry from that date for that id, The highlighted one is exclude because that date is more than 1 year from the latest date for that id
Similarly for Id 3 we have all the date range in one year from the latest date for that particular id
I tried using the below query and I can get the latest date for each id but I am not sure how to extract all the dates for each id from the latest date to one year, I would appreciate if some one could help me.
I am using Microsoft sql server would need the query which executes in sql server, Table name is emp and have millions of id
Select *
From emp as t
inner join (
Select tm.id, max(tm.date_tran) as MaxDate
From emp tm
Group by tm.id
) tm on t.id = tm.id and t.date_tran = tm.MaxDate
To exclude transactions where the date difference between the tran_date and the maximum tran_date for each id is greater than 1 year, something like this:
;with max_cte(id, max_date) as (
Select id, max(date_tran)
From emp tm
Group by id )
Select *
From emp e
join max_cte mc on e.id=mc.id
and datediff(d, e.date_tran, mc.max_date)<=365;
Update: per comments, added volume. Thnx GMB :)
;with max_cte(id, date_tran, volume, max_date) as (
Select *, dateadd(year, -1, max(date_tran) over(partition by id)) max_date
From #emp tm)
Select id, sum(volume) sum_volume
From max_cte mc
where mc.date_tran>max_date
group by id;
You can do this with window functions:
select id, sum(volume) total_volume
from (
select t.*, max(date_tran) over(partition by id) max_date_tran
from mytable t
) t
where date_tran > dateadd(year, -1, max_date_tran)
group by id
Alternatively, you can use a correlated subquery for filtering:
select id, sum(volume) total_volume
from mytable t
where t.date_tran > (
select dateadd(year, -1, max(t1.date_tran))
from mytable t1
where t1.id = t.id
)
The second query would take advantage of an index on (id, date_tran).
this should do the trick for you:
SELECT
*
FROM
emp
JOIN
(
SELECT
MAX(date_tran) max_date_tran
, Id
FROM
emp
GROUP BY
id
) emp2
ON emp2.Id = emp.Id
AND DATEADD(YEAR, -1, emp2.max_date_tran) <= emp.date_tran;
Your code is good. Just add the date difference function to get the particular time in between the transaction, like the following:
Select *
From emp as t
inner join ( Select id as id, max(date_tran) as maxdate
From emp tm
Group by id
) tm on t.id = tm.id and datediff(d, e.date_tran, mc.maxdate)<=365;

Oracle query to find employee who has taken maximum number of leaves in last 1 month

I have these tables with the following columns :
Employee24( EMPLOYEEID, FIRSTNAME, LASTNAME, GENDER );
Leave25( EMPLOYEEID,LEAVEID, LEAVETYPE, STARTDATE, ENDDATE, NOOFDAYS );
I want to write a query to find employee who has taken maximum number of leaves in last 1 month
SELECT *
FROM EMPLOYEE24
WHERE EMPLOYEEID IN (SELECT EMPLOYEEID
FROM LEAVE25
WHERE STARTDATE < ADD_MONTHS(SYSDATE, -1));
If your DB version is 12c, you may use Row Limiting Clause for Top-N Queries as below :
SELECT e.*, l.max_leaves
FROM (SELECT employeeid, count(1) as max_leaves
FROM LEAVE25
WHERE startdate >= add_months(sysdate, -1)
GROUP BY employeeid
) l JOIN
EMPLOYEE24 e
ON ( e.employeeid = l.employeeid )
ORDER BY l.max_leaves DESC
FETCH FIRST 1 ROWS WITH TIES; -- including the same highest leave owners
If version is 11g, then use Dense_Rank and Count with a nested query as below :
SELECT e.*, l.max_leaves
FROM (SELECT employeeid, count(1) as max_leaves,
dense_rank() over (order by count(1) desc) dr
FROM LEAVE25
WHERE startdate >= add_months(sysdate, -1)
GROUP BY employeeid
) l JOIN
EMPLOYEE24 e
ON ( e.employeeid = l.employeeid )
WHERE l.dr = 1;
SQL Fiddle Demo for 11g

How to continous count by using SQL Oracle

Regarding my table as below
WORKING_CALENDAR_TABLE
===================================================
EMPLOYEE ID | DATE | WORKING DAY (0: Holiday; 1: WORKING DAY)
===================================================
02661 2017/12/01 1
02661 2017/12/02 1
02661 2017/12/03 0
02661 2017/12/04 0
02661 2017/12/05 0
02661 2017/12/06 1
02661 2017/12/07 1
02661 2017/12/08 1
02661 2017/12/09 1
When 2017/12/10, my expected result as below
===================================================
EMPLOYEE ID | CONTINOUS WORKING DAY
===================================================
02661 4
IF WE USE SQL ORACLE, CAN WE UTILIZE SQL ORACLE to got this result ?
One way of doing it can be by:
Getting the last day before your reference date (2017/12/10) where the employee didnĀ“t work.
Counting the rows after the date in 1. and before your reference date. Forcibly, each row represents a working day for the employee.
Here's the code with some comments:
Select employee_id, count(*) as continuous_days from mytable
where employee_id = '02661' and date > (select max(date)
where employee_id ='02661' and working_day = 0 and date date <
'2017/12/10') and date <
'2017/12/10' group by employee_id
/* (select max(date)
where employee_id ='02661' and working_day = 0 and date <
'2017/12/10') gets the last day where the employee didn't work before the reference date. Each row from a date after max(date) represent a working day for the employee because all of them are going to to have working_day = 1*/
Something to improve here:
In the case the employee had no holidays before a reference date then
select max(date)
where employee_id ='02661' and working_day = 0 and date date <
'2017/12/10' will return null so you could use COALESCE to prevent errors and in the case of a null, you get some other value in return. I think that in your case it would suffice with a very early date. You could use it this way:
COALESCE( (select max(date)
where employee_id ='02661' and working_day = 0 and date <
'2017/12/10'), '1900-01-01')
and the complete query would be:
select employee_id, count(*) as continuous_days from mytable
where employee_id = '02661' and date > COALESCE( (select max(date)
where employee_id ='02661' and working_day = 0 and date <
'2017/12/10'), '1900-01-01') and date <
'2017/12/10' group by employee_id
For a given date, the most general solution is:
select employee_id, count(*)
from t
where employee_id = '02661' and
date < date '2017-12-10' and
date > (select max(t2.date)
from t t2
where t2.employee_id = t.employee_id and t2.date < date '2017-12-10'
);
You can try this query:
SELECT T.EMPLOYEE_ID,
COUNT(0) AS CONTINUOUS_DAYS
FROM WORKING_CALENDAR_TABLE T
WHERE T.WORK_DATE BETWEEN (SELECT MAX(WORK_DATE) + 1
FROM WORKING_CALENDAR_TABLE I
WHERE I.EMPLOYEE_ID = T.EMPLOYEE_ID
AND I.WORK_DAY = 0)
AND DATE '2017-12-10' + 1
GROUP BY T.EMPLOYEE_ID

How to simplify this oracle sql query?

I have written following select to get the previous different grade value from jobs table.
This works well but is it possible to simplify the code that it won't have 3 levels?
select value_1
from ( select distinct
value_1,
date_from,
date_to,
emp_id,
(select o.value_1
from jobs o
where o.emp_id=w.emp_id
and (
(o.date_to >= sysdate and o.date_from <= sysdate) or
(o.data_from <= sysdate and o.data_to is null)
)
) current_grade
from jobs w
where w.emp_id = t.emp_id
order by data_from desc
)
where value_1 != current_grade
and data_from <= sysdate
and rownum=1
and t.emp_id=123
order by data_from desc,
value_1,
emp_id
What it suppose to do? I want to select previous different grade value from jobs table. This table is used to store positions for each employee, they have date_from, date_to, additionally in value_1 we store the grade symbol. What is important for me is to select previous different value for grade which could have changed 3 positions before.
I don't think you can get away from a three-level query in this instance, but it can be simplified. As I noted in my comment, the ORDER BY in the outer query is superfluous, and you would actually get incorrect results if the ORDER BY in the second query was not there. Oracle's rownum does not work like other databases' Top-N queries -- rownum is calculated before order by, so using rownum= with an ORDER BY will not necessarily return the highest row.
This should produce the desired result, and is slightly more compact:
SELECT
value_1
FROM
(
SELECT
value_1
FROM
jobs w
WHERE
date_from <= sysdate
and emp_id=123
and value_1 != (SELECT value_1
FROM jobs o
WHERE o.emp_id = w.emp_id
AND (o.date_to >= sysdate and o.date_from <= sysdate
OR o.date_from <= sysdate and o.date_to is null))
ORDER BY date_from desc
)
WHERE
rownum = 1
SQLFiddle here
You can do it with a single table hit by getting value_1 of latest date_to value in the past.
select value_1 from jobs where date_to < sysdate and emp_id = 123
If you need the latest job role do a order by desc and get first row.

Total Count of Active Employees by Date

I have in the past written queries that give me counts by date (hires, terminations, etc...) as follows:
SELECT per.date_start AS "Date",
COUNT(peo.EMPLOYEE_NUMBER) AS "Hires"
FROM hr.per_all_people_f peo,
hr.per_periods_of_service per
WHERE per.date_start BETWEEN peo.effective_start_date AND peo.EFFECTIVE_END_DATE
AND per.date_start BETWEEN :PerStart AND :PerEnd
AND per.person_id = peo.person_id
GROUP BY per.date_start
I was now looking to create a count of active employees by date, however I am not sure how I would date the query as I use a range to determine active as such:
SELECT COUNT(peo.EMPLOYEE_NUMBER) AS "CT"
FROM hr.per_all_people_f peo
WHERE peo.current_employee_flag = 'Y'
and TRUNC(sysdate) BETWEEN peo.effective_start_date AND peo.EFFECTIVE_END_DATE
Here is a simple way to get started. This works for all the effective and end dates in your data:
select thedate,
SUM(num) over (order by thedate) as numActives
from ((select effective_start_date as thedate, 1 as num from hr.per_periods_of_service) union all
(select effective_end_date as thedate, -1 as num from hr.per_periods_of_service)
) dates
It works by adding one person for each start and subtracting one for each end (via num) and doing a cumulative sum. This might have duplicates dates, so you might also do an aggregation to eliminate those duplicates:
select thedate, max(numActives)
from (select thedate,
SUM(num) over (order by thedate) as numActives
from ((select effective_start_date as thedate, 1 as num from hr.per_periods_of_service) union all
(select effective_end_date as thedate, -1 as num from hr.per_periods_of_service)
) dates
) t
group by thedate;
If you really want all dates, then it is best to start with a calendar table, and use a simple variation on your original query:
select c.thedate, count(*) as NumActives
from calendar c left outer join
hr.per_periods_of_service pos
on c.thedate between pos.effective_start_date and pos.effective_end_date
group by c.thedate;
If you want to count all employees who were active during the entire input date range
SELECT COUNT(peo.EMPLOYEE_NUMBER) AS "CT"
FROM hr.per_all_people_f peo
WHERE peo.[EFFECTIVE_START_DATE] <= :StartDate
AND (peo.[EFFECTIVE_END_DATE] IS NULL OR peo.[EFFECTIVE_END_DATE] >= :EndDate)
Here is my example based on Gordon Linoff answer
with a little modification, because in SUBSTRACT table all records were appeared with -1 in NUM, even if no date was in END DATE = NULL.
use AdventureWorksDW2012 --using in MS SSMS for choosing DATABASE to work with
-- and may be not work in other platforms
select
t.thedate
,max(t.numActives) AS "Total Active Employees"
from (
select
dates.thedate
,SUM(dates.num) over (order by dates.thedate) as numActives
from
(
(
select
StartDate as thedate
,1 as num
from DimEmployee
)
union all
(
select
EndDate as thedate
,-1 as num
from DimEmployee
where EndDate IS NOT NULL
)
) AS dates
) AS t
group by thedate
ORDER BY thedate
worked for me, hope it will help somebody
I was able to get the results I was looking for with the following:
--Active Team Members by Date
SELECT "a_date",
COUNT(peo.EMPLOYEE_NUMBER) AS "CT"
FROM hr.per_all_people_f peo,
(SELECT DATE '2012-04-01'-1 + LEVEL AS "a_date"
FROM dual
CONNECT BY LEVEL <= DATE '2012-04-30'+2 - DATE '2012-04-01'-1
)
WHERE peo.current_employee_flag = 'Y'
AND "a_date" BETWEEN peo.effective_start_date AND peo.EFFECTIVE_END_DATE
GROUP BY "a_date"
ORDER BY "a_date"