Aggregate vertices-as-rows to string - sql

I have multi-part polyline vertices stored as individual rows in an Oracle 18c table.
ASSET_ID PART_NUM VERTEX_NUM X Y M
---------- ---------- ---------- ---------- ---------- ----------
001 1 1 0 5 0
001 1 2 10 10 11.18
001 1 3 30 0 33.54
001 2 1 50 10 33.54
001 2 2 60 10 43.54
Sample data:
db<>fiddle
I want to aggregate the rows into a single text value (without the single quotes):
'MULTILINESTRING ((0 5 0, 10 10 11.18, 30 0 33.54),(50 10 33.54, 60 10 43.54))'
How can I do that using Oracle SQL?

Use Listagg function
with cte as (
select 001 c1,1 c2,1 c3,0 c4,5 c5,0 c6 from dual union all
select 001,1,2,10,10,11.18 from dual union all
select 001,1,3,30,0,33.54 from dual union all
select 001,2,1,50,10,33.54 from dual union all
select 001,2,2,60,10,43.54 from dual
), cte1 as
(
select c2,listagg(c4||' '||c5||' '||c6,',')
s1 from cte group by c2 order by c2 asc)
select 'MULTILINESTRING ('||listagg('('||s1||')',',')
within group (order by c2) ||')' as result from cte1;
fiddle

Related

generate sequence number

