除了添加大量列之外,是否有用于存储许多布尔标志的通用模式?

roc*_*oco 19 postgresql database-design

我有一堆需要与用户帐户关联的布尔值。它们需要在真假之间切换,尽管不是特别频繁——它们被阅读的次数将比写入的次数更多。大多数情况下,它们只会从 False 设置为 True 一次,但情况并非总是如此。

现在我正在考虑将列附加到用户表中:

用户身份 other_user_cols did_a did_b did_c
1 ... 错误的 真的 错误的
2 ... 错误的 错误的 错误的
3 ... 真的 错误的 错误的

问题是,有很多这样的布尔列。

假设您需要添加 100 列。这真的仍然是最好的方法吗?

认为:

  • 它不会非常稀疏 - 大多数用户会在某个时候切换大多数布尔值
  • 布尔值没有以自然可以更好地组织的方式在逻辑上相关
  • 可能需要不时添加新的
  • 标志直接与每个用户相关联,除了单个布尔值之外不需要其他信息

我觉得这一定是一个非常常见的模式,但是这种将更多布尔列塞入我的表的方式似乎相当混乱。有没有更典型的方法来做它更干净一点?或者这实际上是最好的方法?

为此,我正在使用 Postgres。

以下是我考虑过的一些替代方案:

  • 序列化为字符串的 json 列表(= 列名)。这听起来比原来的更混乱,如果我想对数据进行任何严肃的 SQL 操作,也不理想
  • 使用数字和按位运算 - 不确定这有多受支持,以及需要对单个标志进行操作/查找的类似问题
  • 有一个单独的表,如:
用户身份 标志名称 价值
1 did_a 错误的
1 did_b 错误的
3 did_a 真的

作为替代方案,这对我来说似乎是最合理的,但不确定是否真的值得麻烦,或者是否有更好的方法。

小智 28

我看不到任何用例,其中有 100 个与用户直接相关的布尔变量,因此属于用户表。我的猜测是这些布尔值用于培训、认证或类似的。我将在我的其余答案中假设这一点。

单独的表“user_training”是执行此操作的方法。在此表中,您有“user_id”和“training_id”。您不需要布尔值,如果该行在数据库中,则培训已完成。

可能需要不时添加新的

使用这种架构不需要向数据库添加新列。只有一个新行。或者,如果数据库包含用于训练的表,则将训练添加到该表中,并且“training_id”指的是该表。

布尔值没有以自然可以更好地组织的方式在逻辑上相关

如果布尔值有多个上下文,则可以使用多个表。想想“user_training”、“user_certification”、“user_something”、“user_whatever”等。这需要更长的时间来完成,但不会花太多时间。你未来的自己会为此感谢你,因为在需要时修复不相关的数据会很麻烦。

如果上下文真的是分开的,你可以只用一张表来完成,比如“user_action”或“user_properties”。

请注意,您还需要决定一些事情。您是否应该有一个包含有关培训名称信息的“培训”表?如果是这样,您可以添加额外的培训,而无需更改应用程序或数据库结构。您想将某些“training_id”设为默认值吗?如果是这样,您如何知道每个培训的默认设置是什么?默认值应该在“训练”表中吗?


Lau*_*lbe 14

我推荐使用varbit,主要是因为它节省空间。当然,存储空间很便宜,但你必须在 RAM 中缓存,这并不便宜,而且操作较小的行性能要好得多。

它也很容易操作:

CREATE TABLE mytable (id bigint PRIMARY KEY, flags varbit);

INSERT INTO mytable VALUES (1, b'11010001000110100');
Run Code Online (Sandbox Code Playgroud)

获取第十个标志的值:

SELECT get_bit(flags, 9) FROM mytable WHERE id = 1;

 get_bit 
?????????
       0
(1 row)
Run Code Online (Sandbox Code Playgroud)

要更改第十个标志:

UPDATE mytable SET flags = set_bit(flags, 9, 1) WHERE id = 1;

SELECT * FROM mytable WHERE id = 1;

 id ?       flags       
????????????????????????
  1 ? 11010001010110100
(1 row)
Run Code Online (Sandbox Code Playgroud)

在最后添加一个新位:

UPDATE mytable SET flags = flags || b'1' WHERE id = 1;
Run Code Online (Sandbox Code Playgroud)

  • 我不会这样做。它看起来很优雅,但实际使用起来却是一种可怕的模式。您需要引用数据字典来确定哪个位是哪个位,几乎不可能一目了然地知道数据发生了什么,通常无法对其进行有意义的索引,添加列很困难,删除列*非常*困难,位掩码操作很容易使逻辑倒退(`flags & '1010' = flags` vs `flags & '1010' = '1010'`)等。 (27认同)
  • @BaconBits 当然它很丑,但并不比数组和 JSON 丑多少。但我可以接受对此的不同意见。 (2认同)

小智 3

有四种模式:column、composite_type、array、json

数组是我最喜欢的模式,因为,

  1. 没有柱数限制
  2. 大量的聚合函数
  3. 与 json 相比易于操作

这是列、复合和数组的示例;

drop table if exists  t_manycolumns ;
drop table if exists t_composite ;
drop table if exists t_arry ;
drop type if exists type_bits;
create table t_manycolumns
(id int,
is_1 boolean,
is_2 boolean,
is_3 boolean,
is_4 boolean,
is_5 boolean,
is_6 boolean,
is_7 boolean,
is_8 boolean
);
create type type_bits as (
is_1 boolean,
is_2 boolean,
is_3 boolean,
is_4 boolean,
is_5 boolean,
is_6 boolean,
is_7 boolean,
is_8 boolean);

create table t_composite( 
id int,
is_bits type_bits
);

create table t_arry(
id int,
is_bits boolean[]
);


-- column
insert into t_manycolumns (id, is_1, is_2, is_3, is_4, is_5, is_6, is_7, is_8)
select id, false, false, false, false, false, false, false, false from generate_series(1,100) id;
--composite type
insert into t_composite (id,is_bits)
select id, '(false, false, false, false, false, false, false, false)' from generate_series(1,100) id;

--array type
insert into t_arry (id,is_bits)
select id, array[false, false, false, false, false, false, false, false] from generate_series(1,100) id;


-- demo show search bit true in array

update  t_arry 
set is_bits[1]=true, is_bits[4]=true 
where id in (1,3);

select id, is_bits 
from t_arry
where is_bits[1]=true and is_bits[4] =true
order by id
Run Code Online (Sandbox Code Playgroud)

两件重要的事情是:

  1. 数组类型可以绕过列数限制。
  2. 性能有待进一步研究。数组性能应该很高,但这有点像您在编程而不是在关系数据库上运行 sql。

希望你喜欢。