Select Tree Structure from two tables - sql

let us assume I have Table A
| PK | Name |
-------------
| 1 | AA |
| 2 | BB |
| 3 | CC |
and table B
| PK | FK | Value |
-------------------
| 1 | 1 | i |
| 2 | 1 | j |
| 3 | 2 | x |
| 4 | 2 | y |
| 5 | 3 | l |
| 6 | 3 | k |
how can I select the below result
| PK | Name |
-------------
| 1 | AA |
| 1 | i |
| 2 | j |
| 2 | BB |
| 3 | x |
| 4 | y |
| 3 | CC |
| 3 | l |
| 4 | k |
List parents and under each parent list its children
Many Thanks for help

Very interesting table design. I believe it's just a matter of unioning your data and then ordering your results how you want them. If it's only a single level child-parent relationship, this should work just fine.
If Object_Id('tempdb..#TableA') Is Not Null Drop Table #TableA;
If Object_Id('tempdb..#TableB') Is Not Null Drop Table #TableB;
Select * Into #TableA
From (Values (1,'AA'),(2,'BB'),(3,'CC')) As a(PK,[Name])
Select * Into #TableB
From (Values (1,1,'i'),(2,1,'j'),(3,2,'x'),(4,2,'y'),(5,3,'l'),(6,3,'k')) As a(PK,FK,[Value])
;With Cte
As
(
Select PK,[Name],PK As OrderID,0 As LevelID /*Used to ensure parents are put above children*/
From #TableA
Union All
Select PK,[Value],FK,1
From #TableB
)
Select [PK], [Name]
From Cte
Order By OrderID,LevelID
Results:
PK | Name
1 | AA
1 | i
2 | j
2 | BB
3 | x
4 | y
3 | CC
5 | l
6 | k
Note: My last two rows(l and k) are a bit different than results. I assumed you it was typo when you put 3 and 4 as the id's rather than 5 and 6

Related

Count without using functions (like count) oracle

