Find parent nodes in time trees by SQL

本文介绍如何使用SQL查询来操作树状结构的数据表。主要包括:从子节点查找所有祖先节点、从父节点查找所有子节点及插入新节点的方法。这些操作对于维护复杂的层级关系非常有用。
1.Find all ancestor node from child.

SELECT parent.name from time_trees as parent,time_trees as node where node.lft between parent.lft and parent.rgt and node.original_id='0036994d34de9862-03-00000000000324'


2.Find all children from parent.

select node.name from time_trees as node,time_trees as parent where node.lft between parent.lft and parent.rgt and parent.name='projects' order by node.lft


3.Insert node.

SELECT @myRight := rgt FROM time_trees
WHERE name = 'Projects';

UPDATE time_trees SET rgt = rgt + 2 WHERE rgt > @myRight;
UPDATE time_trees SET lft = lft + 2 WHERE lft > @myRight;

INSERT INTO time_trees(name, lft, rgt) VALUES('new record', @myRight + 1, @myRight + 2);
import base64 import json import os import subprocess import uuid from fastapi import HTTPException from py2neo import Node from kotei_web_server.neo4j.neo4jdb import Neo4jDBTest class DevDiagram: def display_tree(self): instance = Neo4jDBTest() query = """MATCH (root:REQModel) WHERE NOT (root)<-[:include]-() OPTIONAL MATCH path = (root)-[*]->(descendant) RETURN root.name AS root_name,ID(root) AS root_identity,labels(root) AS root_labels, root.update_time AS update_time,root.device_info AS device_info, CASE WHEN path IS NULL THEN [root] ELSE nodes(path) END AS family""" results = instance.my_graph.run(query) trees = self.results_to_tree(results.data()) return trees def find_node_by_ids_rel(self,nodeIds): instance = Neo4jDBTest() results = [] for nodeId in nodeIds: query = f""" MATCH (root) WHERE ID(root)={nodeId} OPTIONAL MATCH path = (root)-[]-(descendant) RETURN root.name AS root_name,ID(root) AS root_identity,labels(root) AS root_labels, root.update_time AS update_time,root.device_info AS device_info, CASE WHEN path IS NULL THEN [root] ELSE nodes(path) END AS family """ results += instance.my_graph.run(query).data() trees = self.results_to_tree(results) return trees def add_to_tree(self, tree, path): if not path: return parent_id = tree.get("id") node_name = path[0]["name"] node_id = path[0].identity labels = list(path[0].labels) update_time = path[0]["update_time"] device_info = path[0]["device_info"] action = path[0]["action"] # 查找当前节点的子节点中是否已经存在该节点 for child in tree["children"]: if child["id"] == node_id: # 继续在这个子节点中添加剩余路径 self.add_to_tree(child, path[1:]) return # 如果不存在,则创建新的子节点 new_node = {"id": node_id, "labels": labels, "name": node_name, "update_time": update_time, "device_info": device_info, "children": [], "action": action, "parent_id": parent_id} tree["children"].append(new_node) # 继续在新创建的子节点中添加剩余路径 self.add_to_tree(new_node, path[1:]) def results_to_tree(self, results): trees = {} for record in results: root_name = record["root_name"] family = record["family"] node_id = record["root_identity"] labels = record["root_labels"] update_time = record["update_time"] device_info = record["device_info"] # 查找或创建根节点 if node_id not in trees: trees[node_id] = {"id": node_id, "labels": labels, "name": root_name, "update_time": update_time, "device_info": device_info, "children": [], "parent_id": -1} root_tree = trees[node_id] # 添加路径到树中,去掉第一个节点(根节点)以避免重复 self.add_to_tree(root_tree, family[1:]) return list(trees.values()) def plantuml_to_base64(self, plantuml_text): try: # 获取当前文件的目录 current_dir = os.path.dirname(__file__) # 构建 plantuml.jar 的路径 plantuml_jar_path = os.path.join(current_dir, 'plantuml.jar') if not os.path.exists(plantuml_jar_path): raise HTTPException(status_code=500, detail=f"PlantUML JAR not found at {plantuml_jar_path}") # 调用 PlantUML JAR 包来渲染图像 process = subprocess.Popen( ['java', '-Dfile.encoding=UTF-8', '-jar', plantuml_jar_path, '-pipe'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) stdout_data, stderr_data = process.communicate(input=plantuml_text.encode('utf-8')) if process.returncode != 0: raise HTTPException(status_code=500, detail="Error generating PlantUML diagram: " + stderr_data.decode('utf-8')) # 将图像数据编码为 Base64 base64_image = base64.b64encode(stdout_data).decode('utf-8') return base64_image except Exception as e: raise HTTPException(status_code=500, detail="Error generating PlantUML diagram: " + str(e)) def display_list(self, root_id, label): instance = Neo4jDBTest() if root_id == -1: labels = [label, 'sdw_dev_design'] nodes = instance.find_all_node(node_type=labels) # nodes = [dict(node).get('labels',1) for node in nodes] else: nodes = self.display_child_list(root_id, label, instance) return nodes def display_child_list(self, root_id, child_label, instance): query = """ MATCH (n) WHERE ID(n) = $root_id MATCH (n)-[*]->(child:%s) RETURN DISTINCT ID(child) AS child_id,labels(child) AS child_labels, child """ % child_label # 注意这里使用字符串拼接来动态构建查询 results = instance.my_graph.run(query, root_id=root_id) # 处理查询结果 result_list = [] for record in results: child = dict(record["child"]) child.setdefault("id", record["child_id"]) result_list.append(child) return result_list def generate_id(self): # 生成基于时间的UUID uuid_time = uuid.uuid1() # 提取时间戳部分(以100纳秒为单位),转换为毫秒级 timestamp = uuid_time.time // 10 ** 4 return timestamp # 根据ID获取节点的名称 def get_node_name_by_id(self, node_list, node_id): for node in node_list: if node['id'] == node_id: return node['name'] return None def build_forest(self, data): # 创建一个字典来存储所有节点 nodes = {} # 创建一个列表来存储所有根节点 forest = [] # 先遍历数据列表,创建所有节点字典条目 for item in data: node_id = item['id'] condition = item.get('condition') labels = item.get('labels', 'folder') if node_id not in nodes: nodes[node_id] = [] nodes[node_id].append( {'id': node_id, 'name': item['name'], 'children': [], 'parent_id': item.get('parent_id', -1), 'type': item.get('type', 0), 'labels': labels, 'condition': condition}) # 再次遍历数据列表,将每个节点添加到其父节点的子节点列表中 for node_list in nodes.values(): for node in node_list: parent_id = node['parent_id'] if parent_id == -1: # 如果没有父节点或父节点ID为-1,则是根节点 forest.append(node) else: # 将节点添加到其父节点的子节点列表中 if parent_id in nodes: for parent_node in nodes[parent_id]: parent_node['children'].append(node) return forest def display_sop_tree(self): instance = Neo4jDBTest() query = """MATCH (root:Start) WHERE NOT (root)<-[:include]-() OPTIONAL MATCH path = (root)-[*]->(descendant) RETURN root.name AS root_name,ID(root) AS root_identity,labels(root) AS root_labels, root.update_time AS update_time,root.device_info AS device_info, CASE WHEN path IS NULL THEN [root] ELSE nodes(path) END AS family""" results = instance.my_graph.run(query) all_nodes = results.data() trees = self.results_to_tree(all_nodes) return trees 解析一下
09-09
<think>我们正在讨论SQLServer中替代MySQLFIND_IN_SET函数的问题。FIND_IN_SET函数在MySQL中用于查找一个字符串在一个逗号分隔的字符串列表中出现的位置。在SQLServer中没有直接对应的函数,但我们可以通过其他方式实现类似功能。常见的实现方法包括:1.使用字符串分割函数将逗号分隔的字符串拆分成表,然后使用查询判断值是否存在。2.使用LIKE运算符进行模式匹配(注意:这种方法有局限性,比如可能会误匹配部分字符串)。3.使用XML方法进行拆分。这里我们主要介绍第一种方法,因为它是比较通用且准确的方法。具体步骤:-在SQLServer2016及以上版本,可以使用STRING_SPLIT函数来拆分逗号分隔的字符串。-对于更早的版本,我们可以自己创建一个分割函数。以下是一个自定义分割函数的示例,它将逗号分隔的字符串拆分成一个表:```sqlCREATEFUNCTIONdbo.SplitString(@strNVARCHAR(MAX),@delimiterCHAR(1))RETURNS@resultsTABLE(ValueNVARCHAR(MAX))ASBEGINDECLARE@xXMLSET@x=CAST('<s>'+REPLACE(@str,@delimiter,'</s><s>')+'</s>'ASXML)INSERTINTO@resultsSELECTT.c.value('.','NVARCHAR(MAX)')ASValueFROM@x.nodes('/s')T(c)RETURNEND```然后,我们可以这样使用这个函数来模拟FIND_IN_SET:```sqlDECLARE@searchStrNVARCHAR(100)='apple'DECLARE@listNVARCHAR(MAX)='banana,apple,orange'--方法1:检查是否存在SELECTCASEWHENEXISTS(SELECT1FROMdbo.SplitString(@list,',')WHEREValue=@searchStr)THEN1ELSE0ENDASFound--方法2:返回位置(这里我们返回匹配的行号,由于集合是无序的,所以这个行号并不完全等同于FIND_IN_SET的位置,但我们可以通过排序来模拟)SELECTROW_NUMBER()OVER(ORDERBY(SELECTNULL))ASPosition,ValueFROMdbo.SplitString(@list,',')```注意:在MySQL中,FIND_IN_SET返回的是从1开始的位置,但是我们的方法返回的行号可能会因为拆分方式的不同而不一定与原始顺序一致。如果我们希望保持原始顺序,可以在拆分时保留顺序。我们可以修改上面的函数,使其返回位置信息。下面是增强版本的分割函数,它返回每个值及其位置:```sqlCREATEFUNCTIONdbo.SplitStringWithPosition(@strNVARCHAR(MAX),@delimiterCHAR(1))RETURNS@resultsTABLE(PositionINTIDENTITY(1,1),ValueNVARCHAR(MAX))ASBEGINDECLARE@xXMLSET@x=CAST('<s>'+REPLACE(@str,@delimiter,'</s><s>')+'</s>'ASXML)INSERTINTO@results(Value)SELECTT.c.value('.','NVARCHAR(MAX)')ASValueFROM@x.nodes('/s')T(c)RETURNEND```这样,我们就可以通过位置来模拟FIND_IN_SET:```sqlDECLARE@searchStrNVARCHAR(100)='apple'DECLARE@listNVARCHAR(MAX)='banana,apple,orange'SELECTPositionFROMdbo.SplitStringWithPosition(@list,',')WHEREValue=@searchStr```如果在列表中没有找到,则不会返回任何行,类似于MySQL中返回0的情况。我们可以使用ISNULL((SELECTPosition...),0)来返回0。另外,在SQLServer2016及以上版本,我们可以使用STRING_SPLIT,但注意这个函数不保证返回顺序,也不返回位置。所以如果我们只需要判断是否存在,可以这样:```sqlDECLARE@searchStrNVARCHAR(100)='apple'DECLARE@listNVARCHAR(MAX)='banana,apple,orange'SELECT1WHEREEXISTS(SELECT1FROMSTRING_SPLIT(@list,',')WHEREvalue=@searchStr)```但是,如果我们想要位置,则仍然需要使用自定义函数。总结:在SQLServer中替代MySQLFIND_IN_SET函数,我们可以:1.使用自定义分割函数(适用于所有版本),并且可以返回位置。2.使用STRING_SPLIT函数(仅2016+),但只能判断存在性,不能保证顺序。因此,根据需求选择合适的方法。相关问题:1.在SQLServer中如何将分隔字符串拆分为多行?2.SQLServer中有没有内置的字符串分割函数?3.如何优化SQLServer中的字符串分割操作以提高性能?</think> 在 SQL Server 中,没有直接等同于 MySQL `FIND_IN_SET()` 的内置函数,但可以通过以下几种方法实现相同功能: ### 方法 1:使用 XML 方法(兼容所有版本) ```sql CREATE FUNCTION dbo.FindInSet(@search VARCHAR(100), @list VARCHAR(MAX)) RETURNS INT AS BEGIN RETURN ( SELECT Position = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM ( SELECT Split.a.value('.', 'VARCHAR(100)') AS Value FROM ( SELECT CAST('<x>' + REPLACE(@list, ',', '</x><x>') + '</x>' AS XML) AS Data ) AS a CROSS APPLY Data.nodes('/x') AS Split(a) ) AS t WHERE Value = @search ) END ``` ### 方法 2:使用 STRING_SPLIT(SQL Server 2016+) ```sql CREATE FUNCTION dbo.FindInSet(@search VARCHAR(100), @list VARCHAR(MAX)) RETURNS INT AS BEGIN RETURN ( SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM STRING_SPLIT(@list, ',') WHERE value = @search ) END ``` ### 方法 3:使用 JSON(SQL Server 2016+) ```sql CREATE FUNCTION dbo.FindInSet(@search VARCHAR(100), @list VARCHAR(MAX)) RETURNS INT AS BEGIN RETURN ( SELECT [key] + 1 FROM OPENJSON('["' + REPLACE(@list, ',', '","') + '"]') WHERE value = @search ) END ``` ### 使用方法示例 ```sql -- 返回 'banana' 的位置(结果为 2) SELECT dbo.FindInSet('banana', 'apple,banana,orange') -- 未找到时返回 0 SELECT dbo.FindInSet('grape', 'apple,banana,orange') ``` ### 性能说明 1. **XML/JSON 方法** - 适用所有版本 - 处理长字符串时注意性能 2. **STRING_SPLIT** - 只支持 2016+ - **重要**:不保证返回顺序(需额外排序逻辑) - 官方文档明确说明不保证顺序[^1] 3. **索引优化** - 频繁查询建议存储规范化数据 - 避免在 WHERE 子句中直接使用函数计算 ### 替代方案建议 对高频查询场景,建议: 1. 建立关联表替代逗号分隔值 2. 使用临时表存储分割后的值 3. 通过计算列预先存储位置信息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值