Include all rows in table when value in column changes - sql

I have an employee change table which tracks every change made to an employee work history with no clear flag for what that change is. I am trying to track the different departments that an employee has worked for including the first department he/she worked for. So all changes plus the first department he/she worked at. An employee may come back to the department he/she once worked for and we need to be bring those rows too. I have highlighted rows that I would like to bring back
Emp Change History Table

You seem to just want lag():
select t.*
from (select t.*, lag(dept_no) over (partition by emp_no order by effective_date) as prev_dept_no
from t
) t
where prev_dept_no is null or prev_dept_no <> dept_no

Related

SQL Oracle: How to show only one row when the columns diverge

I have an employees table in which most of the results show me only one employee per row.
However, I have to bring the amount of employees by area where 3 employees out of the 3432 have worked on a different area before.
Therefore, the results show me duplicated rows for these 3 employees. It's something like this:
Notice that on Brian's situation he's been admitted on a different area before.
How can I show Brian only once? Nonetheless, how can I show only the most recent area where he's worked on?
You can use ROW_NUMBER() to identify new and old rows per each employee, ordered by admission date.
Then filtering out old rows is easy. For example:
select *
from (
select t.*,
row_number() over(partition by employee order by admission desc) as rn
from t
) x
where rn = 1 -- keeps the latest row only, per employee

How to find when and to what an employee's location has changed and output both previous and current location?

From the original set of records, how do i bring the employees previous location,new location and from when this change is effective. Thanks!
You could try something like this. Not tested, since you did not provide test data in a usable format. If an employee changed location several times, all changes will be shown. (You may want to order by emp_id and eff_from_date to make the output easier to read/use; I didn't do that.) I ignored the emp_status column since you didn't say anything about it; if it must be used, it would probably go in a where clause in the subquery. I also changed your column names to valid ones.
select emp_id, emp_name, prev_emp_location, new_emp_location, eff_from_date
from (
select emp_id, emp_name, emp_location as new_emp_location, eff_from_date,
lag(emp_location) over (partition by emp_id order by eff_from_date)
as prev_emp_location
from your_table
)
where new_emp_location != prev_emp_location
;
The subquery uses the lag() analytic function to record the previous location (partitioned by emp_id and ordered chronologically); then the output can be used to simply filter for the rows where the previous location is not equal to the current one.

OracleSQL: Assigning employees to groups with date values, querying current assignments by date

I have a database which consists of employees (one table) which can be assigned to groups (another table). Bother are joined together with another table, employee-to-group, which lists the group id, the employee id and the start date of the assignment.
An employee always has to be assigned to a group, but the assignments can change daily. One employee could be working in group A for day, then change into group B and work in group C only a week later.
My task is to find out which employees are assigned to a certain group given by its name at any given date. So the input should be: group name, date and I want the output to be the data of all the employees which are part of that group at the given moment in time.
Here's an SQL fiddle with some test data:
http://sqlfiddle.com/#!9/6d0bb
I recreated the database with mysql-statements because I couldn't figure out the oracle statements, I'm sorry.
As you can see from the test data, some employees may never change groups, while others change frequently. THere are also employees which are planned to change assignments in the future. The query has to account for that.
Because the application is a legacy one, the values (especially in the date field) are questionable. They are given as "days since the 1st of january, 1990", so the entry "9131" means "1st of january, 2015". 9468 would be today (2015-12-04) and 9496 would be 2016-01-01).
What I already have is code to find out the "date value" for any given date in what I call the "legacy format" of the application I'm working with (here I've just used CURRENT_DATE):
SELECT FLOOR(CURRENT_DATE - TO_DATE('1990-01-01', 'YYYY-MM-DD')) AS diffdate
For finding out which group a certain employee is assigned to, I tried:
SELECT * FROM history h
WHERE emp_nr = 1 AND valid_from <= 9131
ORDER BY valid_from DESC
FETCH FIRST ROW ONLY;
which should return me the group which an employee is assigned to on the 1st of january 2015.
What I do need help with is creating a statement that joins all tables does the same for a whole group instead of only one employee (as there are thousands of employees in the database and I only want the data of at most 10 groups).
I'm thankful for any kind of pointers in the right direction.
Use row_number to rank your history and get the latest group, just as you did with your FETCH FIRST query:
select *
from
(
select
h.*,
row_number() over (partition by emp_nr order by valid_from desc) as rn
from history h
where valid_from <= 9131
)
where rn = 1
You can then join this result with other tables.

Oracle SQL change value of field to other value from list as long as new value different from current?

