SQL query to get the employee who has not been appraised - sql

There is a employees salary history table, sal_hist which has id,name, salary and effective_date. Requirement is to get the employee who has not been appraised. Below is the table:
Id
name
salary
date
1
a
1000
10-5-2020
1
a
2000
12-6-2020
1
a
3000
12-7-2020
2
b
2500
12-5-2020
2
b
3500
12-7-2020
3
c
2500
12-5-2020
Below is the query I have:
Select id,name from sal_hist group by id,name having count(1)=1;
Is there a different way to achieve the result?

If your date column is the appraisal date and you want the user who has not got an appraisal at the latest date then:
SELECT id, name, salary, appraisal_date
FROM (
SELECT s.*,
MAX(appraisal_date) OVER (PARTITION BY id) AS max_user_appraisal_date,
MAX(appraisal_date) OVER () AS max_appraisal_date
FROM salary_history s
)
WHERE appraisal_date = max_user_appraisal_date
AND max_user_appraisal_date < max_appraisal_date
Which, for the sample data:
CREATE TABLE salary_history (Id, name, salary, appraisal_date) AS
SELECT 1, 'a', 1000, DATE '2020-05-10' FROM DUAL UNION ALL
SELECT 1, 'a', 2000, DATE '2020-06-12' FROM DUAL UNION ALL
SELECT 1, 'a', 3000, DATE '2020-07-12' FROM DUAL UNION ALL
SELECT 2, 'b', 2500, DATE '2020-05-12' FROM DUAL UNION ALL
SELECT 2, 'b', 3500, DATE '2020-07-12' FROM DUAL UNION ALL
SELECT 3, 'c', 2500, DATE '2020-05-12' FROM DUAL;
Outputs:
ID
NAME
SALARY
APPRAISAL_DATE
3
c
2500
12-MAY-20
db<>fiddle here

If you are looking for another way to achieve the result and find the employee with only 1 salary listed (no appraisals in the past) you can use a subquery like this:
SELECT id, name
FROM (
SELECT id, name, COUNT(id) AS [count]
FROM sal_hist
GROUP BY id,name)
WHERE count=1;
But I would note that using HAVING as you have currently is a better method.

Related

Return lowest assignment Employee

There's a request where we need to pick the person with the lowest amount of tasks assigned from each group. Like this:
A new task is received.
Group A has 10 employees. Employee ABC has the lowest amount of tasks assigned out of the 10, therefore ABC will be assigned to this newly received task.
My tables have the following structure:
Group Employee Task
A John Walk the dog
A Jane Pet the cat
A Jane Feed the chicken
B Mozart Play violin
B Mozart Play something
B Bach Fix piano
C James Cook Eggs
C James Fry something
C Emma Salad
C Emma Hummus
If a new task is received for group A, the algorithm would pick John, since he has 1 task only.
If a new task is received for group B, the algorithm would pick Back, since he has 1 task only.
If a new task is received for group C, how can we pick one using order ASC since they both have 2 tasks?
Anybody has any idea how to do it?
Thank you
We can achieve this using a combination of COUNT() and ROW_NUMBER():
WITH cte1 AS (
SELECT t.*, COUNT(*) OVER (PARTITION BY "Group", Employee) cnt
FROM yourTable t
),
cte2 AS (
SELECT t.*, ROW_NUMBER() OVER (PARTITION "Group" ORDER BY cnt) rnk
FROM cte1 t
)
SELECT "Group", Employee, Task
FROM cte2
WHERE rnk = 1
ORDER BY "Group", Employee;
The first CTE assigns counts of employee tasks within each group. The second CTE ranks records according to how many tasks each employee has.
Find within the group the employee with the lowest count of tasks and if there are multiple then, unless you have another metric you can use to assign tasks, pick the employee from those with the lowest number of tasks at random:
SELECT Employee
FROM table_name
WHERE "GROUP" = 'C'
GROUP BY Employee
ORDER BY COUNT(*) ASC, DBMS_RANDOM.VALUE()
FETCH FIRST ROW ONLY;
Which, for your sample data:
CREATE TABLE table_name ("GROUP", Employee, Task) AS
SELECT 'A', 'John', 'Walk the dog' FROM DUAL UNION ALL
SELECT 'A', 'Jane', 'Pet the cat' FROM DUAL UNION ALL
SELECT 'A', 'Jane', 'Feed the chicken' FROM DUAL UNION ALL
SELECT 'B', 'Mozart', 'Play violin' FROM DUAL UNION ALL
SELECT 'B', 'Mozart', 'Play something' FROM DUAL UNION ALL
SELECT 'B', 'Bach', 'Fix piano' FROM DUAL UNION ALL
SELECT 'C', 'James', 'Cook Eggs' FROM DUAL UNION ALL
SELECT 'C', 'James', 'Fry something' FROM DUAL UNION ALL
SELECT 'C', 'Emma', 'Salad' FROM DUAL UNION ALL
SELECT 'C', 'Emma', 'Hummus' FROM DUAL;
May pick:
EMPLOYEE
James
fiddle

pair two rows together in sql oracle 10g

I have a below table structure
When the agreement type for the employee is Basic and Appendix (e.g. row 1,2 & 5,6) then these two rows need to be considered together and status would be active. Below should be the expected outcome
How can this be achieved in oracle 10g. Thanks
This can be achieved using a CASE statement and the LEAD analytic function to see if the next ID is Appendix.
Query
--This is to set up the sample data
WITH
emp_agreements (id, emp_id, agreement_type)
AS
(SELECT 1, 1023, 'Basic' FROM DUAL
UNION ALL
SELECT 2, 1023, 'Appendix' FROM DUAL
UNION ALL
SELECT 3, 1023, 'Basic' FROM DUAL
UNION ALL
SELECT 4, 1023, 'Basic' FROM DUAL
UNION ALL
SELECT 5, 1023, 'Basic' FROM DUAL
UNION ALL
SELECT 6, 1023, 'Appendix' FROM DUAL)
--Real query begins here. You will need to put in your real table name
SELECT emp_id, status
FROM (SELECT id,
emp_id,
agreement_type,
CASE LEAD (agreement_type) OVER (PARTITION BY emp_id ORDER BY id)
WHEN 'Appendix' THEN 'Active'
ELSE 'Pending'
END AS status
FROM emp_agreements)
WHERE agreement_type = 'Basic'
ORDER BY id;
Result
EMP_ID STATUS
_________ __________
1023 Active
1023 Pending
1023 Pending
1023 Active

Select total average of averages grouped by id

