PL SQL Group By Issue - oracle

I have two tables T1 and T2.
T1 has an ID column that is generated as a sequence.
It also has two columns first name and Last name.
The table T2 is connected to table T1 via the ID column (referential).
T2 table has a salary column, that is revised every few years.
I want to get all the first name, last name, salary , and salary date if the salary has changed.
I am not able to get this information using the ID.
A second ID is generated for the same FN and LN pair if the employee comes up for review.
For Example :-
ID FN LN
1 John Doe
2 John Doe
ID SALARY DATE
1 $1 2015
2 $2 2018
I am trying something like this
SELECT T.FN ||' '|| T.LN AS NAME, COUNT(*) AS CT,
S.SALARY, S.DATE
SALARYTABLE S, EMP T
WHERE S.ID=T.ID
HAVING COUNT(*) > 1
GROUP BY (T.FN ||' '|| T.LN);
I have solved this by using a Java program. I have to store all the ID's and loop through all the records and check if the FN and LN matches and then extract the Date and Salary. This is inefficient and I want to do it within PL/SQL.
Please help. Thank you.

Well, your data model is kind of wrong; you shouldn't rely on distinguishing people on their names. What if yet another "John Doe" gets employed?
Anyway: would something like this do?
CTEs T1 and T2 simulate your tables. I added some more rows, just to make sure that the following query doesn't fail too obviously
INTER CTE joins those two tables and calculates employee's previous salary (using the LAG function)
the final query select rows (from INTER) whose current and previous salary are different
As you already have those tables, you'd use lines 16 onwards.
SQL> with
2 t1 (id, fn, ln) as
3 (select 1, 'John', 'Doe' from dual union all
4 select 2, 'John', 'Doe' from dual union all
5 select 3, 'John', 'Doe' from dual union all
6 select 5, 'Billy', 'Jean' from dual union all
7 select 6, 'Billy', 'Jean' from dual
8 ),
9 t2 (id, salary, c_date) as
10 (select 1, 1, 2015 from dual union all
11 select 2, 2, 2018 from dual union all
12 select 3, 2, 2019 from dual union all
13 select 5, 3, 2016 from dual union all
14 select 6, 3, 2017 from dual
15 ),
16 inter as
17 (select
18 t1.id, t1.fn, t1.ln,
19 t2.id, t2.salary, t2.c_date,
20 lag(t2.salary) over (partition by t1.fn, t1.ln
21 order by c_date) prev_salary
22 from t1 join t2 on t1.id = t2.id
23 )
24 select i.fn, i.ln, i.salary, i.c_date
25 from inter i
26 where i.salary <> nvl(i.prev_salary, i.salary)
27 order by i.ln, i.c_date;
FN LN SALARY C_DATE
----- ---- ---------- ----------
John Doe 2 2018
SQL>

Related

Oracle query to keep looking until value is not 0 anymore