I have two tables:
TABLE A :
CREATE TABLE z_ostan ( id NUMBER PRIMARY KEY,
name VARCHAR2(30) NOT NULL CHECK (upper(name)=name)
);
TABLE B:
CREATE TABLE z_shahr ( id NUMBER PRIMARY KEY,
name VARCHAR2(30) NOT NULL CHECK (upper(name)=name),
ref_ostan NUMBER,
CONSTRAINT fk_ref_ostan FOREIGN KEY (ref_ostan) REFERENCES z_ostan(id)
);
How can I find the second and third place "id" from -Table A- The least used table B in the table? Without using predefined functions like "count()"
This only processes existing references to Table A.
Updated for oracle (used 12c)
Without using any aggregate or window functions:
Sample data for Table: tblb
+----+---------+---------+
| id | name | tbla_id |
+----+---------+---------+
| 1 | TBLB_01 | 1 |
| 2 | TBLB_02 | 1 |
| 3 | TBLB_03 | 1 |
| 4 | TBLB_04 | 1 | 4 rows
| 5 | TBLB_05 | 2 |
| 6 | TBLB_06 | 2 |
| 7 | TBLB_07 | 2 | 3 rows
| 8 | TBLB_08 | 3 |
| 9 | TBLB_09 | 3 |
| 10 | TBLB_10 | 3 |
| 11 | TBLB_11 | 3 |
| 12 | TBLB_12 | 3 |
| 13 | TBLB_13 | 3 | 6 rows
| 14 | TBLB_14 | 4 |
| 15 | TBLB_15 | 4 |
| 16 | TBLB_16 | 4 | 3 rows
| 17 | TBLB_17 | 5 | 1 row
| 18 | TBLB_18 | 6 |
| 19 | TBLB_19 | 6 | 2 rows
| 20 | TBLB_20 | 7 | 1 row
+----+---------+---------+
There are many ways to express this logic.
Step by step with CTE terms.
The intent is (for each set of tbla_id rows in tblb)
generate a row_number (n) for the rows in each partition.
We would normally use window functions for this.
But I assume these are not allowed.
Use this row_number (n) to determine the count of rows in each tbla_id partition.
To find that count per partition, find the last row in each partition (from step 1).
Order the results of step 2 by n of these last rows.
Choose the 2nd and 3rd row of this result
Done.
WITH first AS ( -- Find the first row per tbla_id
SELECT t1.*
FROM tblb t1
LEFT JOIN tblb t2
ON t1.id > t2.id
AND t1.tbla_id = t2.tbla_id
WHERE t2.id IS NULL
)
, rnum (id, name, tbla_id, n) AS ( -- Generate a row_number (n) for each tbla_id partition
SELECT f.*, 1 FROM first f UNION ALL
SELECT n.id, n.name, n.tbla_id, c.n+1
FROM rnum c
JOIN tblb n
ON c.tbla_id = n.tbla_id
AND c.id < n.id
LEFT JOIN tblb n2
ON n.tbla_id = n2.tbla_id
AND c.id < n2.id
AND n.id > n2.id
WHERE n2.id IS NULL
)
, last AS ( -- Find the last row in each partition to obtain the count of tbla_id references
SELECT t1.*
FROM rnum t1
LEFT JOIN rnum t2
ON t1.id < t2.id
AND t1.tbla_id = t2.tbla_id
WHERE t2.id IS NULL
)
SELECT * FROM last
ORDER BY n, tbla_id OFFSET 1 ROWS FETCH NEXT 2 ROWS ONLY
;
Final Result, where n is the count of references to tbla:
+------+---------+---------+------+
| id | name | tbla_id | n |
+------+---------+---------+------+
| 20 | TBLB_20 | 7 | 1 |
| 19 | TBLB_19 | 6 | 2 |
+------+---------+---------+------+
Some intermediate results...
last CTE term result. The 2nd and 3rd rows of this become the final result.
+------+---------+---------+------+
| id | name | tbla_id | n |
+------+---------+---------+------+
| 17 | TBLB_17 | 5 | 1 |
| 20 | TBLB_20 | 7 | 1 |
| 19 | TBLB_19 | 6 | 2 |
| 7 | TBLB_07 | 2 | 3 |
| 16 | TBLB_16 | 4 | 3 |
| 4 | TBLB_04 | 1 | 4 |
| 13 | TBLB_13 | 3 | 6 |
+------+---------+---------+------+
rnum CTE term result. This provides the row_number over tbla_id partitions ordered by id
+------+---------+---------+------+
| id | name | tbla_id | n |
+------+---------+---------+------+
| 1 | TBLB_01 | 1 | 1 |
| 2 | TBLB_02 | 1 | 2 |
| 3 | TBLB_03 | 1 | 3 |
| 4 | TBLB_04 | 1 | 4 |
| 5 | TBLB_05 | 2 | 1 |
| 6 | TBLB_06 | 2 | 2 |
| 7 | TBLB_07 | 2 | 3 |
| 8 | TBLB_08 | 3 | 1 |
| 9 | TBLB_09 | 3 | 2 |
| 10 | TBLB_10 | 3 | 3 |
| 11 | TBLB_11 | 3 | 4 |
| 12 | TBLB_12 | 3 | 5 |
| 13 | TBLB_13 | 3 | 6 |
| 14 | TBLB_14 | 4 | 1 |
| 15 | TBLB_15 | 4 | 2 |
| 16 | TBLB_16 | 4 | 3 |
| 17 | TBLB_17 | 5 | 1 |
| 18 | TBLB_18 | 6 | 1 |
| 19 | TBLB_19 | 6 | 2 |
| 20 | TBLB_20 | 7 | 1 |
+------+---------+---------+------+
There are a few other ways to tackle this problem in just SQL.

How to handle multiple rows fulfilling criteria when joining Oracle tables

