SQL Server уникальный столбец автоинкремента в контексте другого столбца - PullRequest
13 голосов
/ 05 февраля 2010

Предположим, таблица с двумя столбцами:

ParentEntityId int foreign key
Number int

ParentEntityId - это внешний ключ для другой таблицы.

Number - это локальный идентификатор, т. Е. Он уникален в пределах одного ParentEntityId.

Уникальность легко достигается с помощью уникального ключа над этими двумя столбцами.

Как заставить Number автоматически увеличиваться в контексте ParentEntityId при вставке?


Приложение 1

Чтобы прояснить проблему, вот аннотация.

ParentEntity имеет несколько ChildEntity, и каждый ChiildEntity должен иметь уникальный инкремент Number в контексте его ParentEntity.


Приложение 2

Рассматривайте ParentEntity как Клиент .

Рассматривать ChildEntity как Заказ .

Итак, заказы для каждого клиента должны быть пронумерованы 1, 2, 3 и т. Д.

Ответы [ 3 ]

9 голосов
/ 05 февраля 2010

Ну, для этого типа столбца нет встроенной поддержки, но вы можете реализовать его с помощью триггера:

CREATE TRIGGER tr_MyTable_Number
ON MyTable
INSTEAD OF INSERT
AS

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

BEGIN TRAN;

WITH MaxNumbers_CTE AS
(
    SELECT ParentEntityID, MAX(Number) AS Number
    FROM MyTable
    WHERE ParentEntityID IN (SELECT ParentEntityID FROM inserted)
)
INSERT MyTable (ParentEntityID, Number)
    SELECT
        i.ParentEntityID,
        ROW_NUMBER() OVER
        (
            PARTITION BY i.ParentEntityID
            ORDER BY (SELECT 1)
        ) + ISNULL(m.Number, 0) AS Number
    FROM inserted i
    LEFT JOIN MaxNumbers_CTE m
        ON m.ParentEntityID = i.ParentEntityID

COMMIT

Не проверено, но я уверен, что это сработает. Если у вас есть первичный ключ, вы можете также реализовать его как триггер AFTER (мне не нравится использовать триггеры INSTEAD OF, их сложнее понять, когда вам нужно изменить их через 6 месяцев).

Просто чтобы объяснить, что здесь происходит:

  • SERIALIZABLE - самый строгий режим изоляции; это гарантирует, что только одна транзакция базы данных за раз может выполнить эти операторы, которые нам нужны, чтобы гарантировать целостность этой «последовательности». Обратите внимание, что это необратимо продвигает всю транзакцию, поэтому вы не захотите использовать ее внутри длительной транзакции.

  • CTE выбирает наибольшее число, уже используемое для каждого родительского идентификатора;

  • ROW_NUMBER генерирует уникальную последовательность для каждого родительского идентификатора (PARTITION BY), начиная с номера 1; мы добавляем это к предыдущему максимуму, если есть один, чтобы получить новую последовательность.

Я, вероятно, должен также упомянуть, что если вам когда-либо нужно будет вставлять только одну новую дочернюю сущность за раз, вам лучше просто направить эти операции через хранимую процедуру вместо использования триггера - вы определенно получите лучшую производительность из этого. Вот как это в настоящее время делается с hierarchyid столбцами в SQL '08.

2 голосов
/ 26 февраля 2010

Нужно добавить предложение OUTPUT для запуска совместимости Linq to SQL.

Например:

INSERT MyTable (ParentEntityID, Number)
OUTPUT inserted.* 
SELECT
  i.ParentEntityID,
  ROW_NUMBER() OVER
  (
   PARTITION BY i.ParentEntityID
   ORDER BY (SELECT 1)
  ) + ISNULL(m.Number, 0) AS Number
FROM inserted i
LEFT JOIN MaxNumbers_CTE m
ON m.ParentEntityID = i.ParentEntityID
0 голосов
/ 05 февраля 2010

Это решает вопрос, насколько я понимаю:: -)

DECLARE @foreignKey int
SET @foreignKey = 1  -- or however you get this

INSERT Tbl (ParentEntityId, Number) 
VALUES (@foreignKey, ISNULL((SELECT MAX(Number) FROM Tbl WHERE ParentEntityId = @foreignKey), 0) + 1)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...