Join on a selected number of digits - sql

The column ddm_zip has only 5 digit zipcodes. The column tt_pad_zip contains values anywhere between 2-5. I would like to perform a join selecting values that appear on both sides of the table.
However, tt_pad_zip has various levels of granularity.
For e.g. when we see value of 55 in tt_pad_zip, it is considering all zipcodes that start with 55, for e.g. 551...553...55636 etc. Hence I want the join to select this.
For e.g. when we see value of 558 in tt_pad_zip, it is considering all zipcodes that start with 558, for e.g. 5581...5585...558743 etc. Hence I want the join to select this.
However if there is a value in tt_pad_zip that has the exact zipcode as ddm_zip e.g. 58636 as shown below, I would like the output to pick this .
column: ddm_zip column: tt_pad_zip
55636 55
57254 5734
58636 57254
58636

Join on the SUBSTRing of ddm_zip that has equal length to the tt_pad_zip value you are comparing it to. Either:
SUBSTR( ddm_zip, 1, LENGTH( tt_pad_zip ) ) = tt_pad_zip
or:
ddm_zip LIKE tt_pad_zip || '%'
Oracle Setup:
CREATE TABLE table1 ( ddm_zip ) AS
SELECT '55636' FROM DUAL UNION ALL
SELECT '57254' FROM DUAL UNION ALL
SELECT '55824' FROM DUAL;
CREATE TABLE table2 ( tt_pad_zip ) AS
SELECT '55' FROM DUAL UNION ALL
SELECT '5734' FROM DUAL UNION ALL
SELECT '57254' FROM DUAL UNION ALL
SELECT '55636' FROM DUAL;
Query:
SELECT *
FROM table1 t1
INNER JOIN
table2 t2
ON t1.ddm_zip LIKE t2.tt_pad_zip || '%'
Output:
DDM_ZIP | TT_PAD_ZIP
:------ | :---------
55636 | 55
55636 | 55636
57254 | 57254
55824 | 55
Query 2: If you want the most granular match.
SELECT DDM_ZIP,
MAX( TT_PAD_ZIP ) AS TT_PAD_ZIP
FROM table1 t1
INNER JOIN
table2 t2
ON t1.ddm_zip LIKE t2.tt_pad_zip || '%'
GROUP BY DDM_ZIP
Output:
DDM_ZIP | TT_PAD_ZIP
:------ | :---------
55636 | 55636
57254 | 57254
55824 | 55
db<>fiddle here

I think you are looking for something like the following:
-- Sample Data:
WITH dat (ddm_zip, tt_pad_zip) AS
(SELECT 0,55 FROM dual
UNION ALL
SELECT 1,5734 FROM dual
UNION ALL
SELECT 2,57254 FROM dual
UNION ALL
SELECT 3,55636 FROM dual
UNION ALL
SELECT 55636, 99 FROM dual
UNION ALL
SELECT 57254, 88 FROM dual
UNION ALL
SELECT 57341, 77 FROM dual)
-- Query:
SELECT d2.ddm_zip, d1.tt_pad_zip
FROM dat d1
JOIN dat d2
-- Join the data via like:
ON d2.ddm_zip LIKE d1.tt_pad_zip || '%'
-- Exclude the data where a "better" match exists:
AND NOT EXISTS (SELECT 1 FROM dat d3
WHERE d2.ddm_zip LIKE d3.tt_pad_zip || '%'
AND LENGTH(d1.tt_pad_zip) < LENGTH(d3.tt_pad_zip))

You can use conditional join with a CASE statement in the ON clause:
select t1.ddm_zip, t2.tt_pad_zip
from table1 t1 inner join table2 t2
on t1.ddm_zip like case
when exists(select 1 from table2 where tt_pad_zip = t1.ddm_zip) then t2.tt_pad_zip
else '%' || t2.tt_pad_zip || '%'
end
See the demo.
Results:
> DDM_ZIP | TT_PAD_ZIP
> :------ | :---------
> 55636 | 55
> 57254 | 57254
> 58636 | 58636

You could try using the SUBSTR operation to just compare the first two numbers of each column. I don't know which type of join you want, your table names, or example data, but here is a good syntax.
SELECT
*
FROM
table1 INNER JOIN table2 ON SUBSTR(ddm_zip, 1, 2) = SUBSTR(tt_pad_zip, 1,2)