I am using Oracle 11.
I have 2 tables
TblA with columns id, entity_id and effective_date.
TblADetail with columns id and value.
If Value = 0 for the effective date, I want to keep looking for the next effective date until I found value <> 0 anymore.
The below query only look for value on 3/10/21.
If value = 0, I want to look for value on 3/11/21. If that's not 0, I want to stop.
But, if that's 0, I want to look for value on 3/12/21. If that's not 0, I want to stop.
But, if that's 0, I want to keep looking until value is not 0.
How can I do that ?
SELECT SUM(pd.VALUE)
FROM TblA p,TblADetail pd
WHERE p.id = pd.id
AND p.effective_date = to_date('03/10/2021','MM/DD/YYYY')
AND TRIM (p.entity_id) = 123
Sample data:
TblA
id entity_id effective_date
1 123 3/10/21
2 123 3/11/21
3 123 3/12/21
TblADetail
id value
1 -136
1 136
2 2000
3 3000
In the above data, for entity_id 123, starting from effective_date 3/10/21, I would like to to return value 2000 (from TblADetail) effective_date 3/11/21.
So, starting from a certain date, I want the results from the minimum date that has non-zero values.
Thank you.
You can do what you need to do by grouping the sum on the effective date, and using the MIN analytic function to find the earliest date. Once you've done that, you simply need to select the date that matches the earliest date.
E.g.:
with tbla as (select 1 id, ' 123' entity_id, to_date('10/03/2021', 'dd/mm/yyyy') effective_date from dual union all
select 2 id, ' 123' entity_id, to_date('11/03/2021', 'dd/mm/yyyy') effective_date from dual union all
select 3 id, ' 123' entity_id, to_date('12/03/2021', 'dd/mm/yyyy') effective_date from dual),
tbla_detail as (select 1 id, -136 value from dual union all
select 1 id, 136 value from dual union all
select 2 id, 2000 value from dual union all
select 3 id, 3000 value from dual),
results as (select a.effective_date,
sum(ad.value) sum_value,
min(case when sum(ad.value) != 0 then a.effective_date end) over () min_effective_date
from tbla a
inner join tbla_detail ad on a.id = ad.id
where a.effective_date >= to_date('10/03/2021', 'dd/mm/yyyy')
and trim(a.entity_id) = '123'
group by a.effective_date)
select sum_value
from results
where effective_date = min_effective_date;
SUM_VALUE
----------
2000
Straightforward; read comments within code. Sample data in lines #1 - 13, query begins at line #14.
SQL> with
2 -- sample data
3 tbla (id, entity_id, effective_date) as
4 (select 1, 123, date '2021-03-10' from dual union all
5 select 2, 123, date '2021-03-11' from dual union all
6 select 3, 123, date '2021-03-12' from dual
7 ),
8 tblb (id, value) as
9 (select 1, -136 from dual union all
10 select 1, 136 from dual union all
11 select 2, 2000 from dual union all
12 select 3, 3000 from dual
13 ),
14 tblb_temp as
15 -- simple grouping per ID
16 (select id, sum(value) value
17 from tblb
18 group by id
19 )
20 -- return TBLA values whose ID equals TBLB_TEMP's minimum ID
21 -- whose value isn't zero
22 select a.id, a.entity_id, a.effective_date
23 from tbla a
24 where a.id = (select min(b.id)
25 from tblb_temp b
26 where b.value > 0
27 );
ID ENTITY_ID EFFECTIVE_
---------- ---------- ----------
2 123 03/11/2021
SQL>

How can we get multiple rows data as single row in oracle

