SQL query for searching in the tree - sql

I have a simple tree that has 4 level of deep data. Here is the table DDL
CREATE TABLE HIER_DEMO(
ID NUMBER,
LABEL VARCHAR2 (100),
PARENT_ID NUMBER)
Hierarchy starts WITH ID = PARENT_ID. Number of levels are fixed. It is always 4. We have leafs to all branches at 4th level. So we can also add 3 more columns that represent LABEL of ancestors if necessary.
I need to build a query that
Searches for particular phrase in LABEL on any level of hierarchy. For example LABEL LIKE '%MAGIC_WORD%'.
Returns all the nodes till leaf level under the hierarchy node that satisfies condition 1.
In addition we need to return all the ancestors of the hierarchy node that satisfies condition 1.
Here is an example
INSERT INTO HIER_DEMO VALUES (1, 'Mike', 1);
INSERT INTO HIER_DEMO VALUES (2, 'Arthur', 2);
INSERT INTO HIER_DEMO VALUES (3, 'Alex', 1);
INSERT INTO HIER_DEMO VALUES (4, 'Suzanne', 1);
INSERT INTO HIER_DEMO VALUES (5, 'Brian', 3);
INSERT INTO HIER_DEMO VALUES (6, 'Rick', 3);
INSERT INTO HIER_DEMO VALUES (7, 'Patrick', 4);
INSERT INTO HIER_DEMO VALUES (8, 'Simone', 4);
INSERT INTO HIER_DEMO VALUES (9, 'Tim', 5);
INSERT INTO HIER_DEMO VALUES (10, 'Andrew', 5);
INSERT INTO HIER_DEMO VALUES (11, 'Sandy', 6);
INSERT INTO HIER_DEMO VALUES (12, 'Brian', 6);
INSERT INTO HIER_DEMO VALUES (13, 'Chris', 7);
INSERT INTO HIER_DEMO VALUES (14, 'Laure', 7);
INSERT INTO HIER_DEMO VALUES (15, 'Maureen', 8);
INSERT INTO HIER_DEMO VALUES (16, 'Andy', 8);
INSERT INTO HIER_DEMO VALUES (17, 'Al', 2);
INSERT INTO HIER_DEMO VALUES (18, 'John', 17);
INSERT INTO HIER_DEMO VALUES (19, 'Frank', 18);
INSERT INTO HIER_DEMO VALUES (20, 'Tim', 19);
I am looking for the query that searches the tree for word 'Brian' in the LABEL column
The query should return these data
ID LABEL PARENT_ID
1 Mike 1
3 Alex 1
5 Brian 3
6 Rick 3
9 Tim 5
10 Andrew 5
12 Brian 6
Could somebody help with the Oracle query? We are using 11.2 version of Oracle database.

SQL> select * from HIER_DEMO
2 start with label like '%Brian%'
3 connect by prior id = parent_id
4 union
5 select * from HIER_DEMO
6 start with label like '%Brian%'
7 connect by prior parent_id = id and PRIOR parent_id != PRIOR id
8 /
ID LABEL PARENT_ID
---- -------------------- ---------
1 Mike 1
3 Alex 1
5 Brian 3
6 Rick 3
9 Tim 5
10 Andrew 5
12 Brian 6

We can use recursive CTE to accomplish this
WITH CTE1(ID, LABEL,PARENT_ID) AS
(
SELECT * FROM Hier_Demo
WHERE LABEL LIKE '%Brian%'
UNION ALL
SELECT h.ID, h.LABEL, h.PARENT_ID FROM Hier_Demo h
INNER JOIN CTE1 c
ON h.ID = c.PARENT_ID
WHERE h.ID <> h.PARENT_ID
),
CTE2(ID, LABEL,PARENT_ID) AS
(
SELECT * FROM Hier_Demo
WHERE LABEL LIKE '%Brian%'
UNION ALL
SELECT h.ID, h.LABEL, h.PARENT_ID FROM Hier_Demo h
INNER JOIN CTE2 c
ON h.PARENT_ID = c.ID
)
SELECT * FROM CTE2
UNION
SELECT * FROM CTE1
UNION
SELECT * FROM HIER_DEMO WHERE ID = 1
In the above code CTE1 gets records up in the hierarchy and CTE2 gets records down in the hierarchy of Brian, after that we just UNION the records returned by these CTEs
see the code working at SQLFiddle: http://sqlfiddle.com/#!4/0c99d/39

