Compare Difference of Successive Rows Within The Same Table - sql

I need help in comparing data in sql query.
I need to compare within the plan_group_id, what measure_id details having different min_target and max_target compared to the other plan_id
Example:
I mean, I needed to compare what plan measure under the same plan_grp_id have different values in min and max.
what if the values are:
another example:

Group by all three columns
select m.plan_id, m.plan_grp_id, m.measure_id
from plan_measure m
group by m.plan_id, m.plan_grp_id, m.measure_id
having min(m.min_target) <> max(m.min_target) or min(m.max_target) <> max(m.max_target);

Looks like
SQL> with plan_measure (measure_id, min_target, max_target) as
2 (select 111, 10, 10 from dual union all
3 select 222, 20, 20 from dual union all
4 select 333, 30, 30 from dual union all
5 select 111, 33, 55 from dual union all
6 select 222, 20, 20 from dual union all
7 select 333, 30, 30 from dual union all
8 select 111, 10, 10 from dual union all
9 select 222, 20, 20 from dual union all
10 select 111, 10, 10 from dual
11 )
12 select measure_id
13 from plan_measure
14 group by measure_id
15 having min(min_target) <> max(max_target);
MEASURE_ID
----------
111
SQL>
I have no idea what is plan table supposed to do here, though. It isn't related to plan_measure in any obvious way (not to me, at least).

Related

Select greatest n per group using EXISTS

