Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/304.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python SQLAlchemy:树节点中的递归混合属性_Python_Tree_Sqlalchemy_Hybrid_Self Reference - Fatal编程技术网

Python SQLAlchemy:树节点中的递归混合属性

Python SQLAlchemy:树节点中的递归混合属性,python,tree,sqlalchemy,hybrid,self-reference,Python,Tree,Sqlalchemy,Hybrid,Self Reference,我有以下自引用(树)节点,希望按计算的属性过滤/排序uuid\u path和name\u path: class Node (db.Model): id = db.Column (db.Integer, db.Sequence ('node_id_seq'), primary_key=True) ########################################################################### root_id = d

我有以下自引用(树)节点,希望按计算的属性过滤/排序
uuid\u path
name\u path

class Node (db.Model):
    id = db.Column (db.Integer, db.Sequence ('node_id_seq'), primary_key=True)

    ###########################################################################

    root_id = db.Column (db.Integer, db.ForeignKey (id, ondelete='CASCADE'),
        index=True)

    nodes = db.relationship ('Node',
        cascade='all, delete-orphan', lazy='dynamic',
        primaryjoin='Node.id==Node.root_id',
        backref=db.backref ('root', remote_side=id))

    ###########################################################################

    _uuid = db.Column (db.String (36), nullable=False, index=True, unique=True,
        name = 'uuid')
    _name = db.Column (db.Unicode (256), nullable=False, index=True,
        name = 'name')

    ###########################################################################

    @hybrid_property
    def uuid (self):
        return self._uuid

    @hybrid_property
    def name (self):
        return self._name
    @name.setter
    def name (self, value):
        self._name = value

    ###########################################################################

    def __init__ (self, name, root, mime=None, uuid=None):

        self.root = root
        self._uuid = uuid if uuid else str (uuid_random ())
        self._name = unicode (name) if name is not None else None

    def __repr__ (self):

        return u'<Node@%x: %s>' % (self.id if self.id else 0, self._name)

    ###########################################################################

    @hybrid_property
    def uuid_path (self):
        node, path = self, []
        while node:

            path.insert (0, node.uuid)
            node = node.root

        return os.path.sep.join (path)

    @hybrid_property
    def name_path (self):
        node, path = self, []
        while node:

            path.insert (0, node.name)
            node = node.root

        return os.path.sep.join (path)

    ###########################################################################
我很确定我必须介绍如下内容:

class Node (db.Model):

    @hybrid_property
    def name_path (self):
        node, path = self, []
        while node:

            path.insert (0, node.name)
            node = node.root

        return os.path.sep.join (path)

    @name_path.expression
    def name_path (cls):
        ## Recursive SQL expression??
但我很难为
@name\u path.expression
(或
@uuid\u path.expression
)找到正确的定义;它应该以某种方式指示SQL传递从根节点到相关节点的路径


我不明白为什么需要这样做,因为我已经告诉SQLAlchemy迭代计算路径值。谢谢您的帮助。

好了,在使用PostgreSQL和SQLAlchemy进行了调整之后,我想我有了一个解决方案:(1)首先,我想用SQL将查询作为函数编写,(2)其次,提供正确的SQLAlchemy胶水:

SQL部分使用带递归的
CTE的

CREATE OR REPLACE FUNCTION name_path(node)
  RETURNS text AS
$BODY$

WITH RECURSIVE graph (id, root_id, id_path, name_path) AS (
    SELECT n.id, n.root_id, ARRAY[n.id], ARRAY[n.name]
    FROM node n
UNION
    SELECT n.id, n.root_id, id_path||ARRAY[n.id], name_path||ARRAY[n.name]
    FROM node n, graph g
    WHERE n.root_id = g.id)

SELECT array_to_string (g.name_path, '/','.*')
FROM graph g
WHERE (g.id_path[1] = $1.base_id OR g.root_id IS NULL)
AND (g.id = $1.id)

$BODY$
  LANGUAGE sql STABLE
  COST 100;
ALTER FUNCTION name_path(node)
  OWNER TO webed;
SQLAlchemy的一面是这样的:

class NamePathColumn (ColumnClause):
    pass

@compiles (NamePathColumn)
def compile_name_path_column (element, compiler, **kwargs):
    return 'node.name_path' ## something missing?

