How to find changed values in column - oracle

I have a table which looks similar to:
AcctNbr
AcctTypCD
ContractDate
Emp
WrkStLct
WrkStRgn
10001
12M
11-01-2021
John Smith
Downtown
D
10002
BCK
11-02-2021
Jane Smith
Uptown
U
10003
HPLS
11-03-2021
Bob Jones
Midtown
M
10005
VPLS
11-04-2021
Chris Ice
Downtown
D
10006
CLBV
11-12-2021
Smith John
Uptown
U
10007
TI80
11-13-2021
Joann Penn
Midtown
M
10008
M360
10-04-2021
Jim Blue
Downtown
D
My initial query is:
Select acctnbr, accttypcd, contractdate, emp, wrkstlct, wrkstrgn
from tableA
where accttypcd in ('HPLS', 'VPLS')
and contractdate between trunc(sysdate,'mm') and sysdate
order by wrkstrgn, wrkstlct, emp, contractdate;
End users are requesting now a report which pulls back any time AcctTypCD changes from any value (a list of up to 80+ different values) to either 'HPLS' or 'VPLS' and the emp who made the change, what would be the best way to accomplish this?
I apologize in advance for any initial mistakes in this question or if this is a duplicate, first time asking a question.

Its hard to determine fully what you want to achieve given that every row has a different AcctNbr, but your description says "If acctnbr 10007 changes from ...". I will assume that a change means a new row in the table. On that assumption, you could do something like
Select acctnbr, accttypcd, contractdate, emp, wrkstlct, wrkstrgn,
lag(accttypcd) over ( partition by acctnbr order by contractdate)
from tableA
where accttypcd in ('HPLS', 'VPLS')
and contractdate between trunc(sysdate,'mm') and sysdate
order by wrkstrgn, wrkstlct, emp, contractdate;
where the LAG function will show you the previous value of 'accttypcd' where the definition of "previous" is segmented by acctnbr (the 'partition by' part) and sequenced by contractdata (the 'order by' part).
Analytic SQL (like lag, lead etc) is a big topic, so you can get a full tutorial here
https://www.youtube.com/watch?v=0cjxYMxa1e4&list=PLJMaoEWvHwFIUwMrF4HLnRksF0H8DHGtt

Related

How to return names of events scheduled on the same date at the same location as 'JOHN BIRTHDAY'

I got this question in the exam wrong, and I want to know how to fix it? How to return names of events scheduled on the same date at the same location as 'JOHN BIRTHDAY'?
Select ename from event e1, location L where e1.LID=L.LID AND
E1.ENAME='JOHN BIRTHDAY' AND E1.START_DATE=DATE'2017-01-02';
EVENT TABLE: LID= LOCATION ID , AND MID = MEMBER ID
EID ENAME MID LID START_DAT END_DATE
1 JOHN BIRTHDAY 1 2 02-JAN-17 02-JAN-17
2 SAMI BIRTHDAY 2 1 02-JAN-17 02-JAN-17
4 THANKSGIVING 2 2 02-JAN-17 02-JAN-17
3 SUSAN GRADUATION 1 2 02-JAN-17 02-JAN-17
What you did wrong...
You are looking up data in two tables, EVENT and LOCATION. You match them by LOCATION_ID - no harm there. But then, from EVENT you select just one row: the row where ENAME = 'JOHN BIRTHDAY'. Then the first mistake: to that, you add "and START_DATE - ..." No need for that; you already limited the rows from the EVENT table to the row with ENAME = 'JOHN BIRTHDAY'.
Then: you are joining to LOCATION for absolutely no reason; you are not selecting anything from LOCATION. (Nor do you need to, since the problem is asking you to find other events that share the same location, meaning the same LOCATION_ID, and that is already available in the EVENT table.)
The biggest mistake: nowhere in your code are you looking for OTHER events. With some constraints: same date and location as 'JOHN BIRTHDAY', but that is irrelevant since you are not looking for ANY other events, anywhere in the code.
How to solve this problem correctly:
You need to look up data in EVENT twice. One time to find out when and where 'JOHN BIRTHDAY' will be. (It is not stated in the problem, but the convention during an exam is that you are not supposed to read the date and location in the table, write them down, and then hard-code them in your query; instead, they should be "variables" in the solution.) Second time you need to look IN THE SAME TABLE to find other events with the same date and location.
So this means you will need to join EVENT to itself (give it two different aliases, so you can refer to them clearly - "this" copy vs. "that" copy of the table). Join them by LID and ... date ... but that's more complicated (see below). Select the ENAME from one copy, while in the other copy put the WHERE condition: where ENAME = 'JOHN BIRTHDAY'.
Something like
select e1.ename
from event e1 join event e2 on e1.lid = e2.lid and e1.start_date = e2.start_date
where e2.ename = 'JOHN BIRTHDAY'
;
which is what Darzen posted in a different Answer before I typed mine.
This may be all you need, except for the following interesting question. The base data has a START_DATE and an END_DATE, not just a straight DATE. Does that mean that an event may be from 9 AM to noon, while 'JOHN BIRTHDAY' is from 6 PM to 8:30 PM? Or perhaps, does it mean that some events may extend for two or three days, and you need to find all those that overlap with 'JOHN BIRTHDAY'?
That makes the problem more interesting, but let's leave it alone; what I shared with you so far may suffice.
Final note - you may be better off learning modern (SQL Standard) notation for joins. You used Oracle syntax, which is not wrong, but it is not the best option.
select b.ename
from events a ,
(select ename, lid, start_Date from events) b
where a.lid = b.lid and a.start_date = b.start_date
and a.ename = 'JOHN BIRTHDAY' and b.ename <> 'JOHN BIRTHDAY';
Looks to me like
WITH cteJOHN_BIRTHDAY AS (SELECT *
FROM EVENT
WHERE ENAME = 'JOHN BIRTHDAY')
SELECT e.ENAME
FROM EVENT e
INNER JOIN cteJOHN_BIRTHDAY j
ON j.START_DAT = e.START_DAT AND
j.LID = e.LID AND
j.EID <> e.EID
ought to do it.
Not tested on animals - you'll be first! :-)