Given a table of roles, companies and a employee table where we store for each employee which role he/she has at each company.
I'm trying to create a view which indicates for each combination of role and company and employee by a ‘Y’ or ‘N’ in the “checked_yn” column, whether this employee has this role at this company.
company table
----------------
|ID | name |
-----------------
| 1 | A |
| 2 | B |
-----------------
roles table
-------------
|ID | role |
-------------
| 1 | X |
| 2 | Y |
| 3 | Z |
-------------
employee table
----------------------------------------------
|ID | company_id | role_id | employee_log_id |
---------------------------------------------|
| 1 | 1 | 1 | 1 |
| 2 | 1 | 2 | 1 |
| 3 | 2 | null | 1 |
----------------------------------------------
The desired outcome is this:
EMPLOYEE_ROLES_VW view
------------------------------------------------------------------------
|Id |company_id | role_id | Checked_yn | employee_id | employee_log_id |
|----------------------------------------------------------------------|
| 1 | 1 | 1 | Y | 1 | 1 |
| 2 | 1 | 2 | Y | 2 | 1 |
| 3 | 1 | 3 | N | null | 1 |
| 4 | 2 | 1 | N | null | 1 |
| 5 | 2 | 2 | N | null | 1 |
| 6 | 2 | 3 | N | null | 1 |
------------------------------------------------------------------------
This is my current query:
with ROLES_X_COMP as (SELECT ROL.ID AS X_ROLE_ID,
COM.ID AS X_COMPANY_ID,
FROM ROLES ROL
CROSS JOIN COMPANY COM)
SELECT ROWNUM AS ID,
EMP.ID AS SMCR_EMPLOYEE_ID,
EMP.EMPLOYEE_LOG_ID AS EMPLOYEE_LOG_ID,
ROLES_X_COMP.X_ROLE_ID ,
EMP.ROLE_ID AS ROLE_ID,
ROLES_X_COMP.X_COMPANY_ID,
EMP.COMPANY_ID AS COMPANY_ID,
CASE
WHEN ROLES_X_COMP.X_ROLE_ID = SE.ROLE_ID AND ROLES_X_COMP.X_COMPANY_ID =
SE.COMPANY_ID THEN 'Y'
ELSE 'N' END AS CHECKED_YN
FROM ROLES_X_COMP
LEFT OUTER JOIN EMPLOYEE EMP ON ROLES_X_COMP.X_COMPANY_ID = EMP.COMPANY_ID
Because of the join on EMPLOYEE “finds” the company with id=1 twice it joins twice with the cross join of role and company table. So I'm getting this result:
------------------------------------------------------------------------
|Id |company_id | role_id | Checked_yn | employee_id | employee_log_id |
|----------------------------------------------------------------------|
| 1 | 1 | 1 | Y | 1 | 1 |
| 2 | 1 | 2 | N | 1 | 1 |
| 3 | 1 | 3 | N | 1 | 1 |
| 4 | 1 | 1 | N | 2 | 1 |
| 5 | 1 | 2 | Y | 2 | 1 |
| 6 | 1 | 3 | N | 2 | 1 |
| 7 | 2 | 1 | N | 3 | 1 |
| 8 | 2 | 2 | N | 3 | 1 |
| 9 | 2 | 3 | N | 3 | 1 |
------------------------------------------------------------------------
I think a JOIN might be the wrong option here and a UNION more appropriate but I can't figure it out.
Use a partitioned outer join:
Query:
SELECT ROWNUM AS id,
e.company_id,
r.id AS role_id,
NVL2( e.role_id, 'Y', 'N' ) AS CheckedYN,
e.role_id AS employee_id,
e.employee_log_id
FROM roles r
LEFT OUTER JOIN
employee e
PARTITION BY ( e.company_id, e.employee_log_id )
ON ( r.id = e.role_id )
or (depending on how you want to partition and join the data):
SELECT ROWNUM AS id,
c.id AS company_id,
r.id AS role_id,
NVL2( e.role_id, 'Y', 'N' ) AS CheckedYN,
e.role_id AS employee_id,
e.employee_log_id
FROM roles r
CROSS JOIN
company c
LEFT OUTER JOIN
employee e
PARTITION BY ( e.employee_log_id )
ON ( c.id = e.company_id AND r.id = e.role_id )
Output:
Both output the same for the test data but may give differing results depending on your actual data.
ID | COMPANY_ID | ROLE_ID | CHECKEDYN | EMPLOYEE_ID | EMPLOYEE_LOG_ID
-: | ---------: | ------: | :-------- | ----------: | --------------:
1 | 1 | 1 | Y | 1 | 1
2 | 1 | 2 | Y | 2 | 1
3 | 1 | 3 | N | null | 1
4 | 2 | 1 | N | null | 1
5 | 2 | 2 | N | null | 1
6 | 2 | 3 | N | null | 1
db<>fiddle here
AND ROLES_X_COMP.X_ROLE_ID = EMP.ROLE_ID
Is missing at the end of your query
But the outcome will be
EMPLOYEE_ROLES_VW view
------------------------------------------------------------------------
|Id |company_id | role_id | Checked_yn | employee_id | employee_log_id |
|----------------------------------------------------------------------|
| 1 | 1 | 1 | Y | 1 | 1 |
| 2 | 1 | 2 | Y | 2 | 1 |
| 3 | 1 | 3 | N | null | null |
| 4 | 2 | 1 | N | null | null |
| 5 | 2 | 2 | N | null | null |
| 6 | 2 | 3 | N | null | null |
------------------------------------------------------------------------

