列与替代连续剧

时间:2012-09-10 20:34:37

标签: sql postgresql database-design auto-increment

我想创建一个user_widgets表,它由user_id和user_widget_id主键控,其中user_widget_id的工作方式类似于序列号,但每个用户的启动时间为1。

对此有共同或实际的解决方案吗?我正在使用PostgreSQL,但也可以理解一个不可知的解决方案。

示例表:user_widgets

 |  user_id  |  user_widget_id  |  user_widget_name    |
 +-----------+------------------+----------------------+
 |  1        |  1               | Andy's first widget  |
 +-----------+------------------+----------------------+
 |  1        |  2               | Andy's second widget |
 +-----------+------------------+----------------------+
 |  1        |  3               | Andy's third widget  |
 +-----------+------------------+----------------------+
 |  2        |  1               | Jake's first widget  |
 +-----------+------------------+----------------------+
 |  2        |  2               | Jake's second widget |
 +-----------+------------------+----------------------+
 |  2        |  3               | Jake's third widget  |
 +-----------+------------------+----------------------+
 |  3        |  1               | Fred's first widget  |
 +-----------+------------------+----------------------+

修改

我只是想包含一些设计理由。

1。信息披露较少,而不仅仅是“通过默默无闻的安全”

在用户不应该彼此了解的系统中,他们也不应该知道彼此的widget_id。如果这是一个库存表,奇怪的商业秘密,发票或更敏感的东西,他们就可以开始为这些小部件设置自己不受影响的ID。除了明显的例行安全检查之外,还会添加一个隐式安全层,其中表具有,以便通过窗口小部件ID 来过滤用户ID。

2。数据导入

应允许用户从其他系统导入数据,而不必删除所有旧ID(如果他们有整数ID)。

第3。清洁度

与我的第一点并不完全不同,但我认为创建内容少于其他用户的用户可能会因其小部件ID的重大跳跃而感到困惑或烦恼。这当然比表面上更肤浅,但仍然有价值。

可能的解决方案

其中一个答案表明应用程序层处理此问题。我可以在该用户的表上存储一个递增的next_id列。或者甚至只计算每个用户的行数,而不允许删除记录(改为使用删除/停用的标志)。这可以通过触发器功能,甚至存储过程而不是在应用程序层中完成吗?

2 个答案:

答案 0 :(得分:4)

如果你有一张桌子:

CREATE TABLE user_widgets (
  user_id int
 ,user_widget_name text  --should probably be a foreign key to a look-up table
  PRIMARY KEY (user_id, user_widget_name)
)

您可以动态分配user_widget_id并查询:

WITH x AS (
   SELECT *, row_number() OVER (PARTITION BY user_id
                                ORDER BY user_widget_name) AS user_widget_id 
   FROM   user_widgets
   )
SELECT *
FROM   x
WHERE  user_widget_id = 2;
在此方案中,每个用户按字母顺序应用

user_widget_id并且没有间隙,显然,添加,更改或删除条目可能会导致更改。

有关window functions in the manual的更多信息。


更多(但不完全)稳定:

CREATE TABLE user_widgets (
  user_id int
 ,user_widget_id serial
 ,user_widget_name
  PRIMARY KEY (user_id, user_widget_id)
)

WITH x AS (
   SELECT *, row_number() OVER (PARTITION BY user_id
                                ORDER BY user_widget_id) AS user_widget_nr 
   FROM   user_widgets
   )
SELECT *
FROM   x
WHERE  user_widget_nr = 2;

解决问题更新

可以实施一种机制来计算每个用户的现有小部件。但是你很难为并发写入做出防弹。你必须锁定整个表或使用SERIALIZABLE transaction mode - 这两个都是真正的性能下降,需要额外的代码。

但如果您保证没有删除任何行,您可以使用我的第二种方法 - 在表格中user_widget_id的一个序列,它会为您提供“raw”< / em> ID。序列是并发加载的成熟解决方案,保留user_widget_id中的相对顺序并且。您可以使用视图提供对该表的访问权限,该动态会将“原始”user_widget_id替换为与上述查询相对应的user_widget_nr

您可以(另外)通过在非工作时间将其替换为user_widget_id或由您选择的事件触发来“实现”无间隙user_widget_nr

为了提高性能,我会让user_widget_id的序列以非常高的数字开头。似乎每个用户只能有一些小部件。

SELECT setval(user_widgets_user_widget_id_seq', 100000);

如果没有数字足够安全,请添加标志。使用条件WHERE user_widget_id > 100000快速识别“原始”ID。如果你的表很大,你可能想要使用条件添加partial index(这将是很小的)。用于CASE语句中的上述视图。并在此声明中“实现”ID:

UPDATE user_widgets w
SET    user_widget_id = u.user_widget_nr
FROM (
   SELECT user_id, user_widget_id
         ,row_number() OVER (PARTITION BY user_id
                             ORDER BY user_widget_id) AS user_widget_nr 
   FROM   user_widgets
   WHERE  user_widget_id > 100000
   ) u
WHERE  w.user_id = u.user_id
AND    w.user_widget_id = u.user_widget_id;

可能会在非工作时间跟进REINDEX甚至VACUUM FULL ANALYZE user_widgets。考虑低于100的FILLFACTOR,因为列将至少更新一次。

我肯定将此留给应用程序。这引入了多个额外的失败点。

答案 1 :(得分:1)

我将加入,质疑具体要求。通常,如果您尝试订购此类产品,那么最好留给应用程序。如果你认识我,你会发现这真的在说些什么。我担心的是,我能想到的每一个案例都可能需要对申请进行重新排序,否则这些数字将无关紧要。

所以我会:

CREATE TABLE user_widgets (
      user_id int references users(id),
      widget_id int,
      widget_name text not null,
      primary key(user_id, widget_id)
);

我会离开它。

现在根据您的理由,这解决了您的所有问题(进口)。但是我曾经有一段时间不得不做类似的事情。我使用的用例是当地税务管辖区要求装箱单(!)按顺序编号而没有间隙,与发票分开。计算记录,顺便说一句不符合您的进口要求。

我们所做的是创建一个表,每个序列有一行并使用它然后将其与触发器绑定。