我想创建一个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列。或者甚至只计算每个用户的行数,而不允许删除记录(改为使用删除/停用的标志)。这可以通过触发器功能,甚至存储过程而不是在应用程序层中完成吗?
答案 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)
);
我会离开它。
现在根据您的理由,这解决了您的所有问题(进口)。但是我曾经有一段时间不得不做类似的事情。我使用的用例是当地税务管辖区要求装箱单(!)按顺序编号而没有间隙,与发票分开。计算记录,顺便说一句不符合您的进口要求。
我们所做的是创建一个表,每个序列有一行并使用它然后将其与触发器绑定。