SQL“子类别”和子子类别“中的所有行:recursion?

我无法编写一个解决以下问题的查询,我相信这需要某种recursion:

我有一个有houses的桌子,每个houses都有一个特定的房屋types,房子,平房等。房屋types相互inheritance,也在一个名为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应该出现。

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

这是一个recursion的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只是设置数据。)

然后,您可以将其join其他数据以获得任何级别的层次结构。

开始@戈登 – closures答案是真棒。 我只是在这里添加具体如何使用knex / objection.js做到这一点。

这听起来像很讨厌的数据库devise。 我会denormal的types数据,以便查询会更容易做不recursion公用表expression式(knex目前不支持)。

无论如何,这里是一些可运行的代码如何做的objection.js模型和JavaSript端的types信息denormalisation能够作出您正在尝试做的查询: https ://runkit.com/mikaelle/stackoverflow-43554373

由于stackoverflow喜欢有代码也包含在答案中我也会复制粘贴在这里。 示例使用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 eg 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 );