Related

How to count the number of occurrences of a attribute in a string of another table

Im struggling with a complex SQL query, I want to count how many times attribute x in table 1 shows up in a string in table 2.
so t1 has a name attribute and t2 has an participants attribute and a project attribute
ID | Name
---------
0 | Bob
1 | Bill
2 | Jill
T2
Project | Participants
-----------------
0 | Bob, Bill
1 | Bob, Jill
2 | Bob
Output
Bob 3
Bill 1
Jill 1
Participants in t2 is a string. Is there anyway to do this?
You should primarily focus on fixing your data model. Each participant to each project should be stored as a separate row rather than munged in a delimited string, possibly referencing the other table through a foreign key constraint:
project_id participant_id
0 0 -- Bob
0 1 -- Bill
1 0 -- Bob
1 2 -- Jill
2 0 -- Bob
Then you could efficiently phrase the query:
select t1.*
(select count(*) from table2 t2 where t2.participant_id = t1.id) cnt
from table1 t1
That said, given your current layout, one option uses string functions:
select t1.*,
(
select count(*)
from table2 t2
where ', ' || t2.participants || ', ' like '%, ' || t1.name || ', %'
) cnt
from table1 t1
If the participants only appear once in each project then you can use:
SELECT t1.Name,
COUNT( t2.Participants) AS cnt
FROM t1
LEFT OUTER JOIN t2
ON ( ', ' || t2.Participants || ', ' LIKE '%, ' || t1.Name || ', %' )
GROUP BY t1.Name
Which, for the sample data:
CREATE TABLE t1 ( ID, Name ) AS
SELECT 0, 'Bob' FROM DUAL UNION ALL
SELECT 1, 'Bill' FROM DUAL UNION ALL
SELECT 2, 'Jill' FROM DUAL UNION ALL
SELECT 3, 'Tim' FROM DUAL;
CREATE TABLE T2 ( Project, Participants ) AS
SELECT 0, 'Bob, Bill' FROM DUAL UNION ALL
SELECT 1, 'Bob, Jill' FROM DUAL UNION ALL
SELECT 2, 'Bob' FROM DUAL;
Outputs:
NAME | CNT
:--- | --:
Bill | 1
Jill | 1
Bob | 3
Tim | 0
db<>fiddle here

How to select first x records from second table

I would like to get all records from first table and only x records from second table.
How many records from second table I have info in first table :
My tables are
table1 :
WITH table1(a,b) AS
(
SELECT 'aa',3 FROM dual UNION ALL
SELECT 'bb',2 FROM dual UNION ALL
SELECT 'cc',4 FROM dual
)
SELECT *
FROM table1;
a | b (number of records from table2 (x))
------
aa | 3
bb | 2
cc | 4
table2 :
WITH table2(a,b) AS
(
SELECT 'aa','1xx' FROM dual UNION ALL
SELECT 'aa','2yy' FROM dual UNION ALL
SELECT 'aa','3ww' FROM dual UNION ALL
SELECT 'aa','4zz' FROM dual UNION ALL
SELECT 'aa','5qq' FROM dual UNION ALL
SELECT 'bb','1aa' FROM dual UNION ALL
SELECT 'bb','2bb' FROM dual UNION ALL
SELECT 'bb','3cc' FROM dual UNION ALL
SELECT 'cc','1oo' FROM dual UNION ALL
SELECT 'cc','2uu' FROM dual UNION ALL
SELECT 'cc','3tt' FROM dual UNION ALL
SELECT 'cc','4zz' FROM dual UNION ALL
SELECT 'cc','5rr' FROM dual
)
SELECT *
FROM table2;
a | b
--------
aa | 1xx
aa | 2yy
aa | 3ww
aa | 4zz
aa | 5qq
bb | 1aa
bb | 2bb
bb | 3cc
bb | 4dd
bb | 5ee
cc | 1oo
cc | 2uu
cc | 3tt
cc | 4zz
cc | 5rr
Expected Result:
a | b
--------
aa | 1xx
aa | 2yy
aa | 3ww
bb | 1aa
bb | 2bb
cc | 1oo
cc | 2uu
cc | 3tt
cc | 4zz
You can use ROW_NUMBER() analytic function with LEFT/RIGHT OUTER JOIN among the tables :
WITH t2 AS
(
SELECT t2.a,t2.b, ROW_NUMBER() OVER (PARTITION BY t2.a ORDER BY t2.b) AS rn
FROM table2 t2
)
SELECT t2.a, t2.b
FROM t2
LEFT JOIN table1 t1
ON t1.a = t2.a
WHERE rn <= t1.b
Demo
You need to write something like:
SELECT a,
b
FROM Table2 T,
( SELECT LEVEL L FROM DUAL
CONNECT BY LEVEL <= (SELECT MAX(b) FROM Table1)
) A
WHERE T.b>= A.L
ORDER BY T.a;
Ideally you should have a ordering column in table2. When you say first X rows it does not make any sense unless you have something like an id or date field to order the records.
Anyway, assuming the number part of the column b in table 2 for ordering and assuming the number would be followed by 2 characters only such as xx,yy etc you can use the logic below
Select Tb1.a, Tb1.b
from
(Select t.*, row_number() over (partition by a order by substr(b,1,length(b)-2)) as seq
from Table2 t) Tb1
join Table1 Tb2
on Tb1.a = Tb2.a
Where Tb1.seq <= Tb2.b;
Demo - https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=3030b2372bcbb007606bbb6481af9884
Again it's just a job for lateral:
WITH prep AS
(
SELECT *
FROM tab1,
LATERAL
(
SELECT LEVEL AS lvl
FROM dual
CONNECT BY LEVEL <= b
)
)
SELECT p.a, t2.b
FROM prep p
JOIN tab2 t2
ON p.lvl = regexp_substr(t2.b,'^\d+')
AND p.a = t2.a
ORDER BY p.a, p.lvl

