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

时间:2019-05-10 03:07:51

标签: python sql algorithm data-structures

我正在使用包含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

任何有关如何高水平完成此操作的建议将不胜感激!

1 个答案:

答案 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