简体   繁体   English

在分层架构中基于外键的滚动ID

[英]Rolling id based on foreign key in a hierarchical schema

As an example, consider this hierarchical schema. 作为示例,请考虑此分层架构。 在此处输入图片说明

Assume all id fields are auto incrementing primary keys and that foreign keys are named by [parent_table_name]_id convention. 假设所有id字段都是自动递增的主键,并且外键由[parent_table_name] _id约定命名。

The problem 问题

As soon as there are multiple companies in the database, then companies will share all primary key sequences between them. 一旦数据库中有多个公司,公司将在它们之间共享所有主键序列。

For example, if there are two company rows, the customer_group table could look like this 例如,如果有两个公司行, customer_group表可能如下所示

| id | company_id |
-------------------
| 1  |     1      |
| 2  |     1      |
| 3  |     2      |
| 4  |     2      |
| 5  |     1      |
-------------------

But it should look like this 但是应该看起来像这样

| id | company_id |
-------------------
| 1  |     1      |
| 2  |     1      |
| 1  |     2      |
| 2  |     2      |
| 3  |     1      |
-------------------

This behavior should also be exhibited for customer and any other table in the tree that directly or indirectly references company . 对于客户以及树中直接或间接引用公司的任何其他表,也应显示此行为。

Note that I will most likely make a second id column (named something like relative_id ) for this purpose, keeping the unique id column intact, as this is really mostly for display purposes and how users will reference these data entities. 请注意,我很可能为此目的创建第二个id列(命名为relative_id之类的东西),保持唯一的id列不变,因为这实际上主要是出于显示目的以及用户如何引用这些数据实体。


Now if this was just one level of hierarchy, it would be a relatively simple solution. 现在,如果这只是层次结构的一个级别,那将是一个相对简单的解决方案。 I could make a table (table_name, company_id, current_id) and a trigger procedure that fires before insert on any of the tables, incrementing the current id by 1 and setting the row's relative_id to that value. 我可以制作一个表(table_name,company_id,current_id)和一个触发过程,该过程在插入任何表之前触发,将当前id递增1并将行的relative_id设置为该值。 It's trivial when the company_id is right there in the insert query. company_id在插入查询中在那里时,这很简单。

But how about the tables that don't reference company directly? 但是没有直接引用公司的表又如何呢? Like the lowest level of the hierarchy in this example, workorder , which only references customer . 与本示例中层次结构的最低级别一样,工作仅引用customer
Is there a clean, reusable solution to climb the ladder all the way from 'customer_id' to ultimately retrieve the parenting company_id ? 是否有一个干净,可重用的解决方案,以从'customer_id'一直爬到最终检索父母的company_id

Going recursively up the hierarchy with SELECTs on each INSERT doesn't sound too appealing to me, performance wise. 在性能上,对每个INSERT的SELECT进行递归处理对我来说听起来不太吸引人。

I also do not like the idea of just adding a foreign key to company for each of these tables, the schema would get increasingly uglier with each additional table. 我也不喜欢只为这些表中的每个表添加外键到公司的想法,每个其他表的架构都会变得越来越难看。

But these are the two solutions I can see, but I may not be looking in the right places. 但是,这是我可以看到的两个解决方案,但我可能没有在正确的地方寻找。

The company shouldn't care what the primary key is if you're using generated keys. 如果您使用生成的密钥,公司将不在乎主密钥是什么。 They're supposed to be meaningless; 他们应该毫无意义。 compared for equality and nothing else. 比较平等,别无其他。 I grumbled about this earlier , so I'm really glad to see you write: 之前我对此事发牢骚 ,所以很高兴看到您写:

Note that I will most likely make a second id column (named something like relative_id) for this purpose, keeping the unique id column intact, as this is really mostly for display purposes and how users will reference these data entities. 请注意,我很可能为此目的创建第二个id列(命名为relative_id之类的东西),保持唯一的id列不变,因为这实际上主要是出于显示目的以及用户如何引用这些数据实体。

