I've been trying for a few hours without success, to recursively join a table to itself. It's probably simplest to see the problem in this SQL Fiddle, rather than me trying to explain the linking relationship:
http://sqlfiddle.com/#!4/367c3/14
In the above example, the actual data might nest up to 5 layers deep, perhaps more. I'd like to write a query that handles joins to any number of layers.
From doing some searches, it seems that it's possible to recursively join the data with CONNECT BY PRIOR, and other methods. I just haven't been able to get it to work :(. It'd be awesome if one of you guru's could show me how it's done.
Copying your tables from SQL Fiddle for others to see in case the external link becomes invalid in the future:
* DEVICE TABLE
DEVICEID DEVICENAME
-------------------
1 Device1
2 Device2
3 Device3
4 Device4
5 Device5
6 Device6
7 Device7
8 Device8
9 Device9
10 Device10
* CONNECTION TABLE
IDPARENT IDCHILD
----------------
1 2
3 4
4 5
6 7
7 8
4 8
4 8
5 9
I don't know what data exactly you want to query from the tables, but if you are just concerned with the self-"join" using a connect by prior, here you go:
select distinct d.deviceid, d.devicename, connect_by_root(deviceid) rootdeviceid
from device d
left join connection c on (d.deviceid = c.idchild)
connect by prior d.deviceid = c.idparent
start with c.idparent is null
order by d.deviceid
Note that there are 11 rows returned by this query given the table you provided -- this is because device 8 actually has two root nodes in the hierarchy you provided, 3 and 6.
Related
I have two tables. One is for Task and second is dependency table for the tasks.
I want a query to give me all the tasks (recursively) based on a particular id.
I have two tables. One is for Task
ID TASK
1 Abc
2 Def
3 Ghi
4 Jkl
5 Mno
6 Pqr
The second one is for getting dependent tasks
ID DEPENDENT_ON
2 1
3 1
4 2
4 6
5 2
6 5
Is it possible to write a sql query to get a list of all the tasks (recursive) which are dependent on a particular task.
Example.
I want to check all tasks dependent on ID=1.
Expected output (which is 2 and 3):
2.Def
3.Ghi
Furthermore query should also give output of these two dependent tasks and so on.
Final output should be:
2.Def -- level one
3.Ghi -- level one
4.Jkl -- Dependent on task 2
5.Mno -- Dependent on task 2
6.Pqr -- Dependent on task 5
Formatting is not important. Just output is required
I need to join two tables and then do a recursive search.
You must OUTER JOIN the second table (which you didn't name, so I have called it TASK_TREE) through DEPENDENT_ON to the parent ID. Outer join because task 1 is the top of the tree and depends on no task. Then use Oracle's hierarchical query syntax to walk the tree:
select t.id, t.task, tt.dependent_on, level
from tasks t
left outer join task_tree tt on tt.id = t.id
connect by prior t.id = tt.dependent_on
start with t.id = 1
/
I have included the level so you can see how the tree unfurls. The Oracle SQL documentation covers hierarchical queries in depth. Find out more. If you don't want to use Oracle's proprietary hierarchical syntax, from 11gR2 Oracle supported recursive WITH clause. Find out more.
Incidentally, your posted data contains a error. Task 4 depends on both 2 and 6. Hierarchies must have child nodes which depend on a single parent node. Otherwise you'll get all sorts of weird results.
I am using Gephi to create a network graph, here is a small subset of the data that I have:
ID Label
1 Sleep quality
2 Stress
3 Healthy Eating
4 Tremor
5 Balance
6 Drooling
7 Exercise
8 Mood
9 Speech
10 Parkinson's On-Off
So I want my graph to have these 10 nodes.
Then for the edges, I have:
Source Target User
1 5 5346
5 4 5346
4 7 5346
7 6 5346
6 9 5346
9 3 5346
3 2 5346
2 8 5346
8 10 5346
The "User" column is something I have added to explain the problem I am having. I am using a big database (in SQL) to obtain this data. On a mobile phone app, users select 10 of the different choices available (as listed in the nodes). In SQL I can query the data easily so that I can obtain the 10 choices of each of the users.
It is easy to create a graph with the edges with the information in the edges table but I would also like to connect each edge to all other edges, this is important for me. So for example, 1 connects to all those in "target". Then 5 connects to all those in "target" and so until all nodes are connected to each other for each user.
I can do this manually but the original data set has 2000+ users and this will take a long time. I know that there is a way of using cross join, possibly in Excel or in SQL... but I'm unsure how to do this..
Thanks!
You can drop this cross join into your SQL: (It'll list all Source's with all possible Target's.)
(SELECT e.Source as Source, n.ID as Target
FROM
(SELECT DISTINCT Source FROM tblEdges) as e
cross join (SELECT DISTINCT ID FROM tblNodes) as n
) as xCross
I saw some useful tips in the web, however I still have some questions.
This is the "main" part of the new site we are creating, it is based on SQL SERVER 2012, the "TAREAS" table is the main key table, which has a self join. I found a way to search for the "tree" of the table, TAREA=TASK, Spanish to English, so basically it is a task manager, on which one task could be part of a primary task, or be a secondary task which can have more "child" tasks. I did it using Common table expressions.
the thing here is on the ID_TipoTarea (TaskType) on TAREAS table, can be on one specific type of task, for example on the diagram there are 2 types availables (but there are and will be more), TipoTareaDesarrollo or TipoTareaEventoSalon, the ID_TipoTarea cant be on both tables, so if ID_TipoTarea=1 then I join on TIpoTareaDesarrollo, if ID_TipoTarea=2 then I join on TipoTareaEventoSalon and so on ID_TipoTarea=3 to another table, and there will be more types, can you help me out?.
how can it be achieved using this query (this is the query to get all the levels on the main table, but I need the conditional joins).
with tareasCTE (id_tarea,id_tareaorigen,id_tipoTarea,nivel)
as(
select *,0 as nivel from tareas t
where id_tarea=#ID_Tarea
union all
select t2.*,nivel+1 from tareasCTE t
inner join tareas t2
on t.id_tarea=t2.id_tareaOrigen
)
I get this output
ID_Tarea, ID_TareaORigen, Nivel, ID_TipoTarea
3 NULL 0 null (no join)
4 3 1 1 (join this one with TipoTareaDesarrollo)
5 3 1 1 (join this one with TipoTareaDesarrollo)
6 3 1 3 (join this one with AnotherTable)
7 4 2 2 (join this one with TipoTareaEventoSalon)
8 4 2 2 (join this one with TipoTareaEventoSalon)
9 4 2 4 (join this one with AnotherTable2)
10 9 3 1 (join this one with TipoTareaDesarrollo)
11 9 3 1 (join this one with TipoTareaDesarrollo)
12 9 3 null (no Join)
13 12 4 1 (join this one with TipoTareaDesarrollo)
14 12 4 2 (join this one with TipoTareaEventoSalon)
15 12 4 2 (join this one with TipoTareaEventoSalon)
You can combine tables TipoTareaDesarrollo, TipoTareaEventoSalon, AnotherTable, AnotherTable2 into a single table using the UNION clause and package this in a second CTE as such:
WITH TipoAreasCTE as
(
SELECT * FROM TipoTareaDesarrollo
UNION
SELECT * FROM TipoTareaEventoSalon
UNION
SELECT * FROM AnotherTable
UNION
SELECT * FROM AnotherTable2
)
You can then join tareasCTE to TipoAreasCTE.
Note that the different tables in the UNION must have the same number of columns with the same datatypes; if not you must use a SELECT list and perhaps CAST the datatypes to make them similar.
select p.intprojectid, p.vcprojectname, md.intmoduleid,
md.vcmodulename, md.intscreensfunc, md.vcname
from projects as p
left join (select m.intprojectid, m.intmoduleid, m.vcmodulename,
s.intscreensfunc, s.vcname
from modules as m
left join screens_func as s on m.intmoduleid = s.intmoduleid) md
on p.intprojectid = md.intprojectid
This query will return:
no |project-name|mod-id|mod-name | screen-id | screen-name
----------------------------------------------------------------
2 Project-1 4 mod-1 11 scr1
2 Project-1 4 mod-1 12 scr2
2 Project-1 4 mod-1 13 scr3
2 Project-1 4 mod-1 14 scr4
2 Project-1 8 Module-2 NULL NULL
Now I want to count no.of mod-name and no.of.screen-name in project-1. i.e. I want the query to return
project-name no.of.mod no.of.screen
------------------------------------------------
Project-1 2 4
It's definitely possible to return multiple counts.
In other words, your query could be modified as follows:
select p.vcprojectname, COUNT(DISTINCT md.intmoduleid) as no.of.mod, COUNT(md.intscreensfunc) as no.of.screen
from projects as p
left join (select m.intprojectid, m.intmoduleid, m.vcmodulename, s.intscreensfunc, s.vcname
from modules as m
left join screens_func as s
on m.intmoduleid=s.intmoduleid)md
on p.intprojectid=md.intprojectid
GROUP BY p.vcprojectname
Based on your example data, I inferred that there would be a one-many relationship between modules and screens and thus you would want a distinct count for modules but that the same requirement would not be needed for screens (since it appears that one screen would not appear multiple times in a given module) If that is not the case, you can also add distinct to the count of screens.
I have the following tables, the groups table which contains hierarchically ordered groups and group_member which stores which groups a user belongs to.
groups
---------
id
parent_id
name
group_member
---------
id
group_id
user_id
ID PARENT_ID NAME
---------------------------
1 NULL Cerebra
2 1 CATS
3 2 CATS 2.0
4 1 Cerepedia
5 4 Cerepedia 2.0
6 1 CMS
ID GROUP_ID USER_ID
---------------------------
1 1 3
2 1 4
3 1 5
4 2 7
5 2 6
6 4 6
7 5 12
8 4 9
9 1 10
I want to retrieve the visible groups for a given user. That it is to say groups a user belongs to and children of these groups. For example, with the above data:
USER VISIBLE_GROUPS
9 4, 5
3 1,2,4,5,6
12 5
I am getting these values using recursion and several database queries. But I would like to know if it is possible to do this with a single SQL query to improve my app performance. I am using MySQL.
Two things come to mind:
1 - You can repeatedly outer-join the table to itself to recursively walk up your tree, as in:
SELECT *
FROM
MY_GROUPS MG1
,MY_GROUPS MG2
,MY_GROUPS MG3
,MY_GROUPS MG4
,MY_GROUPS MG5
,MY_GROUP_MEMBERS MGM
WHERE MG1.PARENT_ID = MG2.UNIQID (+)
AND MG1.UNIQID = MGM.GROUP_ID (+)
AND MG2.PARENT_ID = MG3.UNIQID (+)
AND MG3.PARENT_ID = MG4.UNIQID (+)
AND MG4.PARENT_ID = MG5.UNIQID (+)
AND MGM.USER_ID = 9
That's gonna give you results like this:
UNIQID PARENT_ID NAME UNIQID_1 PARENT_ID_1 NAME_1 UNIQID_2 PARENT_ID_2 NAME_2 UNIQID_3 PARENT_ID_3 NAME_3 UNIQID_4 PARENT_ID_4 NAME_4 UNIQID_5 GROUP_ID USER_ID
4 2 Cerepedia 2 1 CATS 1 null Cerebra null null null null null null 8 4 9
The limit here is that you must add a new join for each "level" you want to walk up the tree. If your tree has less than, say, 20 levels, then you could probably get away with it by creating a view that showed 20 levels from every user.
2 - The only other approach that I know of is to create a recursive database function, and call that from code. You'll still have some lookup overhead that way (i.e., your # of queries will still be equal to the # of levels you are walking on the tree), but overall it should be faster since it's all taking place within the database.
I'm not sure about MySql, but in Oracle, such a function would be similar to this one (you'll have to change the table and field names; I'm just copying something I did in the past):
CREATE OR REPLACE FUNCTION GoUpLevel(WO_ID INTEGER, UPLEVEL INTEGER) RETURN INTEGER
IS
BEGIN
DECLARE
iResult INTEGER;
iParent INTEGER;
BEGIN
IF UPLEVEL <= 0 THEN
iResult := WO_ID;
ELSE
SELECT PARENT_ID
INTO iParent
FROM WOTREE
WHERE ID = WO_ID;
iResult := GoUpLevel(iParent,UPLEVEL-1); --recursive
END;
RETURN iResult;
EXCEPTION WHEN NO_DATA_FOUND THEN
RETURN NULL;
END;
END GoUpLevel;
/
Joe Cleko's books "SQL for Smarties" and "Trees and Hierarchies in SQL for Smarties" describe methods that avoid recursion entirely, by using nested sets. That complicates the updating, but makes other queries (that would normally need recursion) comparatively straightforward. There are some examples in this article written by Joe back in 1996.
I don't think that this can be accomplished without using recursion. You can accomplish it with with a single stored procedure using mySQL, but recursion is not allowed in stored procedures by default. This article has information about how to enable recursion. I'm not certain about how much impact this would have on performance verses the multiple query approach. mySQL may do some optimization of stored procedures, but otherwise I would expect the performance to be similar.
Didn't know if you had a Users table, so I get the list via the User_ID's stored in the Group_Member table...
SELECT GroupUsers.User_ID,
(
SELECT
STUFF((SELECT ',' +
Cast(Group_ID As Varchar(10))
FROM Group_Member Member (nolock)
WHERE Member.User_ID=GroupUsers.User_ID
FOR XML PATH('')),1,1,'')
) As Groups
FROM (SELECT User_ID FROM Group_Member GROUP BY User_ID) GroupUsers
That returns:
User_ID Groups
3 1
4 1
5 1
6 2,4
7 2
9 4
10 1
12 5
Which seems right according to the data in your table. But doesn't match up with your expected value list (e.g. User 9 is only in one group in your table data but you show it in the results as belonging to two)
EDIT: Dang. Just noticed that you're using MySQL. My solution was for SQL Server. Sorry.
-- Kevin Fairchild
There was already similar question raised.
Here is my answer (a bit edited):
I am not sure I understand correctly your question, but this could work My take on trees in SQL.
Linked post described method of storing tree in database -- PostgreSQL in that case -- but the method is clear enough, so it can be adopted easily for any database.
With this method you can easy update all the nodes depend on modified node K with about N simple SELECTs queries where N is distance of K from root node.
Good Luck!
I don't remember which SO question I found the link under, but this article on sitepoint.com (second page) shows another way of storing hierarchical trees in a table that makes it easy to find all child nodes, or the path to the top, things like that. Good explanation with example code.
PS. Newish to StackOverflow, is the above ok as an answer, or should it really have been a comment on the question since it's just a pointer to a different solution (not exactly answering the question itself)?
There's no way to do this in the SQL standard, but you can usually find vendor-specific extensions, e.g., CONNECT BY in Oracle.
UPDATE: As the comments point out, this was added in SQL 99.