Select Distinct Query Left JOIN, Still getting duplicates in my result

I am writing a select query with distinct but I am still getting duplicates in my result. The Oracle View do have duplicates and I am trying to get back only 1 occurrence of that value.
Here is my query
select
person.person_id,
person.last_name,
person.first_name,
person.first_name,
person.middle_name,
skill.skills_id,
(case
when trim(skills.skill_description) = 'typing fast' then 'TP1'
when trim(skills.skill_description) = 'courier district 9' then 'CD9'
when trim(skills.skill_description) = 'helpdesk shift 3' then 'HD3'
when trim(skills.skill_description) = 'helpdesk shift 5' then 'HD5'
....
else ''
end) AS skill_description
from person_view person
left join (select distinct person_id, skill_id, skill_description, updated_date
from skill_view) skills
on skills.person_id = person.person_id and
((trunc(sysdate) - PHONE.UPDATED_DT <= 1)) and
trim(skills.skill_description) in ('skill1', 'skill2', 'skill3' ...)
There is a lot of values for skill_description, so I add the IN clause to filter for 15 - 20 specific skill_description values.
My case will take a the value and set the code for it.
I thought when I used the 'distinct' keyword it would filter out the duplicates but it is not working.
Here is my output so far
105 John E Doe SKILL1
105 John E Doe SKILL1
105 John E Doe SKILL2
105 John E Doe SKILL2
105 John E Doe SKILL3
105 John E Doe SKILL3
Any help is appreciated. Thanks
The problem is the DISTINCT is in an inner level and maybe the duplicates are outside the LEFT JOIN, you must put the DISTINCT clause in the first SELECT.
This should give you the desire output:
SELECT DISTINCT
person.person_id,
person.last_name,
person.first_name,
person.first_name,
person.middle_name,
skill.skills_id,
OMMITED CODE...
You are selecting updated_date in the subquery, but you don't use it in the outer query. QUESTION: Why? Did you mean to use it for something, perhaps to only select the most current info from the table?
In any case, if you have the same person_id, skill_id and skill_description values but different updated_date, DISTINCT won't help; the rows may very well be distinct in the inner query, but cease to be distinct in the outer query (if you don't include updated_date in the projection).

Trying to figure out top 5 land areas of the 50 states in the U.S