How to Find Items that Do NOT Have a pre-Pipe "Base" Value

I have a database with a column (obj_id) in a table (parts) where I SHOULD have an obj_id of 12345 that is a set for another row that would have 12345|.
So:
select obj_id from parts where obj_id like '12345%';
12345
12345|A
12345|B
12345|77
Now, someone violated the guideline and put in some items with the piped-value but not the base value w/o the pipe (e.g. 12378|J, 12378|8 but not 12378).
I need to know how to write a SQL query to find these piped-values that do NOT have their matching base (non-piped) value in the table.
Without some realistic sample data to work with it's hard to know what you really want. Below a 2 queries that may be of assistance, but perhaps it will also make you note how useful sample data can be:
See this working at SQL Fiddle
CREATE TABLE PARTS
(id int, OBJ_ID varchar2(200))
;
INSERT ALL
INTO PARTS (id, OBJ_ID)
VALUES (1,'12345 12345|A 12345|B 12345|77')
INTO PARTS (id, OBJ_ID)
VALUES (2,'12346|A 12346|B 12346|77')
INTO PARTS (id, OBJ_ID)
VALUES (3,'12378|J, 12378|8')
INTO PARTS (id, OBJ_ID)
VALUES (4,NULL)
INTO PARTS (id, OBJ_ID)
VALUES (5,'fred. wilma, barney, betty')
SELECT * FROM dual
;
Query 1:
select
*
from PARTS p
where instr(p.OBJ_ID,' ') > instr(p.OBJ_ID,'|')
Results:
| ID | OBJ_ID |
|----|----------------------------|
| 2 | 12346|A 12346|B 12346|77 |
| 3 | 12378|J, 12378|8 |
| 5 | fred. wilma, barney, betty |
Query 2:
select
id, rn_a, regexp_substr (OBJ_ID_SPLIT, '[^|]+', 1, rn_b) as OBJ_ID_SPLIT
from (
select
p.id, c1.rn_a, regexp_substr (p.OBJ_ID, '[^ ]+', 1, c1.rn_a) as OBJ_ID_SPLIT
from PARTS p
cross join (select rownum as rn_a
from (select max(length (regexp_replace (OBJ_ID, '[^|]+'))) + 1 as mx
from PARTS
)
connect by level <= mx) c1
where p.OBJ_ID like '%|%'
) d
cross join (select 1 rn_b from dual union all select 2 from dual) c2
order by id, rn_a
Results:
| ID | RN_A | OBJ_ID_SPLIT |
|----|------|--------------|
| 1 | 1 | 12345 |
| 1 | 1 | (null) |
| 1 | 2 | 12345 |
| 1 | 2 | A |
| 1 | 3 | 12345 |
| 1 | 3 | B |
| 1 | 4 | 12345 |
| 1 | 4 | 77 |
| 2 | 1 | 12346 |
| 2 | 1 | A |
| 2 | 2 | 12346 |
| 2 | 2 | B |
| 2 | 3 | 12346 |
| 2 | 3 | 77 |
| 2 | 4 | (null) |
| 3 | 1 | 12378 |
| 3 | 1 | J, |
| 3 | 2 | 12378 |
| 3 | 2 | 8 |
| 3 | 3 | (null) |
| 3 | 4 | (null) |

