2017-04-22 67 views
2

我无法编写解决了以下问题,我相信一个查询需要某种递归的:SQL'子类'和子子类''中的所有行:递归?

我有一个表houses,他们每个人具有特定house_type,体育房子,平房等。house_types彼此继承,也在名为house_types的表中声明。

table: houses 
id | house_type 
1 | house 
2 | bungalow 
3 | villa 
etcetera... 

table: house_types 
house_type | parent 
house  | null 
villa  | house 
bungalow | villa 
etcetera... 

在这种逻辑中,平房也是别墅,别墅也是房子。所以当我想要得到所有的别墅时,房子2和3应该出现,当我想要得到所有房屋时,房子1,2和3应该出现,当我想要所有的平房时,只有房子3应该出现。

是一个递归查询的答案,我应该如何解决这个问题。我在node.js应用程序中使用knex/objection.js

+0

相关:http://stackoverflow.com/questions/39805736/get-join-table-as-array-of-results-with-postgresql-nodejs –

回答

2

这里是一个递归的CTE是获得每对在层次结构:

with recursive house_types as (
     select 'house' as housetype, null as parent union all 
     select 'villa', 'house' union all 
     select 'bungalow', 'villa' 
    ), 
    cte(housetype, alternate) as (
     select housetype, housetype as alternate 
     from house_types 
     union all 
     select ht.housetype, cte.alternate 
     from cte join 
      house_types ht 
      on cte.housetype = ht.parent 
    ) 
select * 
from cte; 

(该house_types CTE是刚刚成立的数据。)

然后,您可以加入这个到其他数据获得任何级别的层次结构。

+0

不错,这让我去。只是想知道,因为我在实际数据库中获得了更多的house_types和父母,我应该如何在as()部分中修改它?我必须输入所有可能性吗? –

+0

@MatsdeSwart。 。 。您的查询不需要CTE'house_types'。这只是为了演示它的工作原理。您可以删除该CTE。 –

1

以@ gordon-linoffs开头的答案非常棒。我只是在这里添加具体的如何使用knex/objection.js来做到这一点。

这听起来像很讨厌的数据库设计。我会对类型数据进行denormal化,以便在不递归公用表表达式的情况下更容易进行查询(knex当前不支持它们)。

反正这里是一些可运行的代码怎么办objection.js模型和能够让你正在尝试做的查询,键入JavaSript侧信息denormalisation:https://runkit.com/mikaelle/stackoverflow-43554373

由于计算器喜欢有代码也包含在答案我也会复制粘贴在这里。示例使用sqlite3作为数据库后端,但相同的代码也适用于postgres。

const _ = require('lodash'); 
require("sqlite3"); 

const knex = require("knex")({ 
    client: 'sqlite3', 
    connection: ':memory:' 
}); 

const { Model } = require('objection'); 

// init schema and test data 
await knex.schema.createTable('house_types', table => { 
    table.string('house_type'); 
    table.string('parent').references('house_types.house_type'); 
}); 

await knex.schema.createTable('houses', table => { 
    table.increments('id'); 
    table.string('house_type').references('house_types.house_type'); 
}); 

await knex('house_types').insert([ 
    { house_type: 'house', parent: null }, 
    { house_type: 'villa', parent: 'house' }, 
    { house_type: 'bungalow', parent: 'villa' } 
]); 

await knex('houses').insert([ 
    {id: 1, house_type: 'house' }, 
    {id: 2, house_type: 'villa' }, 
    {id: 3, house_type: 'bungalow' } 
]); 

// show initial data from DB 
await knex('houses') 
    .join('house_types', 'houses.house_type', 'house_types.house_type'); 

// create models 
class HouseType extends Model { 
    static get tableName() { return 'house_types' }; 

    // http://vincit.github.io/objection.js/#relations 
    static get relationMappings() { 
    return { 
     parent: { 
     relation: Model.HasOneRelation, 
     modelClass: HouseType, 
     join: { 
      from: 'house_types.parent', 
      to: 'house_types.house_type' 
     } 
     } 
    } 
    } 
} 

class House extends Model { 
    static get tableName() { return 'houses' }; 

    // http://vincit.github.io/objection.js/#relations 
    static relationMappings() { 
    return { 
     houseType: { 
     relation: Model.HasOneRelation, 
     modelClass: HouseType, 
     join: { 
      from: 'houses.house_type', 
      to: 'house_types.house_type' 
     } 
     } 
    } 
    } 
} 

// get all houses and all house types with recursive eager loading 
// http://vincit.github.io/objection.js/#eager-loading 
JSON.stringify(
    await House.query(knex).eager('houseType.parent.^'), null, 2 
); 

// however code above doesn't really allow you to filter 
// queries nicely and is pretty inefficient so as far as I know recursive 
// with query is only way how to do it nicely with pure SQL 

// since knex doesn't currently support them we can first denormalize housetype 
// hierarchy (and maybe cache this one if data is not changing much) 
const allHouseTypes = await HouseType.query(knex).eager('parent.^'); 

// initialize house types with empty arrays 
const denormalizedTypesByHouseType = _(allHouseTypes) 
    .keyBy('house_type') 
    .mapValues(() => []) 
    .value(); 

// create denormalized type array for every type 
allHouseTypes.forEach(houseType => { 
    // every type should be returned with exact type e.g. bungalow is bungalow 
    denormalizedTypesByHouseType[houseType.house_type].push(houseType.house_type); 
    let parent = houseType.parent; 
    while(parent) { 
    // bungalow is also villa so when searched for villa bungalows are returned 
    denormalizedTypesByHouseType[parent.house_type].push(houseType.house_type); 
    parent = parent.parent; 
    } 
}); 

// just to see that denormalization did work as expected 
console.log(denormalizedTypesByHouseType); 

// all villas 
JSON.stringify(
    await House.query(knex).whereIn('house_type', denormalizedTypesByHouseType['villa']), 
    null, 2 
);