You can try this :
SELECT * FROM HIER_DEMO WHERE PARENT_ID=
(SELECT id FROM HIER_DEMO WHERE LABEL='Brian');

Related

Select parent and child records while search child record

I have two tables 'Family' and 'FamilyLink'. If I search the 'Family', I need the parent entries also.
Family:
Id Name Type
1 A 1
2 A1 2
3 AAA 3
4 B 1
5 B1 2
6 BBB 3
0 0 0
FamilyLink:
Id parentId
1 0
2 1
3 2
4 0
5 4
6 5
If I search 'AAA' I want results like below
Id Parent Id Name
1:0 0 A
2:1 1:0 A1
3:2 2:1 AAA
If I search 'BBB' I want results like below
Id Parent Id Name
4:0 0 B
5:4 4:0 B1
6:5 5:4 BBB
If nothing in search the results shown like below:
Id Parent Id Name
1:0 0 A
2:1 1:0 A1
3:2 2:1 AAA
4:0 0 B
5:4 4:0 B1
6:5 5:4 BBB
I tried like below query
SELECT DISTINCT F1.ID, FL1.PARENTID, F1.NAME
FROM FAMILY F1 INNER JOIN FAMILYLINK FL1 ON F1.ID= F1.ID
INNER JOIN FAMILY F2 ON F2.ID = FL1.PARENTID
INNER JOIN FAMILYLINK FL2 ON FL2.ID = F2.ID WHERE F1.NAME
LIKE '%AAA%'
Sample table create:
Create table Family (Id int, Name varchar(50), Type int)
insert into family values (0, 'O', 0)
insert into family values (1, 'A', 1)
insert into family values (2, 'A1', 2)
insert into family values (3, 'AAA', 3)
insert into family values (4, 'B', 1)
insert into family values (5, 'B1', 2)
insert into family values (6, 'BBB', 3)
Create table FamilyLink (Id int, parentId int)
insert into FamilyLink values (1, 0)
insert into FamilyLink values (2, 1)
insert into FamilyLink values (3, 2)
insert into FamilyLink values (4, 0)
insert into FamilyLink values (5, 4)
insert into FamilyLink values (6, 5)
Not getting the expected result.
please help.
thanks
I feel like the tables are a bit wierd?
If you want BBB's parent to be B1, shouldn't the parentid of id 6 in familylinks be 5 instead of 2? Because when I look at your sample tables it looks like BBB's parent is A1 (2)
If you switched the parent mapping you could do this:
Declare #Family table (Id int, Name varchar(50), Type int)
Declare #FamilyLink table (Id int, parentId int)
declare #searchvalue varchar(50) = 'AAA'
insert into #Family values (0, 'O', 0)
insert into #Family values (1, 'A', 1)
insert into #Family values (2, 'A1', 2)
insert into #Family values (3, 'AAA', 3)
insert into #Family values (4, 'B', 1)
insert into #Family values (5, 'B1', 2)
insert into #Family values (6, 'BBB', 3)
insert into #FamilyLink values (1, 0)
insert into #FamilyLink values (2, 1)
insert into #FamilyLink values (3, 2)
insert into #FamilyLink values (4, 0)
insert into #FamilyLink values (5, 4)
insert into #FamilyLink values (6, 5)
;WITH CTE AS(
SELECT name, f.id, fl.parentId
FROM #Family f
JOIN #FamilyLink fl ON fl.id = f.Id
WHERE Name = #searchvalue
UNION ALL
SELECT f.Name, f.id, fl.parentId
FROM CTE
JOIN #Family f ON f.id = cte.parentId
JOIN #FamilyLink fl ON fl.id = f.Id
WHERE f.Type > 0
)
SELECT *
FROM CTE
Order by id