I have a table created. With one column named states and another column called land area. I am using oracle 11g. I have looked at various questions on here and cannot find a solution. Here is what I have tried so far:
SELECT LandAreas, State
FROM ( SELECT LandAreas, State, DENSE_RANK() OVER (ORDER BY State DESC) sal_dense_rank
FROM Map )
WHERE sal_dense_rank >= 5;
This does not provide the top 5 land areas as far as number wise.
I have also tried this one but no go either:
SELECT * FROM Map order by State desc)
where rownum < 5;
Anyone have any suggestions to get me on the right track??
Here is a samle of the table
states land areas
michagan 15000
florida 25000
tennessee 10000
alabama 80000
new york 150000
california 20000
oregon 5000
texas 6000
utah 3000
nebraska 1000
Desired output from query:
States land area
new york 150000
alabama 80000
florida 25000
california 20000
Try:
Select * from
(SELECT State, LandAreas FROM Map ORDER BY LandAreas DESC)
where rownum < 6
Link to Fiddle
Use a HAVING clause and count the number state states larger:
SELECT m.state, m.landArea
FROM Map m
LEFT JOIN Map m2 on m2.landArea > m.landArea
GROUP BY m.state, m.landArea
HAVING count(*) < 5
ORDER BY m.landArea DESC
See SQLFiddle
This joins each state to every state whose area is greater, then uses a HAVING clause to return only those states where the number of larger states was less than 5.
Ties are all returned, leading to more than 5 rows in the case of a tie for 5th.
The left join is needed for the case of the largest state, which has no other larger state to join to.
The ORDER BY is optional.
Try something like this
select m.states,m.landarea
from map m
where (select count(‘x’) from map m2 where m2.landarea > m.landarea)<=5
order by m.landarea
There are two bloomers in your posted code.
You need to use landarea in the DENSE_RANK() call. At the moment you're ordering the states in reverse alphabetical order.
Your filter in the outer query is the wrong way around: you're excluding the top four results.
Here is what you need ...
SELECT LandArea, State
FROM ( SELECT LandArea
, State
, DENSE_RANK() OVER (ORDER BY landarea DESC) as area_dr
FROM Maps )
WHERE area_dr <= 5
order by area_dr;
... and here is the SQL Fiddle to prove it. (I'm going with the statement in the question that you want the top 5 biggest states and ignoring the fact that your desired result set has only four rows. But adjust the outer filter as you will).
There are three different functions for deriving top-N result sets: DENSE_RANK, RANK and ROW_NUMBER.
Using ROW_NUMBER will always guarantee you 5 rows in the result set, but you may get the wrong result if there are several states with the same land area (unlikely in this case, but other data sets will produce such clashes). So: 1,2,3,4,5
The difference between RANK and DENSE_RANK is how they handle ties. DENSE_RANK always produces a series of consecutive numbers, regardless of how many rows there are in each rank. So: 1,2,2,3,3,3,4,5
RANK on the other hand will produce a sparse series if a given rank has more than one hit. So: 1,2,2,4,4,4.
Note that each of the example result sets has a different number of rows. Which one is correct? It depends on the precise question you want to ask.
Using a sorted sub-query with the ROWNUM pseudo-column will work like the ROW_NUMBER function, but I prefer using ROW_NUMBER because it is more powerful and more error-proof.

Where two or more values match condition?

I have been asked this question;
You list county names and the surnames of the representatives if the representatives in the counties have the same surname.
and I have the following tables;
***REPRESENTATIVE***
REPI SURNAME FIRSTNAME COUNTY CONS
---- ---------- ---------- ---------- ----
R100 Gorege Larry kent CON1
R101 shneebly john kent CON2
R102 shneebly steve kent CON3
I cant seem to figure out the correct way to ask Orical to display a surname that exists more then twice and the surnames are in the same country.
I know how to ask WHERE something = something, but that's doesn't ask what I want to know.
It sounds like you want to use the HAVING clause after doing a GROUP BY
SELECT surname, county, count(*)
FROM you_table
GROUP BY surname, county
HAVING count(*) > 1;
If you really mean "more than twice" as you wrote, none of the data you'd want HAVING count(*) > 2 but then none of your sample data would be returned.
In words, this SQL statement says
Group the data into buckets by surname and county. Each distinct combination of surname and county is a separate bucket.
Count the number of rows in each bucket
Return those buckets where there are at least two rows

Select all rows from SQL based upon existence of multiple rows (sequence numbers)

Let's say I have table data similar to the following:
123456 John Doe 1 Green 2001
234567 Jane Doe 1 Yellow 2001
234567 Jane Doe 2 Red 2001
345678 Jim Doe 1 Red 2001
What I am attempting to do is only isolate the records for Jane Doe based upon the fact that she has more than one row in this table. (More that one sequence number)
I cannot isolate based upon ID, names, colors, years, etc...
The number 1 in the sequence tells me that is the first record and I need to be able to display that record, as well as the number 2 record -- The change record.
If the table is called users, and the fields called ID, fname, lname, seq_no, color, date. How would I write the code to select only records that have more than one row in this table? For Example:
I want the query to display this only based upon the existence of the multiple rows:
234567 Jane Doe 1 Yellow 2001
234567 Jane Doe 2 Red 2001
In PL/SQL
First, to find the IDs for records with multiple rows you would use:
SELECT ID FROM table GROUP BY ID HAVING COUNT(*) > 1
So you could get all the records for all those people with
SELECT * FROM table WHERE ID IN (SELECT ID FROM table GROUP BY ID HAVING COUNT(*) > 1)
If you know that the second sequence ID will always be "2" and that the "2" record will never be deleted, you might find something like:
SELECT * FROM table WHERE ID IN (SELECT ID FROM table WHERE SequenceID = 2)
to be faster, but you better be sure the requirements are guaranteed to be met in your database (and you would want a compound index on (SequenceID, ID)).
Try something like the following. It's a single tablescan, as opposed to 2 like the others.
SELECT * FROM (
SELECT t1.*, COUNT(name) OVER (PARTITION BY name) mycount FROM TABLE t1
)
WHERE mycount >1;
INNER JOIN
JOIN:
SELECT u1.ID, u1.fname, u1.lname, u1.seq_no, u1.color, u1.date
FROM users u1 JOIN users u2 ON (u1.ID = u2.ID and u2.seq_no = 2)
WHERE:
SELECT u1.ID, u1.fname, u1.lname, u1.seq_no, u1.color, u1.date
FROM users u1, thetable u2
WHERE
u1.ID = u2.ID AND
u2.seq_no = 2
Check out the HAVING clause for a summary query. You can specify stuff like
HAVING COUNT(*) >= 2
and so forth.

Resources