You're doing it right. 您做对了。

Most of the time it doesn't matter what the ID is, so you can just give them whatever comes out of a sequence and not care about holes/gaps. 在大多数情况下,ID无关紧要,因此您可以给它们提供序列中的任何内容,而不必关心孔/间隙。 If you're concerned about inter-company leakage (unlikely) you can obfuscate the IDs by using the sequence as an input to a pseudo-random generator. 如果您担心公司间泄漏(不太可能),可以通过使用序列作为伪随机生成器的输入来混淆ID。 See the function Daniel Verité wrote in response to my question about this a few years ago, pseudo_encrypt . 请参阅DanielVerité在几年前针对我对此问题的回答所编写的函数pseudo_encrypt

There are often specific purposes for which you need perfectly sequential gapless IDs, like invoice numbers. 通常,出于特定目的,您需要完美的顺序无间断ID,例如发票编号。 For those you need to use a counter table and - yes - look up the company ID. 对于那些您需要使用计数器表,并且-是-查找公司ID。 Such ID generation is slow and has terrible concurrency anyway, so an additional SELECT with a JOIN or two on indexed keys won't hurt much. 这样的ID生成速度很慢,并且具有严重的并发性,因此在索引键上使用JOIN或两个JOIN的附加SELECT不会有多大伤害。 Don't go recursively up the schema with SELECT s though, just use a series of JOIN s. 但是,不要使用SELECT递归地使用模式,只需使用一系列JOIN For example, for an insert into workorder your key generation trigger on workorder would be something like the (untested): 例如,对于插入到workorder上的密钥生成触发workorder会是这样的(未经测试):

   CREATE OR REPLACE FUNCTION workorder_id_tgfn() RETURNS trigger AS $$
   BEGIN
       IF tg_op = 'INSERT' THEN
           -- Get a new ID, locking the row so no other transaction can add a
           -- workorder until this one commits or rolls back.
           UPDATE workorder_ids
           SET next_workorder_id = next_workorder_id + 1 
           WHERE company_id = (SELECT company_id
                 FROM customer
                 INNER JOIN customer_group ON (customer.customer_group_id = customer_group.id) 
                 INNER JOIN company ON (customer_group.company_id = company.id)
               WHERE customer.id = NEW.customer_id)
           RETURNING next_workorder_id
           INTO NEW.id;
       END IF;
   END;
   $$ LANGUAGE 'plpgsql';

For the UPDATE ... RETURNING ... INTO syntax see Executing a Query with a Single-Row Result . 有关UPDATE ... RETURNING ... INTO语法的信息,请参见执行具有单行结果的查询

There can be gaps in normal sequences even if there's no multi-company problem. 即使没有多公司问题,正常顺序中也可能存在间隙。 Observe: 注意:

CREATE TABLE demo (id serial primary key, blah text);

BEGIN;
INSERT INTO demo(blah) values ('aa');
COMMIT;

BEGIN;
INSERT INTO demo(blah) values ('bb');
ROLLBACK;

BEGIN;
INSERT INTO demo(blah) values ('aa');
COMMIT;

SELECT * FROM demo;

Result: 结果:

regress=#     SELECT * FROM demo;
 id | blah 
----+------
  1 | aa
  3 | aa

"But it should look like this" “但是看起来应该像这样”

| id | company_id |
-------------------
| 1  |     1      |
| 2  |     1      |
| 1  |     2      |
| 2  |     2      |
| 3  |     1      |
-------------------

I think it should not and I think you want a many to many relationship. 我认为不应该,而且我想您需要多对多的关系。 The customer_group table: customer_group表:

| id | name |
-------------
| 1  |  n1  |
| 2  |  n2  |
| 3  |  n3  |
-------------

And then the customer_group_company table: 然后是customer_group_company表:

| group_id | company_id |
-------------------------
|    1     |     1      |
|    2     |     1      |
|    1     |     2      |
|    2     |     2      |
|    3     |     1      |
-------------------------

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM