SQL子类别中的所有行'和孩子的分类' :递归?

时间:2017-04-22 01:51:08

标签: node.js postgresql recursion knex.js objection.js

我在编写解决以下问题的查询时遇到问题,我认为这需要某种递归:

我有一张houses的表,每个表都有一个特定的house_type,p.e。 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应该出现。

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

2 个答案:

答案 0 :(得分:2)

这是一个递归CTE,它获取层次结构中的每一对:

 belongs_to :worker
 has_one :worker

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; CTE只是为了设置数据。)

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

答案 1 :(得分:2)

从@ gordon-linoffs开始回答非常棒。我只是在这里用knex / objection.js添加具体内容。

这听起来很糟糕的数据库设计。我会对类型数据进行非规范化处理,以便在没有递归公用表表达式的情况下更容易进行查询(knex目前不支持它们)。

无论如何,这里有一些可运行的代码如何做objection.js模型并在JavaSript端输入info denormalisation,以便能够进行你想要做的查询:https://runkit.com/mikaelle/stackoverflow-43554373

由于stackoverflow喜欢在答案中也包含代码,我也会复制粘贴它。示例使用sqlite3作为DB后端,但相同的代码也适用于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
);