在MySQL中,广度优先搜索查询?

时间:2021-03-04 18:26:49

I want to build a mySQL query, which returns all nodes in a graph in x depth from a given node. The depth will be only 2-4.

我想构建一个mySQL查询,它从给定的节点返回x深度图中的所有节点。深度只有2-4。

The table structure is (neighborIDs can contain multiple values):

表结构为(嵌套可以包含多个值):

Id  Name  Desc  neighborIDs

So the task is basically a Breadth-first search in mySQL. I have found a way to do it in T-SQL, is this possible in mySQL? Is a single SQL query better, than writing a PHP function, that runs a simple SELECT on every neighbour of a node (so basically making tons of simple queries)?

这个任务基本上是在mySQL中首先进行搜索。我在T-SQL中找到了一种方法,这在mySQL中是可行的吗?一个SQL查询比编写一个PHP函数更好,它在一个节点的每个邻居上运行一个简单的选择(所以基本上是做大量简单的查询)?

Thanks for help

谢谢你的帮助


A try:

一试:

SELECT  root.ID,
        d1.ID,
        d2.ID
FROM    Locations root
        LEFT JOIN Locations d1 ON
          root.neighborIDs LIKE CONCAT('%',d1.id,'%')
        LEFT JOIN Locations d2 ON
          d1.neighborIDs LIKE CONCAT('%',d2.id,'%')
WHERE root.id = 1  # i guess this defines the starting node for the search..

An example table is:

表是一个例子:

id   name   desc                   neighborIDs  
1    id1    --     
2    id2    ---        
3    id3    neighborours are 1,2   1,2  
4    id4    neighbour is 3         3
10   id10   neigh is 4             4

If i run the query with the input id=1, it should return the row with id=3 if the BFS goes 1 level deep.

如果我使用输入id=1运行查询,如果BFS深入1层,它应该返回id=3的行。


Another try:

另一个尝试:

SELECT id,neighborIDs
FROM locations
WHERE id = 3
OR
neighborIDs LIKE '%3%'
OR (SELECT neighborIDs FROM locations WHERE id = 3) LIKE CONCAT('%',id,'%')

This query selects the neighbors of the node with id = 3.

该查询选择id = 3的节点的邻居。

2 个解决方案

#1


1  

step 0: Create a view that shows all neighbour pairs

CREATE VIEW neighbour AS
( SELECT loc1.id AS a
       , loc2.id AS b
  FROM locations loc1
     , locations loc2
  WHERE FIND_IN_SET(loc1.id, loc2.neighbours)>0
     OR FIND_IN_SET(loc2.id, loc1.neighbours)>0
) ;

step 1: Find neighbours of depth 1

SELECT b AS depth1
FROM neighbour
WHERE a = 1;               <-- for root with id=1

step 2: Find neighbours of depth 2

SELECT DISTINCT d2.b AS depth2
FROM neighbour d1
  JOIN neighbour d2
    ON d1.b = d2.a
      AND d2.b != 1
WHERE d1.a = 1                <-- for root with id=1
  AND d2.b NOT IN
     ( SELECT b AS depth1     <- depth1 subquery
       FROM neighbour
       WHERE a = 1            <-- for root with id=1
      )
;

step 3: Find neighbours of depth 3

SELECT d3.b as depth3
FROM neighbour d1
  JOIN neighbour d2
    ON d1.b = d2.a
    AND d2.b != 1
    AND d2.b NOT IN
       ( SELECT b as depth1
         FROM neighbour
         WHERE a = 1
       )
  JOIN neighbour d3
    ON d2.b = d3.a
    AND d3.b != 1
WHERE d1.a = 1
  AND d3.b NOT IN
     ( SELECT b as depth1
       FROM neighbour
       WHERE a = 1
      )
  AND d3.b NOT IN
     ( SELECT d2.b AS depth2
       FROM neighbour d1
         JOIN neighbour d2
           ON d1.b = d2.a
           AND d2.b != 1
       WHERE d1.a = 1
         AND d2.b NOT IN
            ( SELECT b AS depth1
              FROM neighbour
              WHERE a = 1
            )
     )
;