In my database that represents a car service station, I am trying to figure out a SQL query that would give me a total average of how much does the customer pays for a single service but instead of getting AVG() of the price on all existing Invoices, I want to group the invoices by the same reservation_id. After that, I would like to get the total average of all of those grouped results.
I am using the two tables listed in the picture below. I want to get the value of a total average price by applying AVG() on all averages that are made by grouping prices by the same FK Reservation_reservation_id.
I tried to make this into a single query but I failed so I came looking for help from more experienced users. Also, I need to select (get) only the result of the total average. This result should give me an overview of how much each customer pays on average for one reservation.
Thanks for your time
You appear to want to aggregate twice:
SELECT AVG( avg_price ) avg_avg_price
FROM (
SELECT AVG( price ) AS avg_price
FROM invoice
GROUP BY reservation_reservation_id
)
Which, for the sample data:
CREATE TABLE invoice ( reservation_reservation_id, price ) AS
SELECT 1, 10 FROM DUAL UNION ALL
SELECT 1, 12 FROM DUAL UNION ALL
SELECT 1, 14 FROM DUAL UNION ALL
SELECT 1, 16 FROM DUAL UNION ALL
SELECT 2, 10 FROM DUAL UNION ALL
SELECT 2, 11 FROM DUAL UNION ALL
SELECT 2, 12 FROM DUAL;
Outputs:
AVG_AVG_PRICE
12
db<>fiddle here
If you want this per customer:
SELECT customer_customer_id, AVG(avg_reservation_price)
FROM (SELECT i.customer_customer_id, i.reservation_reservation_id,
AVG(i.price) as avg_reservation_price
FROM invoice i
GROUP BY i.customer_customer_id, i.reservation_reservation_id
) ir
GROUP BY customer_customer_id;
If you want this for a particular "checkout reason" -- which is the closest that I imagine that "service" means -- then join in the reservations table and filter:
SELECT customer_customer_id, AVG(avg_reservation_price)
FROM (SELECT i.customer_customer_id, i.reservation_reservation_id,
AVG(i.price) as avg_reservation_price
FROM invoice i JOIN
reservation r
ON i.reservation_reservation_id = r.reservation_id
WHERE r.checkup_type = ?
GROUP BY i.customer_customer_id, i.reservation_reservation_id
) ir
GROUP BY customer_customer_id;
You might want to try the below:
with aux (gr, subgr, val) as (
select 'a', 'a1', 1 from dual union all
select 'a', 'a2', 2 from dual union all
select 'a', 'a3', 3 from dual union all
select 'a', 'a4', 4 from dual union all
select 'b', 'b1', 5 from dual union all
select 'b', 'b2', 6 from dual union all
select 'b', 'b3', 7 from dual union all
select 'b', 'b4', 8 from dual)
SELECT
gr,
avg(val) average_gr,
avg(avg(val)) over () average_total
FROM
aux
group by gr;
Which, applied to your table, would result in:
SELECT
reservation_id,
avg(price) average_rn,
avg(avg(price)) over () average_total
FROM
invoices
group by reservation_id;

How to get an accurate JOIN using Fuzzy matching in Oracle

