新手问题 [不懂就问] Rails 父子关系查询

ciscolive · 2021年06月28日 · 最后由 ciscolive 回复于 2021年07月15日 · 1079 次阅读

大佬们,最近想通过 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 的字段

想要实现的数据结构范例:

多谢大佬们,ancestry 有人用过吗

https://hoverbear.org/blog/postgresql-hierarchical-structures/ 可以看看这个,如果数据库是 PostgreSQL 的话。

ciscolive 回复

用过,推荐

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

经大佬指点,已经搞定

请教 each_with_object

dajianshi 回复

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 库,可以找一下。

dsh0416 回复

可以帮忙提供 SQL 代码给我测试下吗😀

需要 登录 后方可回复, 如果你还没有账号请 注册新账号