As you can see, the growth is exponential for the number of query lines, so I won't try the level 4.

如您所见,查询行数的增长是指数级的,所以我不打算尝试第4级。

#2


2  

As mentioned in my comment, you've made your life difficult. But something similar to the following will produce a list of neighbour IDs at each depth. Depending on your exact needs, the result set can be used a subquery and manipulated further to necessary (such as retrieving the names of the neighbours).

正如我在评论中提到的,你让你的生活变得困难。但是类似于以下内容的内容将生成每个深度的邻居id列表。根据您的具体需要,结果集可以使用子查询并进一步操作到必要的地方(例如检索邻居的名称)。

SELECT  root.ID,
        d1.ID,
        d2.ID
FROM    Locations root
        LEFT JOIN Locations d1 ON
          root.Neighbours LIKE '%'+CAST(d1.ID as varchar)+'%'  --Or equivalent mysql pattern matching function
        LEFT JOIN Locations d2 ON
          d1.Neighbours LIKE '%'+CAST(d2.ID as varchar)+'%'

EDIT: Changed INNER JOIN to LEFT JOIN

编辑:改变内连接到左连接。

#1


1  

step 0: Create a view that shows all neighbour pairs

CREATE VIEW neighbour AS
( SELECT loc1.id AS a
       , loc2.id AS b
  FROM locations loc1
     , locations loc2
  WHERE FIND_IN_SET(loc1.id, loc2.neighbours)>0
     OR FIND_IN_SET(loc2.id, loc1.neighbours)>0
) ;

step 1: Find neighbours of depth 1

SELECT b AS depth1
FROM neighbour
WHERE a = 1;               <-- for root with id=1

step 2: Find neighbours of depth 2

SELECT DISTINCT d2.b AS depth2
FROM neighbour d1
  JOIN neighbour d2
    ON d1.b = d2.a
      AND d2.b != 1
WHERE d1.a = 1                <-- for root with id=1
  AND d2.b NOT IN
     ( SELECT b AS depth1     <- depth1 subquery
       FROM neighbour
       WHERE a = 1            <-- for root with id=1
      )
;

step 3: Find neighbours of depth 3

SELECT d3.b as depth3
FROM neighbour d1
  JOIN neighbour d2
    ON d1.b = d2.a
    AND d2.b != 1
    AND d2.b NOT IN
       ( SELECT b as depth1
         FROM neighbour
         WHERE a = 1
       )
  JOIN neighbour d3
    ON d2.b = d3.a
    AND d3.b != 1
WHERE d1.a = 1
  AND d3.b NOT IN
     ( SELECT b as depth1
       FROM neighbour
       WHERE a = 1
      )
  AND d3.b NOT IN
     ( SELECT d2.b AS depth2
       FROM neighbour d1
         JOIN neighbour d2
           ON d1.b = d2.a
           AND d2.b != 1
       WHERE d1.a = 1
         AND d2.b NOT IN
            ( SELECT b AS depth1
              FROM neighbour
              WHERE a = 1
            )
     )
;

As you can see, the growth is exponential for the number of query lines, so I won't try the level 4.

如您所见,查询行数的增长是指数级的,所以我不打算尝试第4级。

#2


2  

As mentioned in my comment, you've made your life difficult. But something similar to the following will produce a list of neighbour IDs at each depth. Depending on your exact needs, the result set can be used a subquery and manipulated further to necessary (such as retrieving the names of the neighbours).

正如我在评论中提到的,你让你的生活变得困难。但是类似于以下内容的内容将生成每个深度的邻居id列表。根据您的具体需要,结果集可以使用子查询并进一步操作到必要的地方(例如检索邻居的名称)。

SELECT  root.ID,
        d1.ID,
        d2.ID
FROM    Locations root
        LEFT JOIN Locations d1 ON
          root.Neighbours LIKE '%'+CAST(d1.ID as varchar)+'%'  --Or equivalent mysql pattern matching function
        LEFT JOIN Locations d2 ON
          d1.Neighbours LIKE '%'+CAST(d2.ID as varchar)+'%'

EDIT: Changed INNER JOIN to LEFT JOIN

编辑:改变内连接到左连接。