I have a test table which contains 33 employee records in my Oracle 10g XE. Examples are as the following (I'm showing 5 only):
I'm not a SQL person so I only know those basic INSERT, UPDATE etc. What I want to achive is to change the department of each employee to another department from another employee as long as it's not the same as their current department. Basically what I'm thinking is:
Get all of the department from each employee (DEPT A, DEPT B, DEPT B, DEPT C, DEPT A), maybe store it in a variable?
For each of the employee, put a different department into its record from the list of department collected before. The new department must not be the same as what that record has before.
The result of the above example would be like the following.
For the record, there are more than just DEPT A - C. My 33 employee records mostly have unique department. Only 2 - 3 department have more than 1 employee under it. Therefore, I can assure that there probably won't be any cases where an employee record cannot be assign under a different department from what it currently has.
This is a harder problem than you may realize. First, it is a permutation problem because you want the final counts for the departments to be the same. Second, there are times where it is not solvable (say, if more than half the employees are in the same department).
If you want to shuffle the departments for employees, here is a method:
with cte as (
select e.*,
row_number() over (order by emp_id) as orig_seqnum,
row_number() over (order by dbms_random.value) as new_seqnum
from employees e
)
select e1.*, e2.dept as new_dept
from cte e1 join
cte e2
on e1.orig_seqnum = e2.new_seqnum;
This does not guarantee that the new department is different from the old one. There are actually ways to make that happen. However, given that most of your departments have only one employee, this might solve your problem.

SQL - Updating records based on most recent date

I am having difficulty updating records within a database based on the most recent date and am looking for some guidance. By the way, I am new to SQL.
As background, I have a windows forms application with SQL Express and am using ADO.NET to interact with the database. The application is designed to enable the user to track employee attendance on various courses that must be attended on a periodic basis (e.g. every 6 months, every year etc.). For example, they can pull back data to see the last time employees attended a given course and also update attendance dates if an employee has recently completed a course.
I have three data tables:
EmployeeDetailsTable - simple list of employees names, email address etc., each with unique ID
CourseDetailsTable - simple list of courses, each with unique ID (e.g. 1, 2, 3 etc.)
AttendanceRecordsTable - has 3 columns { EmployeeID, CourseID, AttendanceDate, Comments }
For any given course, an employee will have an attendance history i.e. if the course needs to be attended each year then they will have one record for as many years as they have been at the company.
What I want to be able to do is to update the 'Comments' field for a given employee and given course based on the most recent attendance date. What is the 'correct' SQL syntax for this?
I have tried many things (like below) but cannot get it to work:
UPDATE AttendanceRecordsTable
SET Comments = #Comments
WHERE AttendanceRecordsTable.EmployeeID = (SELECT EmployeeDetailsTable.EmployeeID FROM EmployeeDetailsTable WHERE (EmployeeDetailsTable.LastName =#ParameterLastName AND EmployeeDetailsTable.FirstName =#ParameterFirstName)
AND AttendanceRecordsTable.CourseID = (SELECT CourseDetailsTable.CourseID FROM CourseDetailsTable WHERE CourseDetailsTable.CourseName =#CourseName))
GROUP BY MAX(AttendanceRecordsTable.LastDate)
After much googling, I discovered that MAX is an aggregate function and so I need to use GROUP BY. I have also tried using the HAVING keyword but without success.
Can anybody point me in the right direction? What is the 'conventional' syntax to update a database record based on the most recent date?
So you want to update the AttendantsRecordsTable, and set the comment to the comment in the most recent CourseDetailsTable for each employee?
UPDATE
dbo.AttendanceRecordsTable
SET
Comments = #Comments
FROM
CourseDetailsTable cd
INNER JOIN
Employee e ON e.EmployeeID = AttendanceRecordTable.EmployeeID
WHERE
e.LastName = #LastName
AND e.FirstName = #FirstName
AND cd.CourseName = #CourseName
AND AttendanceRecordsTable.CourseID = cd.CourseID
AND AttendanceRecordsTable.LastDate =
(SELECT MAX(LastDate)
FROM AttendanceRecordsTable a
WHERE a.EmployeeID = e.EmployeeID
AND a.CourseID = cd.CourseID)
I think something like that should work.
You basically need to do a join between the AttendanceRecordTable, which you want to update, and the Employee and CourseDetailsTable tables. For these two, you have defined certain parameters to select a single row each, and then you need to make sure to update only that last AttendanceRecordTable entry which you do by making sure it's the MAX(LastDate) of the table.
The subselect here:
(SELECT MAX(LastDate)
FROM AttendanceRecordsTable a
WHERE a.EmployeeID = e.EmployeeID AND a.CourseID = cd.CourseID)
will select the MAX (last) of the LastDate entries in AttendanceRecordsTable, based on selection of a given employee (e.EmployeeID) and a given course (cd.CourseID).
Pair that with the selects to select the single employee by first name and last name (that of course only works if you never have two John Miller in your employee table!). You also select the course by means of the course name, so that too must be unique - otherwise you'll get multiple hits in the course table.
Marc
Assuming that you primary key on the AttendanceRecordsTable is id:
UPDATE AttendanceRecordsTable SET Comments = #Comments
WHERE AttendanceRecordsTable.id = (
SELECT AttendanceRecordsTable.id
FROM EmployeeDetailsTable
JOIN AttendanceRecordsTable ON AttendanceRecordsTable.EmployeeID = EmployeeDetailsTable.EmployeeID·
JOIN CourseDetailsTable ON AttendanceRecordsTable.CourseID = CourseDetailsTable.CourseID
WHERE
EmployeeDetailsTable.LastName =#ParameterLastName AND EmployeeDetailsTable.FirstName =#ParameterFirstName AND
CourseDetailsTable.CourseName =#CourseName
ORDER BY AttendanceRecordsTable.LastDate DESC LIMIT 1)
Basically, that sub select will first join the attendence, employee and coursedetail tables, extract those rows where the employee's and course details' name match those given by your parameters and limit the output in reverted order to one line. You might want to test that sub-select statement first.
Edit: I just read your posting again, you don't have a single primary key column on AttendanceRecordsTable. Bummer.