Limit a sorted number of rows joined

I have two tables, A and B, and a join table M. I want to, for each A.id, get the top 2 B.id's sorting on the value in table M, producing the results below. This is running on an Azure SQL database
Table A Table M Table B
+-----+ +-----+-----+-------+ +-----+
| Id | | AId | BId | Value | | Id |
+-----+ +-----+-----+-------+ +-----+
| 1 | | 1 | 3 | 4 | | 1 |
| 2 | | 1 | 2 | 3 | | 2 |
| 3 | | 3 | 2 | 3 | | 3 |
| 4 | | 3 | 5 | 6 | | 4 |
+-----+ | 3 | 3 | 4 | | 5 |
| 4 | 1 | 2 | +-----+
| 4 | 2 | 1 |
| 4 | 4 | 3 |
+-----+-----+-------+
Result
+-----+-----+-------+
| AId | BId | Value |
+-----+-----+-------+
| 1 | 3 | 4 |
| 1 | 2 | 3 |
| 3 | 5 | 6 |
| 3 | 3 | 4 |
| 4 | 1 | 2 |
| 4 | 4 | 3 |
+-----+-----+-------+
I know that I can select all the M.AId rows where they equal 1, sort it, and limit by 2, but I need to do this for every row in Table A. I've made an attempt to use group by, but I wasn't sure how to sort and limit it. I've also tried to search for resources associated with this issue but I couldn't find any resources.
(I also wasn't sure how to word the title for this issue)
You can just use ROW_NUMBER:
SELECT
AId, BId, Value
FROM (
SELECT *,
Rn = ROW_NUMBER() OVER(PARTITION BY AId ORDER BY Value DESC)
FROM M
) t
WHERE Rn <= 2

Create a combined list from two tables

I have a table with CostCenter_ID (int) and a second table with Process_ID (int).
I'd like to combine the results of both tables so that each cost center ID is assigned to all process IDs, like so:
|CostCenterID | ProcessID |
---------------------------
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 1 |
| 2 | 2 |
| 2 | 3 |
| 3 | 1 |
| 3 | 2 |
| 3 | 3 |
I've done it before but I'm drawing a blank. I've tried this:
SELECT CostCenter_ID,NULL FROM dbo.Cost_Centers
UNION ALL
SELECT NULL,Process_ID FROM dbo.Processes
which returns this:
|CostCenterID | ProcessID |
---------------------------
| 1 | NULL |
| NULL | 1 |
| NULL | 2 |
| NULL | 3 |
Try:
select a.CostCenterID, b.ProcessID
from table1 a
cross join table2 b
or:
select a.CostCenterID, b.ProcessID
from table1 a
,table2 b
NB: cross join is the better method as it makes it clearer to the reader what your intentions are.
More info (with pics) here: http://www.w3resource.com/sql/joins/cross-join.php