OracleSQL -> Sort View by three levels

I created the following View in my Oracle SQL Developer:
The Results are not sorted In the way I need them.
I need to sort the results like this:
So If you would draw it as a graph with a depth of three
It would look like this:
The Maximum level depth is three. I tried different approaches with select connect_by_root, but it does not work. ORDER BY Also does no work because of the tree structure of the data.
Does anyone have a tip for me ?
You could use a left join:
select t.*
from t left join
t tp
on tp.id = t.parent_id
order by coalesce(tp.parent_id, t.parent_id, t.id),
coalesce(t.parent_id, t.id),
t.id
This assumes that the parent ids are smaller than the id, which seems to be true for your data. It is not a necessary assumption, but it simplifies the logic.
With a LEFT self join and conditional sorting:
select t1.*
from tablename t1
left join tablename t2 on t2.id = t1.parent_id
order by coalesce(t2.parent_id, t1.parent_id, t1.id),
case when t1.parent_id is null then 0 else 1 end,
case when t2.parent_id is not null then t1.parent_id else t1.id end,
case when t2.parent_id is null then 0 else 1 end,
t1.id
See the demo.
Results:
> ID | PARENT_ID | LEVEL
> -----: | --------: | :--------
> 29101 | null | LVL_ONE
> 153799 | 29101 | LVL_TWO
> 153800 | 153799 | LVL_THREE
> 153801 | 153799 | LVL_THREE
> 153803 | 29101 | LVL_TWO
> 153804 | 153803 | LVL_THREE
> 153802 | null | LVL_ONE
> 153805 | 153802 | LVL_TWO
> 153806 | 153805 | LVL_THREE
If the view you have used is a hierarchial query, then you have an option to "order siblings by", i would suggest you use that
Here is an example
https://oracle-base.com/articles/misc/hierarchical-queries
drop table a1;
CREATE TABLE a1
as
SELECT 153804 as ID, 153803 as PARENT_ID
FROM DUAL
UNION ALL
SELECT 153801, 153799
FROM DUAL
UNION ALL
SELECT 153803, 29101
FROM DUAL
UNION ALL
SELECT 29101, NULL
FROM DUAL
UNION ALL
SELECT 153802, NULL
FROM DUAL
UNION ALL
SELECT 153805, 153802
FROM DUAL
UNION ALL
SELECT 153806, 153805
FROM DUAL
UNION ALL
SELECT 153800, 153799
FROM DUAL
UNION ALL
SELECT 153799, 29101
FROM DUAL
;
select id, parent_id, level, 'LEVEL_' || level as level_desc
from a1
start with parent_id is null
connect by parent_id = prior(id)
order siblings by id
;