SQL - find exact group of records in large table

I have following data:
ID --- GRP_ID --- REC_VAL
1 --- 1 --- A
2 --- 2 --- A
3 --- 2 --- B
4 --- 3 --- A
5 --- 3 --- B
6 --- 3 --- C
7 --- 4 --- A
8 --- 4 --- B
9 --- 4 --- C
10 --- 5 --- A
11 --- 5 --- B
12 --- 5 --- E
Is there a way how to find id of record groups that have same values ? (in this case only grp_id 3 and 4 have same values)
Second question:
Is there effecient way how to find exact grp_id when i had a set of values ? My solution is not very quick because table with groups has over 6mil. records:
-- Large table - up to 6m records
create table tmp_grp (id number, grp_id number, rec_val varchar2(10));
--
insert into tmp_grp(id, grp_id, rec_val) values (1, 1, 'A');
insert into tmp_grp(id, grp_id, rec_val) values (2, 2, 'A');
insert into tmp_grp(id, grp_id, rec_val) values (3, 2, 'B');
insert into tmp_grp(id, grp_id, rec_val) values (4, 3, 'A');
insert into tmp_grp(id, grp_id, rec_val) values (5, 3, 'B');
insert into tmp_grp(id, grp_id, rec_val) values (6, 3, 'C');
insert into tmp_grp(id, grp_id, rec_val) values (7, 4, 'A');
insert into tmp_grp(id, grp_id, rec_val) values (8, 4, 'B');
insert into tmp_grp(id, grp_id, rec_val) values (9, 4, 'C');
insert into tmp_grp(id, grp_id, rec_val) values (10, 5, 'A');
insert into tmp_grp(id, grp_id, rec_val) values (11, 5, 'B');
insert into tmp_grp(id, grp_id, rec_val) values (12, 5, 'E');
commit;
--
-- CTE representing record group for asking
WITH datrec AS
(SELECT 'A' rec FROM dual UNION ALL
SELECT 'B' rec FROM dual)
--
SELECT x.grp_id
FROM (
-- Count of joined records
SELECT COUNT(1) cnt, t.grp_id
FROM tmp_grp t
JOIN datrec d
ON d.rec = t.rec_val
GROUP BY t.grp_id
--
) x
WHERE
-- Count of all data records
x.cnt = (SELECT COUNT(1) FROM datrec)
-- Count of all group records
AND x.cnt = (SELECT COUNT(1) FROM tmp_grp g WHERE x.grp_id = g.grp_id);
--
This question is similar to Find group of records that match multiple values , but this topic only cover exact set of values (number of values and values in column rec of datrec will be provided by another query) and query return groups which contains this set. I need to return only exact match.
UPDATE
- added data in table for better clarification
Also related to How to compare groups of tuples in sql
Here is a way that avoids joining the base table to itself. It will be more efficient especially if there are several (many?) possible values of rec_val for each grp_id. It can be made faster still if the distinct grp_id already exist somewhere in your data; I create them on the fly.
with gid ( grp_id ) as (
select distinct grp_id from tmp_grp
),
prep ( grp_id_1, grp_id_2, rec_val ) as (
select t.grp_id, g.grp_id, t.rec_val
from tmp_grp t join gid g on t.grp_id < g.grp_id
union all
select g.grp_id, t.grp_id, t.rec_val
from gid g join tmp_grp t on g.grp_id < t.grp_id
),
counts ( grp_id_1, grp_id_2, cnt ) as (
select grp_id_1, grp_id_2, count(*)
from prep
group by grp_id_1, grp_id_2, rec_val
)
select grp_id_1, grp_id_2
from counts
group by grp_id_1, grp_id_2
having min(cnt) = 2
;

