2009-05-25 5 views
6

私は、使用されていない/生成されていない/データベースに存在しない特定のレコードに対してID(ユニークキー)を割り当てる必要があるクエリを記述する必要があります。テーブルで最初に未使用のIDを取得するにはどうすればよいですか?

要するに、特定のレコードに対してidを生成し、それを印刷画面に表示する必要があります。

E. gは:

 
ID Name 

1 abc 
2 def 
5 ghi 

ので、事はそれがまだ発生していないされた直後のようID=3を返す必要があり、かつidのこの世代の後、私はこのデータを保存することですデータベーステーブルに戻る。

これはHWではありません。私はプロジェクトを行っています。私はこのクエリを書く必要があるので、これを達成するためにいくつかの助けが必要です。

このクエリを作成する方法、またはこれを達成する方法を教えてください。

ありがとうございました。

私は私の手順は次のようになります..私はデータベースとしてMySQLを使用しています ..ので、私はここに私のコメントを書いていますThatsなぜ,,

コメントを追加することはできませんよ: -

1)使用されていないデータベーステーブルからIDを取得します。

2)そうではありません。 (ウェブサイトベースのプロジェクト)のため、私は同時実行が起こらないようにしたいので、1人のユーザーに1つのIDが生成された場合、同じユーザーがIDを受け取ってそのIDのレコードを保存するまで、データベースをロックする必要があります。その後、他のユーザーはIDが存在しないものを取得することができます..(主な要件)..

私はこれらすべてをMySQLで実現できますか?また、Quassnoiの答えは価値があると思います。 MySQLで作業していますので、plzは私にとって新しいものであるため、クエリについて少し説明します。このクエリはMySQLで動作します。

+0

あなたのプロジェクトにはどのようなRDBMSを使用していますか? – Quassnoi

+4

ここでは並行性に注意してください。複数のユーザーがいる場合、Quassnoiのクエリを実行してからDBに結果を格納するまでに時間差があると、IDが重複する可能性があります。なぜ、RDBMSにあなたのID列を管理させるだけではないのですか? –

+2

DDaviesBrackettが書いているように、これが宿題でない場合は、現実世界の重大な問題があります。2つのプロセスがクエリを実行し、回答を得た後、それぞれが重複レコードを挿入しようとします。これがちょうど質問に答えることであるならば、何か間隙があるか?それは違う。それで誰も気にするのはちょうど面白いです。 – Yishai

答えて

6

私はあなたのテーブルunusedという名前を付けました。

SELECT id 
FROM (
     SELECT 1 AS id 
     ) q1 
WHERE NOT EXISTS 
     (
     SELECT 1 
     FROM unused 
     WHERE id = 1 
     ) 
UNION ALL 
SELECT * 
FROM (
     SELECT id + 1 
     FROM unused t 
     WHERE NOT EXISTS 
       (
       SELECT 1 
       FROM unused ti 
       WHERE ti.id = t.id + 1 
       ) 
     ORDER BY 
       id 
     LIMIT 1 
     ) q2 
ORDER BY 
     id 
LIMIT 1 

このクエリは2つの部分で構成されています。

最初の部分:

SELECT * 
FROM (
     SELECT 1 AS id 
     ) q 
WHERE NOT EXISTS 
     (
     SELECT 1 
     FROM unused 
     WHERE id = 1 
     ) 

1を選択し、このidとテーブル内のエントリが存在しないです。

第二部:

SELECT * 
FROM (
     SELECT id + 1 
     FROM unused t 
     WHERE NOT EXISTS 
       (
       SELECT 1 
       FROM unused ti 
       WHERE ti.id = t.id + 1 
       ) 
     ORDER BY 
       id 
     LIMIT 1 
     ) q2 

には次idがないいるテーブル内の最初のidを選択します。

結果のクエリでは、これらの2つの値の中で最小のものを選択します。

+1

は、最初の既存のIDよりも小さいIDを見つけることはありません。つまりテーブルのIDが3,4,6の場合、5は検索されますが、1と2は検索されません。0より大きく、最初のidよりも小さいidを検索する別のselectと結合することができます。 –

+0

@リムス:良い点、追加、ありがとう。 – Quassnoi

+0

私の手順は次のようになります: - 1)使用されていないデータベーステーブルからIDを取得してください。 2)そうではありません。 (ウェブサイトベースのプロジェクト)のため、私は同時性が発生しないようにしたいので、1人のユーザーに1つのIDが生成された場合、同じユーザーがIDを受け取ってそのIDのレコードを保存するまで、データベースをロックする必要があります。その後、他のユーザーはIDが存在しないものを取得することができます。(主な要件) どのように私はこれらのことをすべてMySQLで達成できますか? – AGeek

5

「次のID」の意味と生成方法によって異なります。

IDを生成するためにデータベース内のシーケンスまたはIDを使用している場合は、「次のID」が提示されている場合は3または4ではなく6である可能性があります。後で削除されたIDが3または4の値があるかどうかを知る方法はありません。配列やアイデンティティは必ずしもギャップを取り戻そうとはしません。彼らがいなくなったら、あなたはそれらを再利用しません。

正しいことは、INSERTを実行すると自動的にインクリメントされるシーケンスまたはID列をデータベースに作成し、生成された値をSELECTすることです。

+0

を参照してください。ただし、データベースにアクセスするユーザーが異なるため、2人のユーザーが同じIDを受け取る時間がある可能性があります。この同時性を避けるにはどうすればよいでしょうか、plz Thanx .. – AGeek

+3

mysqlでauto_incrementフィールドを使うと、並行性について心配する必要はありません。ちょうど挿入した行のIDを取得するには、LAST_INSERT_ID()を使用してください。 –

