Перефразируя, у вас есть ориентированный граф , узлами которого являются таблицы, а направленными ребрами являются внешние ключи. Если я правильно интерпретирую ваше значение, вы хотите найти наименьший набор ребер, который содержит пути от начального узла (основной таблицы) к каждому из узлов, которые вы даете.
Следует спросить, является ли ваш граф ацикличным (это означает, что не существует цикла внешних ключей, все ссылающиеся друг на друга; дочерний элемент дочернего элемента ... дочерний элемент таблицы никогда не является исходной таблицей). Если у вас нет циклов, могут быть сделаны некоторые упрощения, но ради аргумента предположим, что вы не знаете, существуют ли циклы.
С другой стороны, я буду предполагать, что любой произвольный действительный порядок соединения действительно то, что вы хотите (будьте осторожны - это довольно предположение для вашей модели данных!)
Один алгоритм, который вы можете использовать, - это поиск в ширину (bfs) , чтобы получить кратчайший путь от основной таблицы к каждому дочернему элементу. Это не гарантируется как оптимальное (оно жадно выбирает кратчайший путь для каждого узла назначения, когда объединение путей может быть проще). Тем не менее, это, безусловно, хорошая догадка для оптимального, и она справляется с возможностью циклов.
Как только вы выяснили все эти пути, у вас есть дерево, укорененное в главной таблице, и вы хотите добавить в таблицы оператор, сначала родители.
Ниже приведен некоторый поспешный код Python, который я написал для этого. Не стесняйтесь рефакторинг или комментарий для ясности.
from collections import deque
def join_sql(schema, main_table, *tables):
# for each table, which tables does it connect to?
children_map = {table:set() for table in schema}
for child, properties in schema.items():
parents = properties['fk']
for parent in parents:
children_map[parent].add(child)
# What are all the tables in consideration?
nodes = set(schema.keys())
# get a tree of parent tables via breadth-first search.
parent_tree = bfs(nodes, children_map, main_table)
# Create a topological ordering on the graph;
# order so that parent is joined before child.
join_order = []
used = {main_table}
def add_to_join_order(t):
if t in used or t is None:
return
parent = parent_tree.get(t, None)
add_to_join_order(parent)
join_order.append(t)
used.add(t)
for table in tables:
add_to_join_order(table)
lines = [f"FROM {main_table}"]
for fk_table in join_order:
parent_table = parent_tree[fk_table]
parent_col = schema[parent_table]['pk']
fk_col = schema[fk_table]['fk'][parent_table]
lines.append(f'INNER JOIN {fk_table} ON {fk_table}.{fk_col} = {parent_table}.{parent_col}')
return "\n".join(lines)
def bfs(nodes, children, start):
parent = {}
q = deque([start])
while q:
v = q.popleft()
for w in children[v]:
if w not in parent:
parent[w] = v
q.append(w)
return parent
if __name__ == "__main__":
schema = {'table1_name': {'pk': 'id', 'fk': {'table2_name': 'table2_id', 'table3_name': 'table3_id'}},
'table2_name': {'pk': 'id', 'fk': {}},
'table3_name': {'pk': 'id', 'fk': {'table1_name': 'table1_id'}},
'table4_name': {'pk': 'id', 'fk': {'table3_name': 'table3_id'}},
'table5_name': {'pk': 'id', 'fk': {'table3_name': 'table3_id'}},
}
print(join_sql(schema, 'table2_name', 'table2_name', 'table3_name', 'table4_name', 'table5_name'))
# FROM table2_name
# INNER JOIN table1_name ON table1_name.table2_id = table2_name.id
# INNER JOIN table3_name ON table3_name.table1_id = table1_name.id
# INNER JOIN table4_name ON table4_name.table3_id = table3_name.id
# INNER JOIN table5_name ON table5_name.table3_id = table3_name.id