SQL View to flatten out a hierarchy - sql

I have a table with some parent child relationships. I want to create a view that has all possible ids for a location id.
I need the sql to do this
Table:
ID PARENT_ID LOCATION_ID
1 NULL ABC
2 1 XYZ
3 NULL EFG
view results:
LOCATION_ID ID
XYZ 1
XYZ 2
ABC 1
ABC 2
EFG 3

You don't mention the database you are using, so I'll assume PostgreSQL. You can adjust the answer to your specific engine:
with recursive
n as (
select id, id as grp, location_id from t where parent_id is null
union all
select t.id, n.grp, t.location_id
from n
join t on t.parent_id = n.id
)
select b.id, a.location_id
from n a
join n b on a.grp = b.grp
Result:
id location_id
-- -----------
1 ABC
2 ABC
1 XYZ
2 XYZ
3 EFG
For the record, the data script I used is:
create table t (
id int,
parent_id int,
location_id varchar(10)
);
insert into t (id, parent_id, location_id) values
(1, null, 'ABC'),
(2, 1, 'XYZ'),
(3, null, 'EFG');

Related

Return results from a table match on exact number of rows

I have two tables A and B, that are in a many to many relationship in a third table. What A want to achieve is get the "repeating" A rows based on B. For example:
table A table B table A_B
---------- ---------- ----------
1 A 1 A
2 B 1 B
3 C 2 A
4 D 2 B
5 3 A
3 B
3 C
4 A
4 D
5 A
What I want is, when searching table A_B by lets say '1', to get only 2, although 3 has both A and B and 4 has A, same goes for 5 too, it matches A but only A so it should be ignored as well. I've tried some suggestions form similar questions with cross join but I had no luck. I am trying to achieve this with just selects and joins, without stored procedures or temporary tables. Any suggestions is welcomed, thank you.
Repeat all base table rows for EACH left join row match
I want my output to look like:
table A_B
----------
2 A
2 B
Or if possible it would be even better if it matches the A_id by which the search is being done
table A_B
----------
1 A
1 B
2 A
2 B
However, the B_id column is not as important so if it is only
table A_B
----------
2
or
table A_B
----------
1
2
is acceptable as well.
EDIT 1:
Until now this is what I've came up with, although a bit unclean but it gets the expected result
select
A_id
from
tableA_B
where
A_id in
(
select
A_id
from
tableA_B
group by
A_id
having
count (A_id) IN (
select
count (A_id)
from
tableA_B
where
A_id = 1
)
)
AND
B_id IN (
select
B_id
from
tableA_B
where
A_id = 1
)
group by
A_id
Basically process of elimination, step by step. It would be ideal if it took only one step.
EDIT 2:
I'm sorry I left out some important information, my B values can be repeated for instance
table A table B table A_B
---------- ---------- ----------
1 A 1 A
2 B 1 B
3 c 2 A
4 D 2 B
5 AB 3 A
6 3 B
3 C
4 A
4 D
5 A
6 AB
so using XML path may return incorrect values. Because in my case it will return 6 as well which is incorrect. I apologies for leaving out this information.
Other solution which use INTERSECT could be:
CREATE TABLE tableA_B (A_id INT, B_id VARCHAR(8))
GO
INSERT INTO tableA_B VALUES
(1,'A'),(1,'B'),(2,'A'),(2,'B'),(3,'A'),(3,'B'),(3,'C'),(4,'A'),(4,'D'),(5,'A')
GO
DECLARE #x INT = 1;
SELECT A_id FROM tableA_B ab1
LEFT JOIN (
SELECT B_id FROM tableA_B
WHERE A_id=#x
) ab2 ON ab1.B_id=ab2.B_id
GROUP BY ab1.A_id
HAVING COUNT(*)=(SELECT COUNT(*) FROM tableA_B WHERE A_id=#x)
INTERSECT
SELECT A_id FROM tableA_B ab1
JOIN (
SELECT B_id FROM tableA_B
WHERE A_id=#x
) ab2 ON ab1.B_id=ab2.B_id
GROUP BY ab1.A_id
HAVING COUNT(*)=(SELECT COUNT(*) FROM tableA_B WHERE A_id=#x)
DROP TABLE tableA_B
GO
Try this,
declare #A_B table(col int,col2 varchar(30))
insert into #A_B VALUES
(1 ,'A') ,(1 ,'B') ,(2 ,'A') ,(2 ,'B') ,(3 ,'A') ,(3 ,'B')
,(3 ,'C') ,(4 ,'A') ,(4 ,'D') ,(5 ,'A'),(6 ,'AB')
declare #i int=1
declare #007 char(1)='-'
;with CTE as
(
select col,col2
,(select #007+col2 from #A_B y
where col=x.col for xml path(''))ConcateCol
from #A_B x
--where col=#i
)
select col,col2
from cte c
where
exists(select * from cte c1
where col=#i and c.ConcateCol=c1.ConcateCol)
you can further maniplate to get whatever desire output
;With tableA(ID)
AS
(
Select 1 uNION ALL
Select 2 uNION ALL
Select 3 uNION ALL
Select 4
)
, tableB(VAL)
As
(
SELECT 'A' UNION ALL
SELECT 'B' UNION ALL
SELECT 'C' UNION ALL
SELECT 'D'
)
SELECT ID,VAL FROM
(
SELECT *,ROW_NUMBER()OVER(PARTITION BY ID ORDER BY ID)AS Seq FROM tableA
CROSS JOIN tableB
)Dt
WHERE ID In (SELECT Id From tableA where id in(1,2) ) AND Dt.Seq<3
OutPut
table A_B
----------
1 A
1 B
2 A
2 B

SQL join combine with condition

Please how to achieve following result from this data?
(I am trying to combine join with conditions but still with some unwanted rows.)
Input is in one table only:
ID IN OUT
-----------------
1 6:00 null
2 7:11 null
2 null 16:30
3 null 19:00
Output (a view) should combine same IDs so the result will looks like:
ID IN OUT
------------------
1 6:00 null
2 7:11 16:30
3 null 19:00
You can try to achieve this with a CTE as follows:
CREATE TABLE #data
(
Id int not null,
inTime varchar(10) null,
outTime varchar(10) null
)
INSERT INTO #data VALUES
(1, '6:00', null),
(2 , '7:11', null),
(2 , null, '16:30'),
(3 , null, '19:00')
WITH combinedTable AS (
SELECT
Id,
MAX(inTime) AS A,
MAX(outTime) AS B
FROM #data
GROUP BY Id
HAVING MAX(2) = MIN(2)
AND MAX(3) = MIN(3)
)
SELECT *
FROM combinedTable
UNION ALL
SELECT *
FROM #data
WHERE ID NOT IN (SELECT ID FROM combinedTable)
The following can serve the purpose:
1. Have a table with ID and IN where IN is not equal to null:
(select ID, IN from table where In ne NULL()) as tableA
O/P would be:
ID IN
1 6:00
2 7:11
2.Have a table with ID and OUT where OUT is not equal to null:
(select ID, OUT from table where OUT ne NULL()) as tableB
O/P would be:
ID OUT
2 16:30
3 19:00
3.Outer Join the two tables:
select tableA.ID,tableA.IN, tableB.OUT
from table as tableA
outer full join
select ID,OUT from table as tableB
On tableA.ID=tableB.ID and tableA.IN ne Null() and tableB.OUT ne NULL()
Output will be the desired output:
ID IN OUT
1 6:00 null
2 7:11 16:30
3 null 19:00

MS SQL how to select n rows from table even if table has n - x rows

I have a table with a following data:
ID | Name
---------
1 | John
2 | Alice
And when I want to select 5 rows I want to get the next data:
ID | Name
---------
1 | John
2 | Alice
1 | John
2 | Alice
1 | John
Is there any ideas how to make this?
-- to create a sample table.
CREATE TABLE table1 (ID BIGINT, NAME VARCHAR(255))
INSERT INTO table1 VALUES (1, 'John'), (2, 'Alice')
-- set number of rows you need
DECLARE #RowsReqd INT = 5
-- find out the max ID we want to generate
DECLARE #Limit INT
SELECT #Limit = MAX(ID) FROM table1
-- generate the list
;WITH NumbersList
AS (
SELECT 1 AS Number, 1 AS ID
UNION ALL
SELECT Number + 1, Number % #Limit + 1 FROM NumbersList
WHERE Number < #RowsReqd
)
SELECT T.*
FROM NumbersList NL
INNER JOIN table1 T ON T.ID = NL.ID
ORDER BY NL.Number
OPTION (MAXRECURSION 10000) -- increase this value to generate more numbers
OUTPUT:
ID NAME
1 John
2 Alice
1 John
2 Alice
1 John
Works for n > 1 rows:
;WITH cte AS (
SELECT *
FROM (VALUES
(1, 'John'),
(2, 'Alice')
) AS t(ID, Name)
)
,res AS (
SELECT id,
name,
ROW_NUMBER() OVER (partition by id ORDER BY ID) as pid
FROM cte
UNION ALL
SELECT c.id,
c.name,
pid + 1
FROM res r
INNER JOIN cte c
ON pid = c.id
)
SELECT TOP 5
id,
name
FROM res
Output:
id name
----------- -----
1 John
2 Alice
1 John
2 Alice
1 John
(5 row(s) affected)

Oracle Order By different columns same select statement

I have an interesting problem here. But it is just for knowledge because I already solve it in a non elegant way.
I have a table that have costumers and they can be holders or dependants and this relation is described as a
family. Each family can have only a holder and 0-n dependants. A holder is identified by an H and a Dependant by a D.
What I need is a way to order the data by name of holder and theirs dependants. So the sample data below
idcostumer name idfamily relation
1 Natalie Portman 1 H
2 Mark Twain 3 D
3 Carl Sagan 2 D
4 Bob Burnquist 2 H
5 Sheldon Cooper 1 D
6 Anakin Skywalker 4 H
7 Luke Skywalker 4 D
8 Leia Skywalker 4 D
9 Burnquist Jr. 2 D
10 Micheal Jackson 3 H
11 Sharon Stone 1 H
12 Michelle Pfeiffer 3 D
Is it possible to get the above results in just one query? As you can see the order is name (just for the holders)
idcostumer name idfamily relation
6 Anakin Skywalker 4 H
8 Leia Skywalker 4 D
7 Luke Skywalker 4 D
4 Bob Burnquist 2 H
9 Burnquist Jr. 2 D
3 Carl Sagan 2 D
10 Micheal Jackson 3 H
2 Mark Twain 3 D
12 Michelle Pfeiffer 3 D
11 Sharon Stone 1 H
1 Natalie Portman 1 D
5 Sheldon Cooper 1 D
The test case data for this example.
create table costumer (
idcostumer integer primary key,
name varchar2(20),
idfamily integer,
relation varchar2(1)
);
This is the inserts statments for this table:
insert into costumer values ( 1 , 'Natalie Portman' , 1, 'D');
insert into costumer values ( 2 , 'Mark Twain' , 3, 'D');
insert into costumer values ( 3 , 'Carl Sagan' , 2, 'D');
insert into costumer values ( 4 , 'Bob Burnquist' , 2, 'H');
insert into costumer values ( 5 , 'Sheldon Cooper' , 1, 'D');
insert into costumer values ( 6 , 'Anakin Skywalker' , 4, 'H');
insert into costumer values ( 7 , 'Luke Skywalker' , 4, 'D');
insert into costumer values ( 8 , 'Leia Skywalker' , 4, 'D');
insert into costumer values ( 9 , 'Burnquist Jr.' , 2, 'D');
insert into costumer values ( 10, 'Micheal Jackson' , 3, 'H');
insert into costumer values ( 11, 'Sharon Stone' , 1, 'H');
insert into costumer values ( 12, 'Michelle Pfeiffer', 3, 'D');
I've tried some things, create a father sun relationship with connect by statement and familyid concatenated with the relation.
Used a row_count with a over clause ordering by relation desc and family id, but this way I lost the name order.
If I understand you correctly, you want to first order the families by the name of the holder, then by the names of the dependents. The following does that.
with family_order as (
select idfamily, rownum r from (
select idfamily from costumer where relation = 'H' order by name
)
)
select c.* from costumer c
inner join family_order fo on c.idfamily = fo.idfamily
order by fo.r, relation desc, name
Fiddle here
Try:
select * from table order by idfamily desc, relation desc, name asc
Link to Fiddle
For un-natural order you can use "union all":
select * from (select idcostumer, name, idfamily, relation from costumer
where idfamily > 3
order by idfamily desc, relation desc, name asc)
union all
select * from (
select idcostumer, name, idfamily, relation from costumer
where idfamily = 2
order by idfamily desc, relation desc, name asc)
union all
select * from (
select idcostumer, name, idfamily, relation from costumer
where idfamily != 2 and idfamily < 4
order by idfamily desc, relation desc, name asc)
Link to Fiddle

Oracle Hierarchical Query

Using Oracle 10g. I have two tables:
User Parent
-------------
1 (null)
2 1
3 1
4 3
Permission User_ID
-------------------
A 1
B 3
The values in the permissions table get inherited down to the children. I would like to write a single query that could return me something like this:
User Permission
------------------
1 A
2 A
3 A
3 A
3 B
4 A
4 B
Is it possible to formulate such a query using 10g connect .. by syntax to pull in rows from previous levels?
you can achieve the desired result with a connect by (and the function CONNECT_BY_ROOT that returns the column value of the root node):
SQL> WITH users AS (
2 SELECT 1 user_id, (null) PARENT FROM dual
3 UNION ALL SELECT 2, 1 FROM dual
4 UNION ALL SELECT 3, 1 FROM dual
5 UNION ALL SELECT 4, 3 FROM dual
6 ), permissions AS (
7 SELECT 'A' permission, 1 user_id FROM dual
8 UNION ALL SELECT 'B', 3 FROM dual
9 )
10 SELECT lpad('*', 2 * (LEVEL-1), '*')||u.user_id u,
11 u.user_id, connect_by_root(permission) permission
12 FROM users u
13 LEFT JOIN permissions p ON u.user_id = p.user_id
14 CONNECT BY u.PARENT = PRIOR u.user_id
15 START WITH p.permission IS NOT NULL
16 ORDER SIBLINGS BY user_id;
U USER_ID PERMISSION
--------- ------- ----------
3 3 B
**4 4 B
1 1 A
**2 2 A
**3 3 A
****4 4 A
You could take a look at http://www.adp-gmbh.ch/ora/sql/connect_by.html
Kind of black magic, but you can use table-cast-multiset to reference one table from another in WHERE clause:
create table t1(
usr number,
parent number
);
create table t2(
usr number,
perm char(1)
);
insert into t1 values (1,null);
insert into t1 values (2,1);
insert into t1 values (3,1);
insert into t1 values (4,3);
insert into t2 values (1,'A');
insert into t2 values (3,'B');
select t1.usr
, t2.perm
from t1
, table(cast(multiset(
select t.usr
from t1 t
connect by t.usr = prior t.parent
start with t.usr = t1.usr
) as sys.odcinumberlist)) x
, t2
where t2.usr = x.column_value
;
In the subquery x I construct a table of all parents for the given user from t1 (including itself), then join it with permissions for these parents.
Here is a example for just one user id. you can use proc to loop all.
CREATE TABLE a_lnk
(user_id VARCHAR2(5),
parent_id VARCHAR2(5));
CREATE TABLE b_perm
(perm VARCHAR2(5),
user_id VARCHAR2(5));
INSERT INTO a_lnk
SELECT 1, NULL
FROM DUAL;
INSERT INTO a_lnk
SELECT 2, 1
FROM DUAL;
INSERT INTO a_lnk
SELECT 3, 1
FROM DUAL;
INSERT INTO a_lnk
SELECT 4, 3
FROM DUAL;
INSERT INTO b_perm
SELECT 'A', 1
FROM DUAL;
INSERT INTO b_perm
SELECT 'B', 3
FROM DUAL;
-- example for just for user id = 1
--
SELECT c.user_id, c.perm
FROM b_perm c,
(SELECT parent_id, user_id
FROM a_lnk
START WITH parent_id = 1
CONNECT BY PRIOR user_id = parent_id
UNION
SELECT parent_id, user_id
FROM a_lnk
START WITH parent_id IS NULL
CONNECT BY PRIOR user_id = parent_id) d
WHERE c.user_id = d.user_id
UNION
SELECT d.user_id, c.perm
FROM b_perm c,
(SELECT parent_id, user_id
FROM a_lnk
START WITH parent_id = 1
CONNECT BY PRIOR user_id = parent_id
UNION
SELECT parent_id, user_id
FROM a_lnk
START WITH parent_id IS NULL
CONNECT BY PRIOR user_id = parent_id) d
WHERE c.user_id = d.parent_id;