2

user_id と user_widget_id を主キーとする user_widgets のテーブルを作成したいと思います。ここで、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 の大幅なジャンプに困惑したりイライラしたりする可能性があると思います。もちろん、これは機能的というよりは表面的なものですが、それでも価値がある可能性があります。

可能な解決策

答えの 1 つは、アプリケーション層がこれを処理することを示唆しています。インクリメントされるユーザーのテーブルに next_id 列を格納できます。または、ユーザーごとに行数を数えて、レコードの削除を許可しないこともできます (代わりに削除済み/非アクティブ化フラグを使用)。これは、アプリケーション層ではなく、トリガー関数またはストアド プロシージャを使用して実行できますか?

4

2 に答える 2

6

テーブルがある場合:

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このシナリオではユーザーごとにアルファベット順に適用され、ギャップはありません。エントリを追加、変更、または削除すると、明らかに変更が生じる可能性があります。

ウィンドウ関数の詳細については、マニュアルを参照してください。


ある程度 (完全ではありませんが) 安定しています:

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トランザクション モードを使用する必要があります。どちらもパフォーマンスを大幅に低下させ、追加のコードが必要です。

ただし、行が削除されないことを保証する場合は、2 番目のアプローチを使用できます。これは、 「生の」 IDuser_widget_idを提供するテーブル全体の1 つのシーケンスです。シーケンスは、同時ロードの実績のあるソリューションであり、相対的な順序を保持し、高速です。「生」を上記のクエリのような対応するものに動的に置き換えるビューを使用して、テーブルへのアクセスを提供できます。user_widget_iduser_widget_iduser_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 をすばやく識別します。テーブルが巨大な場合は、条件を使用して部分インデックスを追加することができます (これは小さくなります)。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ます。FILLFACTOR列は少なくとも 1 回更新されるため、100 未満を考慮してください。

私は確かにこれをアプリケーションに任せません。これにより、複数の障害点が追加されます。

于 2012-09-10T20:53:28.920 に答える
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)
);

そして、私はそれをそのままにしておきます。

あなたの正当化に基づいて、これはあなたのすべての懸念(インポート)に対処します。しかし、私は長い間、似たようなことをしなければなりませんでした。私が持っていたユースケースは、地方の税管轄区域が、請求書とは別に、梱包伝票 (!) にギャップなしで連続番号を付ける必要がある場合でした。レコードを数えますが、インポート要件を満たしていません。

私たちが行ったことは、シーケンスごとに 1 つの行を持つテーブルを作成し、それを使用して、それをトリガーに結び付けることでした。

于 2012-09-11T06:47:31.590 に答える