【问题标题】:Contruct Tree from table从表构造树
【发布时间】:2016-03-14 22:11:36
【问题描述】:

我的数据库中有一个这样的表:

等等……

如您所见,有多个根父级(没有 parent_id 的父级),每个类别都有 n 个子级。

我想使用这个类将其转换为 Java 中的树结构:

private int id;
private String name;
private int parent;
private List<Category> children;

我通过这个查询获得数据,我认为它可以改进:

SELECT c.*, ca.name, NVL(ca.parent_id, -1) AS parent_id FROM 
( 
    SELECT id, name, parent_id FROM categories 
) ca, 
( 
    SELECT LISTAGG(id || ':' || name || ':' || DECODE(parent_id, NULL, 
    DECODE(id,    NULL, NULL, -1), parent_id), ';') 
    WITHIN GROUP (ORDER BY id) AS  children, parent_id AS id 
    FROM categories 
    GROUP BY parent_id HAVING parent_id IS NOT NULL 
) c 
WHERE c.id = ca.id

我得到每个类别(id、name 和 parent_id)和一个字符串及其子项。

然后我循环抛出每个 ResultSet

List<Category> categories = new ArrayList<Category>();

while (rs.next()) { 
    Category c = new Category();
    c = JdbcToModel.convertToCategory(rs); //
    if (c.getParent() == -1) { // parent_id is null in database
        categories.add(c);
     else {
        categories = JdbcToModel.addCategoryToTree(categories, c);

    }
}

方法convertToCategory

public static Category convertToCategory(ResultSet rs) {

Category toRet = new Category();
List<Category> children = new ArrayList<Category>();
try {       
    children = parseCategoriesFromReview(rs.getString("children"));
    toRet.setId(rs.getInt("id"));
    toRet.setName(rs.getString("name"));
    toRet.setParent(rs.getInt("parent_id"));
    toRet.setChildren(children);

} catch (Exception e) {
    e.printStackTrace();
}
return toRet;   

}

解析childs字符串时的方法parseCategoriesFromReview

public static List<Category> parseCategoriesFromReview(String categoriesString) {

        List<Category> toRet = new ArrayList<Category>();
        try {       
            if (!categoriesString.equals("::")) {

                String [] categs = categoriesString.split(";");
                for (String categ : categs) {
                    String [] category = categ.split(":");
                    Category c = new Category(Integer.parseInt(category[0]), category[1], Integer.parseInt(category[2]), new ArrayList<Category>());
                    toRet.add(c);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        return toRet;   
    }

以及递归方法addCategoryToTree

public static List<Category> addCategoryToTree(List<Category> categories, Category c) { 
    try {       
        for (Category ct : categories) {
            if (ct.getId() == c.getParent()) {
                ct.getChildren().add(c);
                break;
            } else {
                return addCategoryToTree(ct.getChildren(), c);
            }
        }

    } catch (Exception e) {
        e.printStackTrace();
    }
    return categories;

}

我认为这个方法最大的问题是......我从来没有写过一个内部有循环的递归方法,我不知道它是否正确。关键是我得到了一个树结构,但只有几个类别。最终的树没有这么多。

也许我让事情变得复杂,但我不知道如何以另一种方式做到这一点..

有人帮忙吗??

问候!

【问题讨论】:

    标签: java oracle recursion tree


    【解决方案1】:

    Oracle 设置

    CREATE TABLE categories ( id, name, parent_id ) AS
    SELECT  1, 'Restauracion', NULL FROM DUAL UNION ALL
    SELECT  2, 'Desayuno',        1 FROM DUAL UNION ALL
    SELECT  3, 'Calidad',         2 FROM DUAL UNION ALL
    SELECT  4, 'Organizacion',    2 FROM DUAL UNION ALL
    SELECT  5, 'Variedad',        2 FROM DUAL UNION ALL
    SELECT  6, 'Personal',     NULL FROM DUAL UNION ALL
    SELECT  7, 'Pisos',           6 FROM DUAL UNION ALL
    SELECT  8, 'Falta de Personal', 7 FROM DUAL UNION ALL
    SELECT  9, 'Trato',           7 FROM DUAL UNION ALL
    SELECT 10, 'Informacion',     7 FROM DUAL UNION ALL
    SELECT 11, 'Idiomas',         7 FROM DUAL UNION ALL
    SELECT 12, 'Otros',           7 FROM DUAL;
    

    Java

    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.HashMap;
    
    public class Category {
        private final String name;
        private final int id;
        private final Category parent;
        private final ArrayList<Category> children = new ArrayList<>();
    
        private Category(final String name, final int id, final Category parent) {
            this.name   = name;
            this.id     = id;
            this.parent = parent;
            if ( parent != null )
                parent.children.add(this);
        }
    
        @Override
        public String toString(){
            final StringBuffer buffer = new StringBuffer();
            buffer.append( '<' );
            buffer.append(name);
            buffer.append(':');
            buffer.append(id);
            buffer.append(':');
            buffer.append(parent == null ? "" : parent.name );
            buffer.append( '>' );
            return buffer.toString();
        }
    
        public String toHierarchyString(){
            return toHierarchyString(0);
        }
    
        private String toHierarchyString( int level ){
            final StringBuffer buffer = new StringBuffer();
            for ( int i = 0; i < level; i++ )
                buffer.append('\t');
            buffer.append( toString() );
            buffer.append( '\n' );
            for ( final Category child : children )
                buffer.append( child.toHierarchyString(level+1));
            return buffer.toString();
        }
        public static ArrayList<Category> loadCategoriesFromDatabase(){
            try{
    
                Class.forName("oracle.jdbc.OracleDriver");
    
                final Connection con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:XE","TEST","TEST");
                final PreparedStatement st = con.prepareStatement(
                        "SELECT id, name, parent_id " +
                        "FROM  categories " +
                        "START WITH parent_id IS NULL " +
                        "CONNECT BY PRIOR id = PARENT_ID " +
                        "ORDER SIBLINGS BY name"
                );
    
                final ResultSet cursor = st.executeQuery();
    
                final HashMap<Integer,Category> categoryMap = new HashMap<>();
                final ArrayList<Category> categories = new ArrayList<>();
    
                while ( cursor.next() )
                {
                    final String name       = cursor.getString("NAME");
                    final int id            = cursor.getInt("ID");
                    final Integer parent_id = cursor.getInt("PARENT_ID");
                    final Category parent   = categoryMap.get( parent_id );
                    final Category category = new Category( name, id, parent );
                    categoryMap.put(id, category);
                    if ( parent == null )
                        categories.add(category);
                }
                return categories;
            } catch(ClassNotFoundException | SQLException e) {
                System.out.println(e);
            }
            return null;
        }
    
        public static void main( final String[] args ){
            ArrayList<Category> categories = loadCategoriesFromDatabase();
            for ( final Category cat : categories )
                System.out.println( cat.toHierarchyString() );
        }
    }
    

    输出

    <Personal:6:>
        <Pisos:7:Personal>
            <Falta de Personal:8:Pisos>
            <Idiomas:11:Pisos>
            <Informacion:10:Pisos>
            <Otros:12:Pisos>
            <Trato:9:Pisos>
    
    <Restauracion:1:>
        <Desayuno:2:Restauracion>
            <Calidad:3:Desayuno>
            <Organizacion:4:Desayuno>
            <Variedad:5:Desayuno>
    

    【讨论】:

    • 它就像一个魅力!这就是我想要的。非常感谢 MT0!”
    【解决方案2】:

    我相信你的问题在于结果的排序。您的代码假定,所有子 ID 的 ID 都高于所有父 ID,这根本不需要如此。例如。如果你有一个类别(id=5,parent=10),那么它将递归当前树(包含类别 id 的 0 到 4)。它不会找到正确的父类别,在这种情况下(取决于您的类别类中的 children 字段是否初始化为 null 或空列表)将打印异常的堆栈跟踪,或者只是迭代在所有叶子类别中为空循环并且什么都不做。

    要从这样的数据结构构建树,您可能需要一个两阶段的方法。我更喜欢的一个(因为它是最简单的,虽然不是很高效)是:

    • 初始化HashMap&lt;Integer, List&lt;Category&gt;&gt;
    • 遍历您的数据库结果项并执行mapFromAbove.get(category.getId()).add(category)(您还需要初始化这些列表)
    • 完成后,执行mapFromAbove.get(0),并迭代结果,对于每个结果,从同一个哈希映射中获取子代并将其添加到它们。然后递归地执行此操作。

    它实际上很容易实现。

    【讨论】:

    • 好的。我理解前两点,但最后一点我迷路了。
    • Map> categories = new HashMap>(); categories.put(category.getId(), new ArrayList()); categories.get(category.getId()).add(category)) 但我在最后一个迷路了。你说mapFromAbove.get(0)是什么意思???应该是变量i吗?
    • mapFromAbove.get(0) 将返回父 ID 为 0 的所有类别(这些类别都是根类别)
    猜你喜欢
    • 2021-05-23
    • 1970-01-01
    • 1970-01-01
    • 2023-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多