I'm trying to join a set of county names from one table with county names in another table. The issue here is that, the county names in both tables are not normalized. They are not same in count; also, they may not be appearing in similar pattern always. For instance, the county 'SAINT JOHNS' in "Table A" may be represented as 'ST JOHNS' in "Table B". We cannot predict a common pattern for them.
That means , we cannot use "equal to" (=) condition while joining. So, I'm trying to join them using the JARO_WINKLER_SIMILARITY function in oracle.
My Left Outer Join condition would be like:
Table_A.State = Table_B.State
AND UTL_MATCH.JARO_WINKLER_SIMILARITY(Table_A.County_Name,Table_B.County_Name)>=80
I've given the measure 80 after some testing of the results and it seemed to be optimal.
Here, the issue is that I'm getting set of "false Positives" when joining. For instance, if there are some counties with similarity in names under the same state ("BARRY'and "BAY" for example), they will be matched if the measure is >=80.
This creates inaccurate set of joined data.
Can anyone please suggest some work around?
Thanks,
DAV
Can you plz help me to build a query that will lookup Table_A for each record in Table B/C/D, and match against the county name in A with highest ranked similarity that is >=80
Oracle Setup:
CREATE TABLE official_words ( word ) AS
SELECT 'SAINT JOHNS' FROM DUAL UNION ALL
SELECT 'MONTGOMERY' FROM DUAL UNION ALL
SELECT 'MONROE' FROM DUAL UNION ALL
SELECT 'SAINT JAMES' FROM DUAL UNION ALL
SELECT 'BOTANY BAY' FROM DUAL;
CREATE TABLE words_to_match ( word ) AS
SELECT 'SAINT JOHN' FROM DUAL UNION ALL
SELECT 'ST JAMES' FROM DUAL UNION ALL
SELECT 'MONTGOMERY BAY' FROM DUAL UNION ALL
SELECT 'MONROE ST' FROM DUAL;
Query:
SELECT *
FROM (
SELECT wtm.word,
ow.word AS official_word,
UTL_MATCH.JARO_WINKLER_SIMILARITY( wtm.word, ow.word ) AS similarity,
ROW_NUMBER() OVER ( PARTITION BY wtm.word ORDER BY UTL_MATCH.JARO_WINKLER_SIMILARITY( wtm.word, ow.word ) DESC ) AS rn
FROM words_to_match wtm
INNER JOIN
official_words ow
ON ( UTL_MATCH.JARO_WINKLER_SIMILARITY( wtm.word, ow.word )>=80 )
)
WHERE rn = 1;
Output:
WORD OFFICIAL_WO SIMILARITY RN
-------------- ----------- ---------- ----------
MONROE ST MONROE 93 1
MONTGOMERY BAY MONTGOMERY 94 1
SAINT JOHN SAINT JOHNS 98 1
ST JAMES SAINT JAMES 80 1
Using some made up test data inline (you would use your own TABLE_A and TABLE_B in place of the first two with clauses, and begin at with matches as ...):
with table_a (state, county_name) as
( select 'A', 'ST JOHNS' from dual union all
select 'A', 'BARRY' from dual union all
select 'B', 'CHEESECAKE' from dual union all
select 'B', 'WAFFLES' from dual union all
select 'C', 'UMBRELLAS' from dual )
, table_b (state, county_name) as
( select 'A', 'SAINT JOHNS' from dual union all
select 'A', 'SAINT JOANS' from dual union all
select 'A', 'BARRY' from dual union all
select 'A', 'BARRIERS' from dual union all
select 'A', 'BANANA' from dual union all
select 'A', 'BANOFFEE' from dual union all
select 'B', 'CHEESE' from dual union all
select 'B', 'CHIPS' from dual union all
select 'B', 'CHICKENS' from dual union all
select 'B', 'WAFFLING' from dual union all
select 'B', 'KITTENS' from dual union all
select 'C', 'PUPPIES' from dual union all
select 'C', 'UMBRIA' from dual union all
select 'C', 'UMBRELLAS' from dual )
, matches as
( select a.state, a.county_name, b.county_name as matched_name
, utl_match.jaro_winkler_similarity(a.county_name,b.county_name) as score
from table_a a
join table_b b on b.state = a.state )
, ranked_matches as
( select m.*
, rank() over (partition by m.state, m.county_name order by m.score desc) as ranking
from matches m
where score > 50 )
select rm.state, rm.county_name, rm. matched_name, rm.score
from ranked_matches rm
where ranking = 1
order by 1,2;
Results:
STATE COUNTY_NAME MATCHED_NAME SCORE
----- ----------- ------------ ----------
A BARRY BARRY 100
A ST JOHNS SAINT JOHNS 80
B CHEESECAKE CHEESE 92
B WAFFLES WAFFLING 86
C UMBRELLAS UMBRELLAS 100
The idea is matches computes all scores, ranked_matches assigns them a sequence within (state, county_name), and the final query picks all the top scorers (i.e. filters on ranking = 1).
You may still get some duplicates as there is nothing to stop two different fuzzy matches scoring the same.

Unique & repeating row numbers for duplicate rows in a table

I have a table with a column called 'ID'
ID column has 8 rows with values 100,100,100,200,200,300,300,300
I want to create a new column called 'Row_ID' which should look up at the ID column and give unique row number to each row where a duplicate id is found and restart the counter when a different value is found.
Example , for the above values of ID columns, the Row column should look like
1,2,3,1,2,1,2,3
For Oracle Database
SELECT ROW_NUMBER()
OVER (PARTITION BY ID ORDER BY ID) AS "ROW_ID"
FROM (
SELECT 100 ID FROM dual
UNION ALL
SELECT 100 ID FROM dual
UNION ALL
SELECT 100 ID FROM dual
UNION ALL
SELECT 200 ID FROM dual
UNION ALL
SELECT 200 ID FROM dual
UNION ALL
SELECT 300 ID FROM dual
UNION ALL
SELECT 300 ID FROM dual
UNION ALL
SELECT 300 ID FROM dual
)