0

あなたはユーティリティテーブルを持っていますか?ので、私はそうのようなテーブルを作成するかどう:

CREATE TABLE number_helper (
    n INT NOT NULL 
    ,PRIMARY KEY(n) 
); 

は次にあなたがそうのように選択することができ、すべての正の32ビット整数(あなたが生成する必要がidは正の32ビット整数であると仮定した場合)

でそれを埋めます:

SELECT MIN(h.n) as nextID 
FROM my_table t 
LEFT JOIN number_helper h ON h.n = t.ID 
WHERE t.ID IS NULL 

これは実際にテストされていませんが動作するはずです。

+0

明らかに、これはパフォーマンスを賢明に吸うことになりますが、アイデンティティ列を説明するだけではなく、質問に記載されている仕様を満たすのが比較的簡単な方法です(私は現時点で考えることができます)。 – Kris

1
/* 
This is a query script I wrote to illustrate my method, and it was created to solve a Real World problem where we have multiple machines at multiple stores creating transfer transactions in their own databases, 
that are then synced to other databases on the store (this happens often, so getting the Nth free entry for the Nth machine should work) where the transferid is the PK and then those are synced daily to a MainFrame where the maximum size of the key (which is the TransactionID and StoreID) is limited. 
*/ 

--- table variable declarations 
/* list of used transaction ids (this is just for testing, it will be the view or table you are reading the transaction ids from when implemented)*/ 

DECLARE @SampleTransferIDSourceTable TABLE(TransferID INT)  

/* Here we insert the used transaction numbers*/ 

DECLARE @WorkTable TABLE (WorkTableID INT IDENTITY (1,1), TransferID INT) 

/*this is the same table as above with an extra column to help us identify the blocks of unused row numbers (modifying a table variable is not a good idea)*/ 

DECLARE @WorkTable2 TABLE (WorkTableID INT , TransferID INT, diff int) 

--- Machine ID declared 

DECLARE @MachineID INT 

-- MachineID set 

SET @MachineID = 5 

-- put in some rows with different sized blocks of missing rows. 
-- comment out the inserts after two to the bottom to see how it handles no gaps or make 
-- the @MachineID very large to do the same. 
-- comment out early rows to test how it handles starting gaps. 

INSERT @SampleTransferIDSourceTable (TransferID) VALUES (1) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (2) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (4) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (5) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (6) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (9) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (10) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (20) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (21) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (24) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (25) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (30) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (31) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (33) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (39) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (40) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (50) 

-- copy the transaction ids into a table with an identiy item. 
-- When implemented add where clause before the order by to limit to the local StoreID 
-- Zero row added so that it will find gaps before the lowest used row. 

INSERT @WorkTable (TransferID) 

SELECT 0 

INSERT @WorkTable (TransferID) 

SELECT TransferID FROM @SampleTransferIDSourceTable ORDER BY TransferID 

-- copy that table to the new table with the diff column 

INSERT @WorkTable2 

SELECT WorkTableID,TransferID,TransferID - WorkTableID 

    FROM @WorkTable    

--- gives us the (MachineID)th unused ID or the (MachineID)th id beyond the highest id used. 

IF EXISTS (

SELECT Top 1 

     GapStart.TransferID + @MachineID - (GapStart.diff + 1) 

    FROM @WorkTable2 GapStart 

INNER JOIN @WorkTable2 GapEnd 

    ON GapStart.WorkTableID = GapEnd.WorkTableID - 1 

    AND GapStart.diff < GapEnd.diff 

    AND gapEnd.diff >= (@MachineID - 1) 

ORDER BY GapStart.TransferID 

) 

SELECT Top 1 

     GapStart.TransferID + @MachineID - (GapStart.diff + 1) 

    FROM @WorkTable2 GapStart 

INNER JOIN @WorkTable2 GapEnd 

    ON GapStart.WorkTableID = GapEnd.WorkTableID - 1 

    AND GapStart.diff < GapEnd.diff 

    AND gapEnd.diff >= (@MachineID - 1) 

ORDER BY GapStart.TransferID 

ELSE 

SELECT MAX(TransferID) + @MachineID FROM @SampleTransferIDSourceTable 
1

正しい方法は、主キーにID列を使用することです。既に挿入された行を見て、未使用の値を選択しないでください。 ID列には、アプリケーションが有効な新しい(より高い)値を決して使い果たしないような大きさの番号を入れる必要があります。

あなたの説明では、後で使用しようとしている値をスキップしている場合、おそらく値に意味を与えています。再考してください。このフィールドは、別のテーブルから参照(参照)値としてのみ使用してください。

データベースエンジンがIDの次に高い値を割り当てるようにします。複数のプロセスが同時に実行されている場合は、LAST_INSERT_ID()関数を使用して、データベースが行に対して生成したIDを判別する必要があります。 LAST_INSERT_ID()関数は、コミットする前に同じトランザクション内で使用できます。

インデックスフィールドの最大値に1を加えた値を使用することをお勧めします。並行性の問題を管理するには、テーブルロックを行う必要があります。

+0

本当に、彼が質問した質問は彼にとって正しい質問ではないかもしれませんが、実際には、私たちの中には答えがある(つまり、限られたリソースプールから未使用のリソースを割り当てる、プライマリキーの場合と同様に制約のないプール)。 – ijw

0

MySqlで動作する必要があります。

SELECT TOP 100 
    T1.ID + 1 AS FREE_ID 
FROM TABLE1 T1 
LEFT JOIN TABLE2 T2 ON T2.ID = T1.ID + 1 
WHERE T2.ID IS NULL 
関連する問題