I have a RoadInsp table in Oracle 18c. I've put the data in a CTE for purpose of this question:
with roadinsp (objectid, asset_id, date_) as (
select 1, 1, to_date('2016-04-01','YYYY-MM-DD') from dual union all
select 2, 1, to_date('2019-03-01','YYYY-MM-DD') from dual union all
select 3, 1, to_date('2022-01-01','YYYY-MM-DD') from dual union all
select 4, 2, to_date('2016-04-01','YYYY-MM-DD') from dual union all
select 5, 2, to_date('2021-01-01','YYYY-MM-DD') from dual union all
select 6, 3, to_date('2022-03-01','YYYY-MM-DD') from dual union all
select 7, 3, to_date('2016-04-01','YYYY-MM-DD') from dual union all
select 8, 3, to_date('2018-03-01','YYYY-MM-DD') from dual union all
select 9, 3, to_date('2013-03-01','YYYY-MM-DD') from dual union all
select 10, 3, to_date('2010-06-01','YYYY-MM-DD') from dual
)
select * from roadinsp
OBJECTID ASSET_ID DATE_
---------- ---------- ----------
1 1 2016-04-01
2 1 2019-03-01
3 1 2022-01-01 --select this row
4 2 2016-04-01
5 2 2021-01-01 --select this row
6 3 2022-03-01 --select this row
7 3 2016-04-01
8 3 2018-03-01
9 3 2013-03-01
10 3 2010-06-01
I'm using GIS software that only lets me use SQL in a WHERE clause/SQL expression, not a full SELECT query.
I want to select the greatest n per group using the WHERE clause. In other words, for each ASSET_ID, I want to select the row that has the latest date.
As an experiment, I want to make the selection specifically using the EXISTS operator.
The reason being: While this post technically pertains to Oracle (since that's what S.O. community members would have access to), in practice, I want to use the logic in a proprietary database called a file geodatabase. The file geodatabase has very limited SQL support; a small subset of SQL-92 syntax. But it does seem to support EXISTS and subqueries, although not correlated subqueries, joins, or any modern SQL syntax. Very frustrating.
SQL reference for query expressions used in ArcGIS
Subquery support in file geodatabases is limited to the following:
Scalar subqueries with comparison operators. A scalar subquery returns a single value, for example:
GDP2006 > (SELECT MAX(GDP2005) FROM countries)
For file geodatabases, the set functions AVG, COUNT, MIN, MAX, and
SUM can only be used in scalar subqueries.
EXISTS predicate, for example:
EXISTS (SELECT * FROM indep_countries WHERE COUNTRY_NAME = 'Mexico')
Question:
Using the EXISTS operator, is there a way to select the greatest n per group? (keeping in mind the limitations mentioned above)
Edit:
If an asset has multiple rows with the same top date, then only one of those rows should be selected.
rank analytic function does the job, if it is available to you (Oracle 18c certainly does support it).
Sample data:
SQL> with roadinsp (objectid, asset_id, date_) as (
2 select 1, 1, to_date('2016-04-01','YYYY-MM-DD') from dual union all
3 select 2, 1, to_date('2019-03-01','YYYY-MM-DD') from dual union all
4 select 3, 1, to_date('2022-01-01','YYYY-MM-DD') from dual union all
5 select 4, 2, to_date('2016-04-01','YYYY-MM-DD') from dual union all
6 select 5, 2, to_date('2021-01-01','YYYY-MM-DD') from dual union all
7 select 6, 3, to_date('2022-03-01','YYYY-MM-DD') from dual union all
8 select 7, 3, to_date('2016-04-01','YYYY-MM-DD') from dual union all
9 select 8, 3, to_date('2018-03-01','YYYY-MM-DD') from dual union all
10 select 9, 3, to_date('2013-03-01','YYYY-MM-DD') from dual union all
11 select 10, 3, to_date('2010-06-01','YYYY-MM-DD') from dual
12 ),
Query begins here: first rank rows per asset_id by date in descending order:
13 temp as
14 (select r.*,
15 rank() over (partition by asset_id order by date_ desc) rnk
16 from roadinsp r
17 )
Finally, fetch rows that rank as the highest:
18 select *
19 from temp
20 where rnk = 1;
OBJECTID ASSET_ID DATE_ RNK
---------- ---------- ---------- ----------
3 1 2022-01-01 1
5 2 2021-01-01 1
6 3 2022-03-01 1
SQL>
If you can't use that, how about a subquery?
<snip>
13 select r.objectid, r.asset_id, r.date_
14 from roadinsp r
15 where (r.asset_id, r.date_) in (select t.asset_id, t.max_date
16 from (select a.asset_id, max(a.date_) max_date
17 from roadinsp a
18 group by a.asset_id
19 ) t
20 );
OBJECTID ASSET_ID DATE_
---------- ---------- ----------
6 3 2022-03-01
5 2 2021-01-01
3 1 2022-01-01
SQL>

Recursive query - Oracle

I have this data and expected result:
Data Expected Result
No A B A B
1 10 500 10 500
2 10 c=20 20 400
3 20 400 30 600
4 30 600 30 700
5 30 c=40 30 800
6 30 c=50 40 700
7 40 700 50 900
8 50 c=60 60 900
9 60 c=70 70 900
10 70 900 10 400
I need to perform a self join and get the result.
For line number 1 the expected result is same as the row.
For line number 2, I need to take the substring of column B (c=20) as 20 and join with column B and get the result as 400.
Lines number 5 and 6 need to substring column B and get the result from column A.
I tried a recursive query, but still am not getting the expected result.
with rec(A, B, nested) as
(
select A, B, case when instr(B, 'C=') != 0 then substr(B, instr(B, 'C=')) as nested
from table
union all
select A, rec.B from table
inner join rec
on (table.A = rec.nested)
)
select A, B, nested from rec;
Answer for the initial version of the question
You do not need a recursive query. To get your desired output you just need to exclude the rows where B starts with c=:
SELECT a, b
FROM table_name
WHERE b NOT LIKE 'c=%';
Which, for the sample data:
CREATE TABLE table_name (no, a, b) AS
SELECT 1, 10, '500' FROM DUAL UNION ALL
SELECT 2, 10, 'c=20' FROM DUAL UNION ALL
SELECT 3, 20, '400' FROM DUAL UNION ALL
SELECT 4, 30, '600' FROM DUAL UNION ALL
SELECT 5, 30, 'c=40' FROM DUAL UNION ALL
SELECT 6, 30, 'c=50' FROM DUAL UNION ALL
SELECT 7, 40, '700' FROM DUAL UNION ALL
SELECT 8, 50, '800' FROM DUAL;
Outputs your desired output:
A
B
10
500
20
400
30
600
40
700
50
800
fiddle
Answer for the 3rd edit of the question
You can use a hierarchical query:
SELECT DISTINCT
CONNECT_BY_ROOT a AS a,
b
FROM table_name
WHERE CONNECT_BY_ISLEAF = 1
CONNECT BY
PRIOR b LIKE 'c=%'
AND PRIOR SUBSTR(b, 3) = a
ORDER BY a, b;
Which, for the sample data:
CREATE TABLE table_name (no, a, b) AS
SELECT 1, 10, '500' FROM DUAL UNION ALL
SELECT 2, 10, 'c=20' FROM DUAL UNION ALL
SELECT 3, 20, '400' FROM DUAL UNION ALL
SELECT 4, 30, '600' FROM DUAL UNION ALL
SELECT 5, 30, 'c=40' FROM DUAL UNION ALL
SELECT 6, 30, 'c=50' FROM DUAL UNION ALL
SELECT 7, 40, '700' FROM DUAL UNION ALL
SELECT 8, 50, 'c=60' FROM DUAL UNION ALL
SELECT 9, 60, 'c=70' FROM DUAL UNION ALL
SELECT 10, 70, '900' FROM DUAL;
Outputs:
A
B
10
400
10
500
20
400
30
600
30
700
30
900
40
700
50
900
60
900
70
900
fiddle

insert missing row in table oracle

I have data in table like below:
Primary_id Serial_ID PRIMARY_ID SECONDARY_ID queue_ID
1 100 58 89 Q1428291
2 100 58 89 Q1428281
3 100 58 89 Q1428293
4 100 89 58 Q1428293
5 100 89 58 Q1428291
6 100 89 58 Q1428000281
7 200 16 28 Q1433144
8 200 16 28 Q1431953
9 200 16 28 Q1432397
10 200 16 28 Q1431921
11 200 28 16 Q1433144
12 200 28 16 Q1432397
13 200 28 16 Q1431921
We have primary_ID and Secondary_ID column. for serial_ID 100 we have 3 primary and 3 secondary records ..If you see the data Primary_ID become secondary and secondary become primary(3 each).but for serial_id 200 we have 4 primary records but 3 secondry records..
I want to insert missing record in the table.For ex serial_id 200 has missing data for primary ID 28 so insert this into table.Please assist
You can use something like this. In the temp_table, I put rows into two different types ('P' or 'S').
I assume that the queue_id = 'Q1428000281' (from row 6) is a mistake, and It should be 'Q1428281'.
drop table table_test ;
create table table_test (id, serial_id, primary_id, secondary_id, queue_id) as (
select 1 , 100, 58, 89, 'Q1428291' from dual union all
select 2 , 100, 58, 89, 'Q1428281' from dual union all
select 3 , 100, 58, 89, 'Q1428293' from dual union all
select 4 , 100, 89, 58, 'Q1428293' from dual union all
select 5 , 100, 89, 58, 'Q1428291' from dual union all
select 6 , 100, 89, 58, 'Q1428000281' from dual union all
select 7 , 200, 16, 28, 'Q1433144' from dual union all
select 8 , 200, 16, 28, 'Q1431953' from dual union all
select 9 , 200, 16, 28, 'Q1432397' from dual union all
select 10, 200, 16, 28, 'Q1431921' from dual union all
select 11, 200, 28, 16, 'Q1433144' from dual union all
select 12, 200, 28, 16, 'Q1432397' from dual union all
select 13, 200, 28, 16, 'Q1431921' from dual
)
;
insert into table_test (id, serial_id, primary_id, secondary_id, queue_id)
with temp_table as (
select t.*
, case when primary_id < secondary_id then 'P' else 'S' end type_t
from table_test t
)
select v_max.id_max + rownum ID, SERIAL_ID, PRIMARY_ID, SECONDARY_ID, queue_id
from (
select SERIAL_ID
, max(SECONDARY_ID) PRIMARY_ID
, min(PRIMARY_ID) SECONDARY_ID
, max(queue_id)queue_id
, count(TYPE_T) cnt
from temp_table tp
group by SERIAL_ID
, least(PRIMARY_ID, SECONDARY_ID)
, greatest(PRIMARY_ID, SECONDARY_ID)
, QUEUE_ID
having count(TYPE_T) != 2
)t
cross join (select max(id) id_max from table_test) v_max
;
Hmmm . . . You can generate the missing rows using cross join and then something like left join or not exists:
select sp.serial_id, sp.primary_id, sp.secondary_id
from (select d.serial_id, d.primary_id
from data d
) sp join
(select d.serial_id, d.secondary_id
from data d
) ss
on sp.serial_id = ss.serial_id left join
data d
on d.serial_id = sp.serial_id and
d.primary_id = sp.primary_id and
d.secondary_id = sp.secondary_id
where d.serial_id is null;
You can then insert the results of this query into your table.
Your question doesn't specify what value of queue_id should be used. NULL seems like a very reasonable value under these circumstances.

Compute "Amount" using values from tables

I have the following tables already in my DB
EMP
E_N E_NAM E_RATE E_DEP
--- ----- ---------- -----
1 A 400
2 B 200 1
3 C 150 2
4 D 150 3
5 E 120 1
6 F 100 1
7 G 100 2
8 H 50 2
9 I 50 3
10 J 50 3
11 K 150 3
WORKS
E_NO PR_NO HRS
--- --- ----------
2 1 10
3 2 20
5 1 20
5 2 20
5 3 20
6 1 10
6 2 10
I have to compute the amount billed to each project as AMOUNT, and that is the sum of the amount billed to the project by all employees who work on said project. The amount billed being E_RATE*HRS (product of HRS and E_RATE).
There are only 3 PR_NO: 1, 2 and 3.
I've tried this multiple times with no avail, I know that it has to be a nested query and the calculation to be shown AS AMOUNT, but no clue on how exactly to only display the 3 projects with the calculation already made.
Sounds like simple join and aggregation:
select w.pr_no,
sum(w.hrs * e.e_rate) as amount
from works w
join emp e on w.e_no = e.e_n
group by w.pr_no;
simple aggregate SUM() function after joining the tables
--test data
with EMP(e_no, e_name, e_rate, e_dep) as
(select 1, 'A', 400, null from dual union all
select 2, 'B', 200, 1 from dual union all
select 3, 'C', 150, 2 from dual union all
select 4, 'D', 150, 3 from dual union all
select 5, 'E', 120, 1 from dual union all
select 6, 'F', 100, 1 from dual union all
select 7, 'G', 100, 2 from dual union all
select 8, 'H', 50, 2 from dual union all
select 9, 'I', 50, 3 from dual union all
select 10, 'J', 50, 3 from dual union all
select 11, 'K', 150, 3 from dual),
WORKS(e_no, pr_no, hrs) as
(select 2, 1, 10 from dual union all
select 3, 2, 20 from dual union all
select 5, 1, 20 from dual union all
select 5, 2, 20 from dual union all
select 5, 3, 20 from dual union all
select 6, 1, 10 from dual union all
select 6, 2, 10 from dual)
-- actual query starts here
select w.pr_no, sum(w.hrs*e.e_rate) as amount
from works w
inner join emp e on (w.e_no = e.e_no)
group by w.pr_no;
"PR_NO"|"AMOUNT"
1|5400
2|6400
3|2400

Form and count the most frequented pair sql oracle

I am creating a database for the video replay site and I have a table with users and table with viewing history. I need to find using SQL query several the most watched pairs of videos. Exemple: user 1 watched videos 12, 43, 50, 66, 78; user 2 watched 12, 43, 45, 50; user 3 watched 12, 35, 50, 66, 78; user 4 watched 33, 66, 69, 78
So the two most viewed couples are (12,50) and (66,78).
But I can't even get how to form this couples for the future counting.
So, my question is how to form all possible couples and count the quantity of views of each of them.
A self join is the right way to do this. I think the simplest form of the query is:
select vh.*
from (select vh1.movie as movie1, vh2.movie as movie2, count(*) as cnt,
rank() over (order by count(*) desc) as seqnum
from viewing_history vh1 inner join
viewing_history vh2
on vh1.userid = vh2.userid and vh1.movie < vh2.movie
group by vh1.movie, vh2.movie
) vh
where seqnum = 1;
In the solution below, I create a subquery to simulate input data. In your application, instead of viewing_history you should use your viewing history table. I don't see how the "users" table is relevant in this problem. The second subquery, which I named movie_pairs, is an inner join of the viewing history with itself - that's how you create the pairs. I went beyond that - with that in hand, I went on to identify the pairs that are viewed together most often.
with
viewing_history ( userid, movie ) as (
select 1, 12 from dual union all
select 1, 43 from dual union all
select 1, 50 from dual union all
select 1, 66 from dual union all
select 1, 78 from dual union all
select 2, 12 from dual union all
select 2, 43 from dual union all
select 2, 45 from dual union all
select 2, 50 from dual union all
select 3, 12 from dual union all
select 3, 35 from dual union all
select 3, 50 from dual union all
select 3, 66 from dual union all
select 3, 78 from dual union all
select 4, 33 from dual union all
select 4, 66 from dual union all
select 4, 69 from dual union all
select 4, 78 from dual
),
-- end test data, query begins here (but include the keyword WITH from above)
movie_pairs ( movie1, movie2, ct ) as (
select a.movie, b.movie, count(*)
from viewing_history a inner join viewing_history b
on a.userid = b.userid and a.movie < b.movie
group by a.movie, b.movie
)
select movie1, movie2
from movie_pairs
where ct = (select max(ct) from movie_pairs)
order by movie1, movie2 -- ORDER BY is optional
;
Output:
MOVIE1 MOVIE2
---------- ----------
12 50
66 78