Hi can any one help me generate version number based on below table data
ex:
book_name version
math_1 1
physics_1 3
physics_6 4
chemist_1 1
chemist_2 2
eng_1 1
i want a query to rerun below result based on above table data( especially Physics )
expected result
book_name version
math_1 1
physics_1 1
physics_6 2
chemist_1 1
chemist_2 2
eng_1 1
Thanks in advance
To produce the new version you need to separate the subject from the level. Once you do that, a simple ROW_NUMBER() function does the job.
For example:
select x.*,
row_number() over(partition by subject order by lvl) as new_version
from (
select t.*,
substr(book_name, 1, instr(book_name, '_') - 1) as subject,
to_number(substr(book_name, 1 + instr(book_name, '_'))) as lvl
from t
) x;
Result:
BOOK_NAME version SUBJECT LVL NEW_VERSION
----------- --------- --------- ----- -------------
chemist_1 1 chemist 1 1
chemist_2 2 chemist 2 2
eng_1 1 eng 1 1
math_1 1 math 1 1
physics_1 3 physics 1 1
physics_6 4 physics 6 2
See running example at db<>fiddle.
Similarly (lines #1 - 8 represent sample data; you don't type that. Query that returns result you wanted begins at line #9):
SQL> with test (book_name, version) as
2 (select 'math_1' , 1 from dual union all
3 select 'physics_1', 3 from dual union all
4 select 'physics_6', 4 from dual union all
5 select 'chemist_1', 1 from dual union all
6 select 'chemist_2', 2 from dual union all
7 select 'eng_1' , 1 from dual
8 )
9 select book_name,
10 version,
11 row_number() over (partition by regexp_substr(book_name, '[[:alpha:]]')
12 order by to_number(regexp_substr(book_name, '\d+$'))
13 ) new_version
14 from test
15 order by book_name, new_version;
BOOK_NAME VERSION NEW_VERSION
--------- ---------- -----------
chemist_1 1 1
chemist_2 2 2
eng_1 1 1
math_1 1 1
physics_1 3 1
physics_6 4 2
6 rows selected.
SQL>
You seem to just want the last digits:
select t.*,
regexp_substr(book_name, '[0-9]+$')
from t;
Here is a db<>fiddle.

ORACLE SQL - Wildcard on rowname

I have 2 tables - Table1 and Table2.
Table1.ROrd = 00123 and Table2.Ord = 123.
I need to find all values where Table2.Ord contains part of the value in able1.ROrd.
Since I was getting invalid number error, I tried this. But this is not good enough because the values are not exact match.
select * from Table1 where to_char(ROrd) in (select to_char(Ord) from Table2)
If you perform cross join on those tables and compute some similarities or a good, old instr function's result, then you get something like this (with my sample data, of course):
SQL> with
2 tab1 (ord) as
3 (select '00123' from dual union all
4 select 'ab445' from dual union all
5 select 'xyz' from dual
6 ),
7 tab2 (ord) as
8 (select '123' from dual union all
9 select 'ab556' from dual union all
10 select 'zyx' from dual
11 )
12 select a.ord, b.ord,
13 utl_match.jaro_winkler_similarity(a.ord, b.ord) jwsim,
14 utl_match.edit_distance_similarity(a.ord, b.ord) edsim,
15 --
16 instr(a.ord, b.ord) ins
17 from tab1 a cross join tab2 b;
ORD ORD JWSIM EDSIM INS
----- ----- ---------- ---------- ----------
00123 123 0 60 3
00123 ab556 0 0 0
00123 zyx 0 0 0
ab445 123 0 0 0
ab445 ab556 78 40 0
ab445 zyx 0 0 0
xyz 123 0 0 0
xyz ab556 0 0 0
xyz zyx 55 34 0
9 rows selected.
SQL>
Now, decide which option suits you most.
INSTR is simple enough:
<snip>
17 from tab1 a cross join tab2 b
18 where instr(a.ord, b.ord) > 0;
ORD ORD JWSIM EDSIM INS
----- ----- ---------- ---------- ----------
00123 123 0 60 3
SQL>
Choose similarity level by yourself, e.g. 60, but that's probably not what you want in this case:
<snip>
17 from tab1 a cross join tab2 b
18 where utl_match.jaro_winkler_similarity(a.ord, b.ord) > 60;
ORD ORD JWSIM EDSIM INS
----- ----- ---------- ---------- ----------
ab445 ab556 78 40 0
SQL>
I guess instr produces better result (according to what you described).
Use EXISTS and LIKE to compare the two tables:
select *
from Table1 t1
where EXISTS (
SELECT 1
FROM table2 t2
WHERE t1.ROrd LIKE '%' || t2.Ord || '%'
)
or, if the values are just zero-padded then:
select *
from Table1 t1
where EXISTS (
SELECT 1
FROM table2 t2
WHERE t1.ROrd = LPAD( t2.Ord, 5, '0' )
)

Create table from loop output Oracle SQL

I need to pull a random sample from a table of ~5 million observations based on 175 demographic options. The demographic table is something like this form:
1 40 4%
2 30 3%
3 30 3%
- -
174 2 .02%
175 1 .01%
Basically I need this same demographic breakdown randomly sampled from the 5M row table. For each demographic I need a sample of the same one from the larger table but with 5x the number of observations (example: for demographic 1 I want a random sample of 200).
SELECT *
FROM (
SELECT *
FROM my_table
ORDER BY
dbms_random.value
)
WHERE rownum <= 100;
I've used this syntax before to get a random sample but is there any way I can modify this as a loop and substitute variable names from existing tables? I'll try to encapsulate the logic I need in pseudocode:
for (each demographic_COLUMN in TABLE1)
select random(5*num_obs_COLUMN in TABLE1) from ID_COLUMN in TABLE2
/*somehow join the results of each step in the loop into one giant column of IDs */
You could join your tables (assuming the 1-175 demographic value exists in both, or there is an equivalent column to join on), something like:
select id
from (
select d.demographic, d.percentage, t.id,
row_number() over (partition by d.demographic order by dbms_random.value) as rn
from demographics d
join my_table t on t.demographic = d.demographic
)
where rn <= 5 * percentage
Each row in the main table is given a random pseudo-row-number within its demographic (via the analytic row_number()). The outer query then uses the relevant percentage to select how many of those randomly-ordered rows for each demographic to return.
I'm not sure I've understood how you're actually picking exactly how many of each you want, so that probably needs to be adjusted.
Demo with a smaller sample in a CTE, and matching smaller match condition:
-- CTEs for sample data
with my_table (id, demographic) as (
select level, mod(level, 175) + 1 from dual connect by level <= 175000
),
demographics (demographic, percentage, str) as (
select 1, 40, '4%' from dual
union all select 2, 30, '3%' from dual
union all select 3, 30, '3%' from dual
-- ...
union all select 174, 2, '.02%' from dual
union all select 175, 1, '.01%' from dual
)
-- actual query
select demographic, percentage, id, rn
from (
select d.demographic, d.percentage, t.id,
row_number() over (partition by d.demographic order by dbms_random.value) as rn
from demographics d
join my_table t on t.demographic = d.demographic
)
where rn <= 5 * percentage;
DEMOGRAPHIC PERCENTAGE ID RN
----------- ---------- ---------- ----------
1 40 94150 1
1 40 36925 2
1 40 154000 3
1 40 82425 4
...
1 40 154350 199
1 40 126175 200
2 30 36051 1
2 30 1051 2
2 30 100451 3
2 30 18026 149
2 30 151726 150
3 30 125302 1
3 30 152252 2
3 30 114452 3
...
3 30 104652 149
3 30 70527 150
174 2 35698 1
174 2 67548 2
174 2 114798 3
...
174 2 70698 9
174 2 30973 10
175 1 139649 1
175 1 156974 2
175 1 145774 3
175 1 97124 4
175 1 40074 5
(you only need the ID, but I'm including the other columns for context); or more succinctly:
with my_table (id, demographic) as (
select level, mod(level, 175) + 1 from dual connect by level <= 175000
),
demographics (demographic, percentage, str) as (
select 1, 40, '4%' from dual
union all select 2, 30, '3%' from dual
union all select 3, 30, '3%' from dual
-- ...
union all select 174, 2, '.02%' from dual
union all select 175, 1, '.01%' from dual
)
select demographic, percentage, count(id) as ids, min(id) as min_id, max(id) as max_id
from (
select d.demographic, d.percentage, t.id,
row_number() over (partition by d.demographic order by dbms_random.value) as rn
from demographics d
join my_table t on t.demographic = d.demographic
)
where rn <= 5 * percentage
group by demographic, percentage
order by demographic;
DEMOGRAPHIC PERCENTAGE IDS MIN_ID MAX_ID
----------- ---------- ---------- ---------- ----------
1 40 200 175 174825
2 30 150 1 174126
3 30 150 2452 174477
174 2 10 23448 146648
175 1 5 19074 118649
db<>fiddle

How to get the full row of data based on max value?

I have an Oracle table with a usage counter. I need to get the full row of data for each sensor with the max counter value?
For ALO I need data in row 2.
For AMA I need data in row 10.
For A11 I need data in row 9658.
For MSP I need data in row 9659.
Any help would be greatly appreciated!
Thanks,
Dave
You can use window function.
selcet * from (
select a.*, row_number() over (partition by facility_id_n
order by usage_counter_n desc) rn
from your_tbl a)
where rn =1;
Or use the First window-funcrion.
ROW_NUMBER won't return correct result if there are two rows with the same USAGE_COUNTER_N value per FACILITY_ID_N. For example:
SQL> WITH test
2 AS (SELECT 9640 ID_n, 'ALO' sensor_id_c, 317 usage_counter_n FROM DUAL
3 UNION
4 SELECT 9641, 'ALO', 18 FROM DUAL
5 UNION
6 SELECT 9642, 'ALO', 0 FROM DUAL
7 UNION
8 SELECT 9659, 'MSP', 25 FROM DUAL --> MAX for MSP ...
9 UNION
10 SELECT 9660, 'MSP', 10 FROM DUAL
11 UNION
12 SELECT 1000, 'MSP', 25 FROM DUAL --> ... but this is also MAX for MSP
13 )
14 SELECT *
15 FROM (SELECT a.*,
16 ROW_NUMBER ()
17 OVER (PARTITION BY sensor_id_c
18 ORDER BY usage_counter_n DESC) rn
19 FROM test a)
20 WHERE rn = 1;
ID_N SEN USAGE_COUNTER_N RN
---------- --- --------------- ----------
9640 ALO 317 1
1000 MSP 25 1
SQL>
RANK might be better:
SQL> l16
16* ROW_NUMBER ()
SQL> c/row_number/rank
16* rank ()
SQL> /
ID_N SEN USAGE_COUNTER_N RN
---------- --- --------------- ----------
9640 ALO 317 1
1000 MSP 25 1
9659 MSP 25 1
SQL>
Or, using the oldfashioned way:
14 SELECT *
15 FROM test t
16 WHERE t.usage_counter_n = (SELECT MAX (t1.usage_counter_n)
17 FROM test t1
18 WHERE t1.sensor_id_c = t.sensor_id_c)
19 ORDER BY sensor_id_c;
ID_N SEN USAGE_COUNTER_N
---------- --- ---------------
9640 ALO 317
9659 MSP 25
1000 MSP 25
SQL>
Depending on your requirements to handle duplicates, the KEEP clause might also be a useful option
SQL> with test
2 AS (SELECT 9640 ID_n, 'ALO' sensor_id_c, 317 usage_counter_n FROM DUAL
3 UNION
4 SELECT 9641, 'ALO', 18 FROM DUAL
5 UNION
6 SELECT 9642, 'ALO', 0 FROM DUAL
7 UNION
8 SELECT 9659, 'MSP', 25 FROM DUAL --> MAX for MSP ...
9 UNION
10 SELECT 9660, 'MSP', 10 FROM DUAL
11 UNION
12 SELECT 1000, 'MSP', 25 FROM DUAL --> ... but this is also MAX for MSP
13 )
14 SELECT sensor_id_c,
15 min(id_n) keep ( dense_rank first order by usage_counter_n DESC ) id_n,
16 min(usage_counter_n) keep ( dense_rank first order by usage_counter_n DESC ) usage_counter_n
17 from test
18 group by sensor_id_c;
SEN ID_N USAGE_COUNTER_N
--- ---------- ---------------
ALO 9640 317
MSP 1000 25

oracle sql - numbering group of rows

i have the following table with different prices in every week and need a numbering like in the last column. consecutive rows with same prices should have the same number like in weeks 11/12 or 18/19. but on the other side weeks 2 and 16 have the same prices but are not consecutive so they should get a different number.
w | price | r1 | need
===========================
1 167,93 1 1
2 180 1 2
3 164,72 1 3
4 147,42 1 4
5 133,46 1 5
6 145,43 1 6
7 147 1 7
8 147,57 1 8
9 150,95 1 9
10 158,14 1 10
11 170 1 11
12 170 2 11
13 166,59 1 12
14 161,06 1 13
15 162,88 1 14
16 180 2 15
17 183,15 1 16
18 195 1 17
19 195 2 17
i have already experimented with the analytics functions (row_number, rank, dens_rank), but didn't found a solution for this problem so far.
(oracle sql 10,11)
does anyone have a hint? thanks.
Simulating your table first:
SQL> create table mytable (w,price,r1)
2 as
3 select 1 , 167.93, 1 from dual union all
4 select 2 , 180 , 1 from dual union all
5 select 3 , 164.72, 1 from dual union all
6 select 4 , 147.42, 1 from dual union all
7 select 5 , 133.46, 1 from dual union all
8 select 6 , 145.43, 1 from dual union all
9 select 7 , 147 , 1 from dual union all
10 select 8 , 147.57, 1 from dual union all
11 select 9 , 150.95, 1 from dual union all
12 select 10, 158.14, 1 from dual union all
13 select 11, 170 , 1 from dual union all
14 select 12, 170 , 2 from dual union all
15 select 13, 166.59, 1 from dual union all
16 select 14, 161.06, 1 from dual union all
17 select 15, 162.88, 1 from dual union all
18 select 16, 180 , 2 from dual union all
19 select 17, 183.15, 1 from dual union all
20 select 18, 195 , 1 from dual union all
21 select 19, 195 , 2 from dual
22 /
Table created.
Your need column is calculated in two parts: first compute a delta column which denotes whether the previous price-column differs from the current rows price column. If you have that delta column, the second part is easy by computing the sum of those deltas.
SQL> with x as
2 ( select w
3 , price
4 , r1
5 , case lag(price,1,-1) over (order by w)
6 when price then 0
7 else 1
8 end delta
9 from mytable
10 )
11 select w
12 , price
13 , r1
14 , sum(delta) over (order by w) need
15 from x
16 /
W PRICE R1 NEED
---------- ---------- ---------- ----------
1 167.93 1 1
2 180 1 2
3 164.72 1 3
4 147.42 1 4
5 133.46 1 5
6 145.43 1 6
7 147 1 7
8 147.57 1 8
9 150.95 1 9
10 158.14 1 10
11 170 1 11
12 170 2 11
13 166.59 1 12
14 161.06 1 13
15 162.88 1 14
16 180 2 15
17 183.15 1 16
18 195 1 17
19 195 2 17
19 rows selected.
You can nest your analytic functions using inline views, so you first group the consecutive weeks with same prices and then dense_rank using those groups:
select w
, price
, r1
, dense_rank() over (
order by first_w_same_price
) drank
from (
select w
, price
, r1
, last_value(w_start_same_price) ignore nulls over (
order by w
rows between unbounded preceding and current row
) first_w_same_price
from (
select w
, price
, r1
, case lag(price) over (order by w)
when price then null
else w
end w_start_same_price
from your_table
)
)
order by w
The innermost inline view with LAG function lets the starting week of every consecutive group get it's own week number, but every consecutive week with same price gets null (weeks 12 and 19 in your data.)
The middle inline view with LAST_VALUE function then use the IGNORE NULLS feature to give the consecutive weeks the same value as the first week within each group. So week 11 and 12 both gets 11 in first_w_same_price and week 18 and 19 both gets 18 in first_w_same_price.
And finally the outer query use DENSE_RANK to give the desired result.
For each row you should count previous rows where (w-1) row price isn't the same as (w) price:
select T1.*,
(SELECT count(*)
FROM T T2
JOIN T T3 ON T2.w-1=T3.w
WHERE T2.Price<>T3.Price
AND T2.W<=T1.W)+1 rn
from t T1
SQLFiddle demo
Try this:
with tt as (
select t.*, decode(lag(price) over(order by w) - price, 0, 1, 0) diff
from t
)
select w
, price
, r1
, row_number() over (order by w) - sum(diff) over(order by w rows between UNBOUNDED PRECEDING and current row) need
from tt
SELECT w, price, r1,
ROW_NUMBER () OVER (PARTITION BY price ORDER BY price) row_column
FROM TABLE