SQL Query by using with

I have the following query...
------ create table
create table test222
(
sid bigint,
scode nvarchar(50),
parentid bigint,
sname nvarchar(50)
)
insert into test222 values (1, '11', 0, 'iam a boy')
insert into test222 values (2, '111', 1, 'boy')
insert into test222 values (3, '1111', 1, 'bo')
insert into test222 values (4, '11111', 3, 'girl')
insert into test222 values (5, '111111', 0, 'boyy')
insert into test222 values (6, '1111111', 5, 'gril')
insert into test222 values (7, '22', 0, 'body')
insert into test222 values (8, '222', 7, 'girll')
following is my code,,,
;WITH SInfo AS
(
SELECT
t.sId,
t.scode,
t.ParentId,
t.sName,
CONVERT(nvarchar(800), t.scode) AS Hierarchy,
t.ParentId as HParentId
FROM test222 as t
WHERE
t.sname like '%bo%'
UNION ALL
SELECT
si.sId,
si.scode,
si.ParentId,
si.sName,
CONVERT(nvarchar(800), TH.scode + '\' + si.Hierarchy),
th.parentid
FROM SInfo as si
INNER JOIN test222 TH
ON TH.sId = si.HParentId
)
Select t.sId, t.scode, t.ParentId, t.sName, t.Hierarchy
from SInfo as t
where
HParentId = 0 and
not exists (select 1 from SInfo as s
where
s.sid <> t.sid and
s.Hierarchy like t.Hierarchy + '%')
the op generated is shown below
5 111111 0 boyy 111111
7 22 0 body 22
3 1111 1 bo 11\1111
the third row is not correct
It should be
3 111111 1 bo 11\111\1111.
How can i do that???
All you need to do is change the parent id of the record - sid=3 to its chronological parent instead of its grand parent :-) . Check below.
Change
insert into #test222 values (3, '1111', 1, 'bo')
to
insert into #test222 values (3, '1111', 2, 'bo')
The reason you are not seeing the middle portion (record - sid:2) is because record - sid=2 and record - sid=3 essentially share the same "FROM" criteria. sname= '%bo%' (or rather LIKE '%bo%') and parentid=1. The record - sid=3 is the last record in the set with this shared "FROM" criteria and hence is the record being returned.
Order of SQL query execution (FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY)
Here is a link to recursive CTE queries
Hope this helps.

Filtering a bottom up recursive CTE with Sql Server 2005

I'm trying to query a hierarchy of data in a single database table from the bottom up (I don't want to include parents that don't have a particular type of child due to authorities). The schema and sample data are as follows:
create table Users(
id int,
name varchar(100));
insert into Users values (1, 'Jill');
create table nodes(
id int,
name varchar(100),
parent int,
nodetype int);
insert into nodes values (1, 'A', 0, 1);
insert into nodes values (2, 'B', 0, 1);
insert into nodes values (3, 'C', 1, 1);
insert into nodes values (4, 'D', 3, 2);
insert into nodes values (5, 'E', 1, 1);
insert into nodes values (6, 'F', 5, 2);
insert into nodes values (7, 'G', 5, 2);
create table nodeAccess(
userid int,
nodeid int,
access int);
insert into nodeAccess values (1, 1, 1);
insert into nodeAccess values (1, 2, 1);
insert into nodeAccess values (1, 3, 1);
insert into nodeAccess values (1, 4, 1);
insert into nodeAccess values (1, 5, 1);
insert into nodeAccess values (1, 6, 0);
insert into nodeAccess values (1, 7, 1);
with Tree(id, name, nodetype, parent)
as
(
select n.id, n.name, n.nodetype, n.parent
from nodes as n
inner join nodeAccess as na on na.nodeid = n.id
where na.access =1 and na.userid=1 and n.nodetype=2
union all
select n.id, n.name, n.nodetype, n.parent
from nodes as n
inner join Tree as t on t.parent = n.id
inner join nodeAccess as na on na.nodeid = n.id
where na.access =1 and na.userid=1 and n.nodetype=1
)
select * from Tree
Yields:
id name nodetype parent
4 D 2 3
7 G 2 5
5 E 1 1
1 A 1 0
3 C 1 1
1 A 1 0
How can I not include the duplicates in the result set? The queries against the real tables have many more nodes at the lowest levels and hence many more duplicates of the parent nodes. The solution needs to work with at least SQL Server 2005.
Thanks in advance!
The simplest (not necessarily the most efficient) solution:
...
)
SELECT DISTINCT id,name,nodetype,parent FROM Tree;
This changes the order from your sample output because the DISTINCT operator implements a sort. If there is some intentional ordering there I cannot detect it but you can add an ORDER BY if you know the order you want.