如果我在纯Python端,我会避免访问DB上的
节点.name\u path
,但如果我在纯Python端,可能会更快。我还不太确定的是,在<代码> CopyLyNAME.PATHOMLITH < /Cord>中,我没有考虑任何<代码>元素、编译器、** kWGss/COD>参数,这使我有点怀疑。


我只是在用SA&PG修补了大约1.5天之后才做了这个,所以很可能还有改进的空间。我非常感谢您对w.r.t.这种方法的任何评论。谢谢。

为了完整起见,我将zzzeek的反馈包括在他的报告中:

from sqlalchemy.sql.expression import ColumnElement ## !!
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.ext.compiler import compiles
from sqlalchemy import inspect

class UuidPathColumn (ColumnElement):
    def __init__(self, entity):
        insp = inspect (entity)
        self.entity = insp.selectable

@compiles (UuidPathColumn)
def compile_uuid_path_column (element, compiler, **kwargs):
    return "%s.uuid_path" % compiler.process (element.entity, ashint=True)

class NamePathColumn (ColumnElement):
    def __init__(self, entity):
        insp = inspect (entity)
        self.entity = insp.selectable

@compiles (NamePathColumn)
def compile_name_path_column (element, compiler, **kwargs):
    return "%s.name_path" % compiler.process (element.entity, ashint=True)

使用
ColumnElement
(而不是
ColumnClause
)是很重要的;相关代码可在和中找到。这是用SQLAlchemy 0.8和PostgreSQL 9.2实现的。

这里的基本任务是提出执行此查找的SQL。SQLAlchemy不知道如何做,在这种情况下,它可能需要使用,并且可能超出了混合语言的范围,因为您需要在此处更改SELECT语句的整个结构。@zzzeek-hmmm,这是否也意味着我不能指示SQLAlchemy按
uuid/name\u-path
进行筛选/排序?我曾考虑在Postgres端引入一个函数(使用递归CTE)来提供
node.uuid/name\u path
,但是否可以告诉SA按这种“伪”混合方式进行过滤/排序?如果您可以提供所需的表达式作为简单的WHERE标准,那么混合方式就可以做到,是的。@zzzeek谢谢您的回答,我很抱歉[对SA来说还是比较新的],但我认为还没有看到它..我设法为@expression编写了SQL:OK,为了使它更灵活(例如,如果您处理的是
别名(节点)
),您可以更改@compiles decorator来实际执行
编译器.process()
在基础
表上
别名
cls
引用的构造。这需要更多的理解,所以让我看看我是否能想出…@zzzeek这很好,我想知道如何解决
别名(节点)
part.;D非常感谢!这些内容将很快在我的NoTex项目中实现,请参阅-A rST编辑器。顺便说一句,有人读过这篇文章:
CREATE FUNCTION name\u path(节点)
SQL函数并不是一个真正的性能英雄,因为带有递归的
cte实际上会提供带有相应路径值的整个表(如果
WHERE
子句不只是选择我们真正感兴趣的节点)。因此重复调用
name\u path(节点)
实际上是一个性能瓶颈(如图所示)。尽管这种方法非常优雅,但为了更快的速度,必须放弃它(目前正在修补[materialized]SQL视图)。
class Node (db.Model):

    def get_path (self, field):

        @cache.version (key=[self.uuid, 'path', field])
        def cached_path (self, field):

            if self.root:
                return self.root.get_path (field) + [getattr (self, field)]
            else:
                return [getattr (self, field)]

        if field == 'uuid':
            return cached_path (self, field)
        else:
            return cached_path.uncached (self, field)

    @hybrid_property
    def name_path (self):
        return os.path.sep.join (self.get_path (field='name'))

    @name_path.expression
    def name_path (cls):
        return NamePathColumn (cls)
from sqlalchemy.sql.expression import ColumnElement ## !!
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.ext.compiler import compiles
from sqlalchemy import inspect

class UuidPathColumn (ColumnElement):
    def __init__(self, entity):
        insp = inspect (entity)
        self.entity = insp.selectable

@compiles (UuidPathColumn)
def compile_uuid_path_column (element, compiler, **kwargs):
    return "%s.uuid_path" % compiler.process (element.entity, ashint=True)

class NamePathColumn (ColumnElement):
    def __init__(self, entity):
        insp = inspect (entity)
        self.entity = insp.selectable

@compiles (NamePathColumn)
def compile_name_path_column (element, compiler, **kwargs):
    return "%s.name_path" % compiler.process (element.entity, ashint=True)