我在编写解决以下问题的查询时遇到问题,我认为这需要某种递归:
我有一张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
。
答案 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
);