创建一个函数来自动执行 SQL 连接

Mat*_*att 5 python sql algorithm data-structures

我正在使用一个包含 60 多个表的数据库,我正在尝试编写一个 python 函数,该函数接受您希望使用的表,并输出您应该用来连接这些表的连接语句。我已将所有 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...
Run Code Online (Sandbox Code Playgroud)

我开始编写一个函数,但收效甚微,因为外键相互交织,使得从一个表遍历到另一个表并找到最短路径变得非常复杂。我尝试编写一个函数来执行此操作,如下所示:

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)
Run Code Online (Sandbox Code Playgroud)

总而言之,该函数的输入类似于 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
Run Code Online (Sandbox Code Playgroud)

任何有关如何在高水平上实现这一目标的建议将不胜感激!

Den*_*nis 2

换言之,您有一个有向图,其节点是表,有向边是外键。如果我正确地解释了您的含义,您想要找到包含从起始节点(主表)到您给出的每个节点的路径的最小边集。

要问的一件事是您的图表是否是非循环的(这意味着不存在相互引用的外键循环;表的子项的子项的...子项永远不是原始表)。如果没有循环,可以进行一些简化,但为了论证起见,假设您不知道是否存在循环。

另一方面,我将假设任何任意有效的连接顺序确实是您想要的(小心 - 这完全是对您的数据模型的假设!)

您可以使用的一种算法是广度优先搜索 (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
Run Code Online (Sandbox Code Playgroud)