Oracle sql group sum

I have table With ID,Sub_ID and value coloumns
ID SUB_ID Value
100 1 100
100 2 150
101 1 100
101 2 150
101 3 200
102 1 100
SUB ID can vary from 1..maxvalue( In this example it is 3). I need Sum of values for each Sub_ID. If SUB_ID is less than MAXVALUE for a particlaur ID then it should take MAX(SUB_ID) of each ID As shown below ( In this example for ID=100 for SUB_ID 3 it should take 150 i.e 2<3 so value=150))
SUB_ID SUM(values) Remarks
1 300 (100+100+100)
2 400 (150+150+100)
3 450 (150+200+100)
This can be easily done in PL/SQL . Can we use SQL for the same using Model Clause or any other options
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TableA ( ID, SUB_ID, Value ) AS
SELECT 100, 1, 100 FROM DUAL
UNION ALL SELECT 100, 2, 150 FROM DUAL
UNION ALL SELECT 101, 1, 100 FROM DUAL
UNION ALL SELECT 101, 2, 150 FROM DUAL
UNION ALL SELECT 101, 3, 200 FROM DUAL
UNION ALL SELECT 102, 1, 100 FROM DUAL
Query 1:
WITH sub_ids AS (
SELECT LEVEL AS sub_id
FROM DUAL
CONNECT BY LEVEL <= ( SELECT MAX( SUB_ID ) FROM TableA )
),
max_values AS (
SELECT ID,
MAX( VALUE ) AS max_value
FROM TableA
GROUP BY ID
)
SELECT s.SUB_ID,
SUM( COALESCE( a.VALUE, m.max_value ) ) AS total_value
FROM sub_ids s
CROSS JOIN
max_values m
LEFT OUTER JOIN
TableA a
ON ( s.SUB_ID = a.SUB_ID AND m.ID = a.ID )
GROUP BY
s.SUB_ID
Results:
| SUB_ID | TOTAL_VALUE |
|--------|-------------|
| 1 | 300 |
| 2 | 400 |
| 3 | 450 |
Try this
SELECT SUB_ID,SUM(values),
(SELECT DISTINCT SUBSTRING(
(
SELECT '+'+ CAST(values AS VARCHAR)
FROM table_Name AS T2
WHERE T2.SUB_ID = d.SUB_ID
FOR XML PATH ('')
),2,100000)[values]) as values
FROm table_Name d
GROUP BY SUB_ID
How about something like this:
select max_vals.sub_id, sum(nvl(table_vals.value,max_vals.max_value)) as sum_values
from (
select all_subs.sub_id, t1.id, max(t1.value) as max_value
from your_table t1
cross join (select sub_id from your_table) all_subs
group by all_subs.sub_id, t1.id
) max_vals
left outer join your_table table_vals
on max_vals.id = table_vals.id
and max_vals.sub_id = table_vals.sub_id
group by max_vals.sub_id;
The inner query gets you a list of all sub_id/id combinations and their fall-back values. The out query uses an nvl to use the table value if it exists and the fall-back value if it doesn't.

How to duplicate each row in sql query?

Suppose we have a query
SELECT * FROM my_table ORDER BY id
which results in
id | title
-----------
1 | 'ABC'
2 | 'DEF'
3 | 'GHI'
How could I modify given select statement to have each row duplicated in the result set like this:
id | title
-----------
1 | 'ABC'
1 | 'ABC'
2 | 'DEF'
2 | 'DEF'
3 | 'GHI'
3 | 'GHI'
Try this...
SELECT * FROM my_table
UNION ALL
SELECT * FROM my_table
ORDER BY id
You can use union all, but I like using cross join:
select *
from MyTable cross join
(select 1 from dual union all select 2 from dual) n
order by id;
The reason I like the cross join is in the case where MyTable is really some complicated subquery. Although the query optimizer might evaluate it only once, you can't really depend on that fact. So the performance should be better in this case.
You could cross join to a row generator, the numeric value indicates how many duplicates per original you want.
select *
from my_table
cross join
(select null
from dual
connect by level <= 2)
order by id