In image I have given table structure and sample data and I need output result as mentioned
With sample data you provided (lines #1 - 8), this returns desired result. Will it work for all other cases, I have no idea as the question lacks in quite a lot of information so YMMV.
SQL> with employee (id, name, type, visit_date) as
2 (select 1, 'Mohan', '01', date '2010-09-09' from dual union all
3 select 1, 'Mohan', '02', date '2010-09-10' from dual union all
4 --
5 select 1, 'Gani' , '01', date '2010-09-01' from dual union all
6 select 1, 'Gani' , '01', date '2010-09-02' from dual union all
7 select 1, 'Gani' , '01', date '2010-09-03' from dual
8 ),
9 --
10 type1 as
11 (select id, name, visit_date
12 from employee
13 where type = '01'
14 ),
15 type2 as
16 (select id, name, visit_date
17 from employee
18 where type = '02'
19 )
20 select
21 a.id,
22 a.name,
23 a.visit_date type1date,
24 b.visit_date type2date
25 from type1 a left join type2 b on a.id = b.id and a.name = b.name
26 order by a.id, a.name desc, a.visit_date;
ID NAME TYPE1DATE TYPE2DATE
---------- ----- ---------- ----------
1 Mohan 09/09/2010 10/09/2010
1 Gani 01/09/2010
1 Gani 02/09/2010
1 Gani 03/09/2010
SQL>

Check that a query contains all the values of a subquery in oracle

Imagine that you have a query and you want to only show that one who has ALL the values of a subquery. For example, we have the following table:
CREATE TABLE test
(
code VARCHAR2(4),
year VARCHAR2(4),
action VARCHAR2(50),
CONSTRAINT pk PRIMARY KEY (code, year)
);
And the following registers:
INSERT INTO test
VALUES ('1','2020','Departure');
INSERT INTO test
VALUES ('1','2021','Arrival');
INSERT INTO test
VALUES ('2','2020','Departure');
Imagine that a subquery returns me the following values:
('Departure','Arrival')
So I want to make a query that returns me only those codes and years which match both of the values that have been returned at the subquery. Looking at the registers, it should return only return ('1','2020') and ('1','2021') because they are the only ones whose actions are 'Arrival' and 'Departure'. How could I do it?
With a little bit expanded sample data, where CODEs 1 and 3 have both Arrival and Departure:
SQL> with test (code, year, action) as
2 (select 1, 2020, 'Departure' from dual union all
3 select 1, 2021, 'Arrival' from dual union all
4 select 2, 2020, 'Departure' from dual union all
5 --
6 select 3, 2018, 'Arrival' from dual union all
7 select 3, 2019, 'Departure' from dual
8 ),
9 subq as
10 (select distinct action,
11 count(distinct action) over () cnt_da
12 from test
13 )
14 select a.code, a.year
15 from test a join subq s on a.action = s.action
16 where s.cnt_da = (select count(distinct action)
17 from test b
18 where b.code = a.code
19 );
CODE YEAR
---------- ----------
1 2021
1 2020
3 2019
3 2018
SQL>
Yet another option, using the MINUS set operator:
SQL> with test (code, year, action) as
2 (select 1, 2020, 'Departure' from dual union all
3 select 1, 2021, 'Arrival' from dual union all
4 select 2, 2020, 'Departure' from dual union all
5 --
6 select 3, 2018, 'Arrival' from dual union all
7 select 3, 2019, 'Departure' from dual
8 ),
9 subq as
10 (select distinct action from test) --> this is your "subquery"
11 select code, year
12 from test a
13 where (select s.action from subq s
14 minus
15 select b.action from test b where b.code = a.code
16 ) is null;
CODE YEAR
---------- ----------
1 2020
1 2021
3 2018
3 2019
SQL>
Enclose your query inside a CTE like this:
with cte as (
<your query here>
)
select t.*
from test t
where
t.action in (select action from cte)
and
code in (
select code
from test
where action in (select action from cte)
group by code
having count(distinct action) = (select count(*) from cte)
)
The subquery of IN returns all the codes that contain all the actions that your query returns.
See the demo.
Results:
> CODE | YEAR | ACTION
> :--- | :--- | :--------
> 1 | 2021 | Arrival
> 1 | 2020 | Departure
As a general solution, I'd look for an opportunity to use HAVING count(*) = #. Roughly,
SELECT code FROM table WHERE action in (SUBSELECT) GROUP BY code HAVING count(*) = (SELECT count(*) from SUBSELECT)
Of course if you can have multiple Arrival for a single code, you have to include a DISTINCT as well. Roughly,
SELECT code FROM (SELECT code, distinct(action) FROM table) WHERE action in...
I worry about the performance of a query like this, but you'd have to check it in situ since most database engines have the ability to transform complex queries like this for efficiency.
===
I think a JOIN will also work if you're sure that there's only one action of each type per code. For example.
SELECT t.code FROM SUBQUERY as s RIGHT JOIN table as t on t.action = s.action GROUP by t.code HAVING count(*) = (SELECT COUNT(*) FROM SUBQUERY)
(and yes in either case, you should use CTE features like #forpas suggests if you're going to inline the subquery to avoid repeatedly executing it).

Oracle SQL Select Query Getting Max Row As a Fraction of a Rollup Total

hoping I might be able to get some advise regarding Oracle SQL…
I have a table roughly as follows (there are more columns, but not necessary for this example)…
LOCATION USER VALUE
1 1 10
1 2 20
1 3 30
2 4 10
2 5 10
2 6 20
1 60
2 40
100
I’ve used rollup to get subtotals.
What I need to do is get the max(value) row for each location and express the max(value) as a percentage or fraction of the subtotal for each location
ie:
LOCATION USER FRAC
1 3 0.5
2 6 0.5
I could probably solve this using my limited knowledge of select queries, but am guessing there must be a fairly quick and slick method..
Thanks in advance :)
Solution using analytic functions
(Please note the WITH MY_TABLE AS serving only as dummy datasource)
WITH MY_TABLE AS
( SELECT 1 AS LOC_ID,1 AS USER_ID, 10 AS VAL FROM DUAL
UNION
SELECT 1,2,20 FROM DUAL
UNION
SELECT 1,3,30 FROM DUAL
UNION
SELECT 2,4,10 FROM DUAL
UNION
SELECT 2,5,10 FROM DUAL
UNION
SELECT 2,6,20 FROM DUAL
)
SELECT LOC_ID,
USER_ID,
RATIO_IN_LOC
FROM
(SELECT LOC_ID,
USER_ID,
RATIO_IN_LOC,
RANK() OVER (PARTITION BY LOC_ID ORDER BY RATIO_IN_LOC DESC) AS ORDER_IN_LOC
FROM
(SELECT LOC_ID,
USER_ID,
VAL,
VAL/SUM(VAL) OVER (PARTITION BY LOC_ID) AS RATIO_IN_LOC
FROM MY_TABLE
)
)
WHERE ORDER_IN_LOC = 1
ORDER BY LOC_ID,
USER_ID;
Result
LOC_ID USER_ID RATIO_IN_LOC
1 3 0,5
2 6 0,5
with inputs ( location, person, value ) as (
select 1, 1, 10 from dual union all
select 1, 2, 20 from dual union all
select 1, 3, 30 from dual union all
select 2, 4, 10 from dual union all
select 2, 5, 10 from dual union all
select 2, 6, 20 from dual
),
prep ( location, person, value, m_value, total ) as (
select location, person, value,
max(value) over (partition by location),
sum(value) over (partition by location)
from inputs
)
select location, person, round(value/total, 2) as frac
from prep
where value = m_value;
Notes: Your table exists already? Then skip everything from "inputs" to the comma; your query should begin with with prep (...) as ( ...
I changed user to person since user is a keyword in Oracle, you shouldn't use it for table or column names (actually you can't unless you use double quotes, which is a very poor practice).
The query will output two or three or more rows per location if there are ties at the top. Presumably this is what you desire.
Output:
LOCATION PERSON FRAC
---------- ---------- ----------
1 3 .5
2 6 .5

Oracle sql retrive records based on maximum time

i have below data.
table A
id
1
2
3
table B
id name data1 data2 datetime
1 cash 12345.00 12/12/2012 11:10:12
1 quantity 222.12 14/12/2012 11:10:12
1 date 20/12/2012 12/12/2012 11:10:12
1 date 19/12/2012 13/12/2012 11:10:12
1 date 13/12/2012 14/12/2012 11:10:12
1 quantity 330.10 17/12/2012 11:10:12
I want to retrieve data in one row like below:
tableA.id tableB.cash tableB.date tableB.quantity
1 12345.00 13/12/2012 330.10
I want to retrieve based on max(datetime).
The data model appears to be insane-- it makes no sense to join an ORDER_ID to a CUSTOMER_ID. It makes no sense to store dates in a VARCHAR2 column. It makes no sense to have no relationship between a CUSTOMER and an ORDER. It makes no sense to have two rows in the ORDER table with the same ORDER_ID. ORDER is also a reserved word so you cannot use that as a table name. My best guess is that you want something like
select *
from customer c
join (select order_id,
rank() over (partition by order_id
order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
from order) o on (c.customer_id=o.order_id)
where o.rnk = 1
If that is not what you want, please (as I asked a few times in the comments) post the expected output.
These are the results I get with my query and your sample data (fixing the name of the ORDER table so that it is actually valid)
SQL> ed
Wrote file afiedt.buf
1 with orders as (
2 select 1 order_id, 'iphone' order_name, '20121201 12:20:23' order_time from dual union all
3 select 1, 'iphone', '20121201 12:22:23' from dual union all
4 select 2, 'nokia', '20110101 13:20:20' from dual ),
5 customer as (
6 select 1 customer_id, 'paul' customer_name from dual union all
7 select 2, 'stuart' from dual union all
8 select 3, 'mike' from dual
9 )
10 select *
11 from customer c
12 join (select order_id,
13 rank() over (partition by order_id
14 order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
15 from orders) o on (c.customer_id=o.order_id)
16* where o.rnk = 1
SQL> /
CUSTOMER_ID CUSTOM ORDER_ID RNK
----------- ------ ---------- ----------
1 paul 1 1
2 stuart 2 1
Try something like
SELECT *
FROM CUSTOMER c
INNER JOIN ORDER o
ON (o.CUSTOMER_ID = c.CUSTOMER_ID)
WHERE TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS') =
(SELECT MAX(TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS')) FROM ORDER)
Share and enjoy.

Resources