大佬们,最近想通过 SQL 语句实现一个简单的树结构查询功能。苦于 SQL 不熟,希望大佬们指点。
class CreateDeviceTrees < ActiveRecord::Migration[6.1]
def change
create_table :device_trees do |t|
t.numeric :pid
t.numeric :sub_count
t.string :name, null: false
t.boolean :enabled, default: false
t.numeric :tree_sort, null: false, default: 999
t.timestamps
end
end
end
当前 SQL 的字段
想要实现的数据结构范例:
https://hoverbear.org/blog/postgresql-hierarchical-structures/ 可以看看这个,如果数据库是 PostgreSQL 的话。
用 awesome_nested_set
def get_data_tree
@zones = Zone.all
ap @zones
# 初始化数据结构
@result = []
# 检查是否为父节点、父节点下的子节点
@zones.each do |zone|
ap zone[:name]
ap "*" * 10
# 转换为HASH对象
zone = zone.as_json.with_indifferent_access
# 检查是否为父节点
zone[:label] = zone[:name] unless zone.respond_to? "label"
@result << zone if zone[:parent_id].blank?
# 检查是否为父节点下的子节点
@zones.each do |item|
item = item.as_json.with_indifferent_access
# 再次循环跳过当前节点
next if item[:id] == zone[:id]
# 命中父节点下的子节点
if item[:parent_id] == zone[:id]
ap item[:name]
ap "-" * 10
zone[:children] = [] unless zone.respond_to? "children"
zone[:children].push(item)
end
end
end
# 返回结算结果
@result
end
大佬们,为什么用上面的代码没办法构造出树结构数据啊,其他语言相同的逻辑可以直接生成数据
# 换perl 可以解析出来
❯ perl 1.pl
[
{
"children" => [
{
"children" => [
{
"dc_zone" => "OA",
"id" => 4,
"label" => "OA",
"pid" => 2
},
{
"dc_zone" => "PRD",
"id" => 5,
"label" => "PRD",
"pid" => 2
}
],
"dc_zone" => "STY",
"id" => 2,
"label" => "STY",
"pid" => 1
},
{
"children" => [
{
"dc_zone" => "OA",
"id" => 6,
"label" => "OA",
"pid" => 3
},
{
"dc_zone" => "PRD",
"id" => 7,
"label" => "PRD",
"pid" => 3
}
],
"dc_zone" => "PBS",
"id" => 3,
"label" => "PBS",
"pid" => 1
}
],
"dc_zone" => "SZ",
"id" => 1,
"label" => "SZ",
"pid" => 0
}
]
#!/usr/bin/env perl
# 引用模块
use 5.016;
use warnings;
# 执行脚本
&get_trees;
sub get_trees {
my $rev = [
{dc_zone => "SZ", pid => 0, id => 1},
{dc_zone => "STY", pid => 1, id => 2},
{dc_zone => "PBS", pid => 1, id => 3},
{dc_zone => "OA", pid => 2, id => 4},
{dc_zone => "PRD", pid => 2, id => 5},
{dc_zone => "OA", pid => 3, id => 6},
{dc_zone => "PRD", pid => 3, id => 7}
];
# 初始化输出变量
my $ret;
# 遍历数据条目
foreach my $model (@{$rev}) {
# 新增 label 属性
$model->{"label"} = $model->{"dc_zone"} unless (exists $model->{"label"});
# 检查是否为根节点
if (exists $model->{"pid"} && $model->{"pid"} == 0) {
# 压入当前根节点
push @{$ret}, $model;
}
# 遍历其他条目
foreach my $item (@{$rev}) {
# 跳过本身
next if $item->{"id"} eq $model->{"id"};
# 检查是否包含子节点
if (exists $item->{"pid"} && $item->{"pid"} == $model->{"id"}) {
# 压入当前子节点
push @{$model->{"children"}}, $item;
}
}
}
# 输出遍历结果
use Mojo::Util qw/dumper/;
say dumper $ret;
return $ret;
}
大哥们,帮我看下
# 生成父子关系的数据结构
def to_tree(all_nodes: nil, children_nodes: nil)
# 序列化数组哈希元素
all_nodes.to_a.map!(&:serializable_hash).map! { |node| node.symbolize_keys }
# 根据 pid 分组
@nodes_group_by_pid ||= all_nodes.try(group_by { |node| node[:pid] })
# 设置子节点,不存在则为根节点
children_nodes ||= @nodes_group_by_pid[nil]
# 获取 pid 节点下的子节点
children_nodes.each_with_object([]) do |node, array|
id = node[:id]
# 获取 pid 下的子节点
children = @nodes_group_by_pid[id] || []
children.empty? ? array << node : array << node.merge(children: to_tree(children_nodes: children))
end
end
经大佬指点,已经搞定
Enumerable#each_with_object Iterates the given block for each element with an arbitrary object given, and returns the initially given object.
If no block is given, returns an enumerator.
evens = (1..10).each_with_object([]) { |i, a| a << i*2 }
#=> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
这样做总感觉性能上会出事... Postgres 的话有 recursive cte query 可以一句 SQL 查出来,还会复用内存结构,比较快。Rails 上有对应的 ORM 库,可以找一下。