Help with a SQL Query

My tables:
suggestions:
suggestion_id|title|description|user_id|status|created_time
suggestion_comments:
scomment_id|text|user_id|suggestion_id
suggestion_votes:
user_id|suggestion_id|value
Where value is the number of points assigned to a vote.
I'd like to be able to SELECT:
suggestion_id, title, the number of comments and the SUM of values for that suggestion.
sorted by SUM of values. LIMIT 30
Any ideas?
You may want to try using sub queries, as follows:
SELECT s.suggestion_id,
(
SELECT COUNT(*)
FROM suggestion_comments sc
WHERE sc.suggestion_id = s.suggestion_id
) num_of_comments,
(
SELECT SUM(sv.value)
FROM suggestion_votes sv
WHERE sv.suggestion_id = s.suggestion_id
) sum_of_values
FROM suggestions s;
Test case:
CREATE TABLE suggestions (suggestion_id int);
CREATE TABLE suggestion_comments (scomment_id int, suggestion_id int);
CREATE TABLE suggestion_votes (user_id int, suggestion_id int, value int);
INSERT INTO suggestions VALUES (1);
INSERT INTO suggestions VALUES (2);
INSERT INTO suggestions VALUES (3);
INSERT INTO suggestion_comments VALUES (1, 1);
INSERT INTO suggestion_comments VALUES (2, 1);
INSERT INTO suggestion_comments VALUES (3, 2);
INSERT INTO suggestion_comments VALUES (4, 2);
INSERT INTO suggestion_comments VALUES (5, 2);
INSERT INTO suggestion_comments VALUES (6, 3);
INSERT INTO suggestion_votes VALUES (1, 1, 3);
INSERT INTO suggestion_votes VALUES (2, 1, 5);
INSERT INTO suggestion_votes VALUES (3, 1, 1);
INSERT INTO suggestion_votes VALUES (1, 2, 4);
INSERT INTO suggestion_votes VALUES (2, 2, 2);
INSERT INTO suggestion_votes VALUES (1, 3, 5);
Result:
+---------------+-----------------+---------------+
| suggestion_id | num_of_comments | sum_of_values |
+---------------+-----------------+---------------+
| 1 | 2 | 9 |
| 2 | 3 | 6 |
| 3 | 1 | 5 |
+---------------+-----------------+---------------+
3 rows in set (0.00 sec)
UPDATE: #Naktibalda's solution is an alternative solution that avoids sub queries.
I was typing the same query as potatopeelings.
But there is an issue:
Resultset after joins contains M*N rows (M-number of comments, N-number of votes, not less than 1) for each suggestion.
To avoid that you have to count distinct comment ids and divide a sum of votes by number of comments.
SELECT
s.*,
COUNT(DISTINCT c.scommentid) AS comment_count,
SUM(v.value)/GREATEST(COUNT(DISTINCT c.scommentid), 1) AS total_votes
FROM suggestions AS s
LEFT JOIN suggestion_comments AS c ON s.suggestion_id = c.suggestion_id
LEFT JOIN suggestion_votes AS v ON s.suggestion_id = v.suggestion_id
GROUP BY s.suggestion_id
ORDER BY total_votes DESC
LIMIT 30