我正在使用包含60多个表的数据库,并且我正在尝试编写一个python函数,该函数接收要使用的表,并输出您应该用于连接这些表的join语句表。我将所有60个表放入一个有序字典中,如下所示,其中列出了表的名称,每个表中的主键和外键。
OrderedDict({
'table1_name':{'pk':'id', 'fk':{'table2_name':'table2_id', 'table3_name':'table3_id'}}
'table2_name':{'pk':'id'},
'table3_name':{'pk':'id', 'fk':{'table1_name':'table1_id'}
}) #Etc...
由于外键相互缠绕,因此我开始编写一个几乎没有成功的函数,这使得在表之间遍历并找到最短路径非常复杂。我尝试编写一个函数来执行此操作的尝试看起来像这样:
def join_creator(main_table, *tables):
#Test if we can join other tables directly to main
try:
main_table
main_pk = table_dict[main_table]["pk"]
except:
print('No primary key, this cannot be your main table')
return
result = f'FROM "public"."{main_table}" \n'
for table in other_tables:
try:
fk = table_dict[table]['fk'][main_table]
result += f'LEFT JOIN "public"."{table}" ON {main_table}.{main_pk}={table}.{fk}\n'
except KeyError:
pass
print(result)
总而言之,此函数的输入类似于
join_creator('table_1', 'table_2', 'table_3')
,输出将是这样的字符串:
FROM table_1
LEFT JOIN table_2 ON table_1.id = table_2.t1_id
LEFT JOIN table_3 ON table_1 = table_3.t1_id
任何有关如何高水平完成此操作的建议将不胜感激!
答案 0 :(得分:2)
换句话说,您有一个directed graph,其节点是表,有向边是外键。如果我正确地解释了您的意思,则希望找到包含从起始节点(主表)到您给定的每个节点的路径的最小边集。
要问的一件事是,您的图形是否为acyclic(这意味着没有外键互相引用的循环;表的孩子的孩子的...孩子永远不是原始表)。如果您没有周期,则可以进行一些简化,但是出于争论的原因,假设您不知道是否存在周期。
另一方面,我将假定您确实想要任何任意有效的连接顺序(请注意-这完全是您的数据模型上的假设!)
您可以使用的一种算法是breadth-first search (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