Эквивалент С# «только для чтения» для столбца MS SQL?

Представьте, что в таблице Products есть столбец Price, и цена может измениться.
Меня это устраивает, но я хочу сохранить исходное значение Price в другом столбце.

Есть ли какой-либо автоматический способ, которым сервер MS SQL может это сделать?

Могу ли я сделать это с помощью поля Значение по умолчанию?
Нужно ли объявлять триггер?

Обновлять

Я попытался использовать Price, чтобы упростить вопрос, но похоже, что это спровоцировало использование отдельных табличных ответов.
Прошу прощения за путаницу, которую я вызвал.

В реальном мире мне нужно сохранить идентификатор внешнего ключа, и мне на 100% нужны только текущее и исходное значения.

Обновление 2

Меня немного смутили различные предложенные подходы, поэтому, пожалуйста, позвольте мне еще раз объяснить ситуацию.

Воображаемая таблица Products имеет три поля: ID, Price и OriginalPrice.
Я хочу установить значение OriginalPrice в Price для любой вставки.

Иногда это один продукт, созданный из кода. Иногда есть тысячи продуктов, созданных одним insert из хранимой процедуры, поэтому я также хочу правильно обрабатывать их.

После установки OriginalPrice я никогда не собираюсь его обновлять.

Надеюсь, теперь мой вопрос стал понятнее.
Спасибо за ваши усилия.

Окончательное обновление

Я хочу поблагодарить всех, особенно @gbn, за их помощь.
Хотя я опубликовал мой собственный ответ, он в значительной степени основан на ответ @gbn и его дальнейшие предложения. Его ответ также более полный, поэтому я отмечаю его как правильный.


person Dan Abramov    schedule 02.08.2011    source источник


Ответы (4)


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

Давайте проигнорируем, если одно и то же обновление произойдет быстро из-за ошибки в клиентском коде и что вас не интересует история (другие ответы)

Вы можете использовать триггер или хранимую процедуру.

Лично я бы использовал хранимую процедуру, чтобы обеспечить базовый контроль. И тогда не требуются прямые разрешения на ОБНОВЛЕНИЕ, что означает, что вы только читали если только через свой код.

  CREATE PROC etc
  ...
  UPDATE
    MyTable
  SET
    OldPrice = Price,
    Price = @NewPrice,
    UpdatedBy = (variable or default)
    UpdatedWhen = DEFAULT --you have a DEFAULT right?
  WHERE
    PKCol = @SomeID
    AND --provide some modicum of logic to trap useless updates
    Price <> @NewPrice;

Триггер был бы похож, но вам нужно иметь JOIN с таблицами INSERTED и DELETED Что, если кто-то обновит OldPrice напрямую?

  UPDATE
    T
  SET
    OldPrice = D.Price
  FROM
    Mytable T
    JOIN
    INSERTED I ON T.PKCol = I.PKCol
    JOIN
    DELETED D ON T.PKCol = D.PKCol
  WHERE
    T.Price <> I.Price;

Теперь ты понимаешь, почему на тебя напали...?

После редактирования вопроса, только для INSERT

  UPDATE
    T
  SET
    OriginalPrice = I.Price
  FROM
    Mytable T
    JOIN
    INSERTED I ON T.PKCol = I.PKCol

Но если все INSERT происходят через хранимую процедуру, я бы установил ее там....

person gbn    schedule 02.08.2011
comment
Да! Ура гбн. Я думаю, что, возможно, неправильно понял, как работает триггер. Я думал, что триггер будет содержать только 1 запись в таблице Inserted, поскольку триггер будет запускаться несколько раз, если несколько записей будут обновлены. Это не тот случай кажется? - person Curt; 02.08.2011
comment
Я отредактировал свой вопрос, чтобы сделать его более понятным. Не могли бы вы взглянуть? Я высоко ценю ваше время и усилия, спасибо. - person Dan Abramov; 02.08.2011
comment
Спасибо за второй образец. Если я выберу триггер, нужно ли мне сделать его for insert? Это правильно? - person Dan Abramov; 02.08.2011
comment
Причина, по которой я запутался, заключается в том, что я думал, что триггеры for insert не имеют таблицы deleted. - person Dan Abramov; 02.08.2011
comment
@Dan Abramov: теперь вы указали цену при вставке, она никогда не будет обновляться, так понятнее. Ваш исходный подразумеваемый текущий и предыдущий (что является ответом Митча) - person gbn; 02.08.2011
comment
Нет, это актуально и оригинально :-). Но ты прав, я запутался в простом вопросе. Да, я хочу установить OriginalPrice только во время первой вставки, и я хочу, чтобы она работала как с пакетной вставкой хранимой процедуры, так и из кода (одиночная вставка). Поэтому я не стал размещать логику в самой процедуре пакетной вставки. Извините за путаницу. - person Dan Abramov; 02.08.2011

Для столбца таблицы SQL Server нет атрибута только для чтения. НО вы можете реализовать описанную вами функциональность с помощью триггера (и ограничения разрешений)

За исключением, это не лучший способ решить проблему. Вместо этого относитесь к цене как к типу 2 'медленно меняющееся измерение'. Это предполагает наличие столбца "Действительно для". (столбцы os 'StartDate' и 'EndDate') и закрытие записи:

Supplier_Key    Supplier_Code   Supplier_Name   Supplier_State  Start_Date  End_Date
123           ABC             Acme Supply Co    CA            01-Jan-2000   21-Dec-2004
124           ABC             Acme Supply Co    IL            22-Dec-2004   

Если вы все же пойдете по пути триггера (я предлагаю вам использовать SCD типа 2), убедитесь, что он может обрабатывать несколько строк: Многострочные рекомендации для триггеров DML

person Mitch Wheat    schedule 02.08.2011
comment
Спасибо за ваш вклад в эту тему. Не могли бы вы взглянуть на последнее обновление, которое я опубликовал? Я думаю, что наконец-то ясно изложил свой вопрос (надеюсь, что да ..) - person Dan Abramov; 02.08.2011

Я бы рекомендовал хранить вашу цену в отдельной таблице под названием Prices со столбцами Price и Date.

Затем всякий раз, когда цена обновляется, INSERT новая запись в таблице Prices. Затем, когда вам нужно узнать текущую цену, вы можете получить оттуда.


Однако, если вы хотите обновить столбец OriginalPrice автоматически, вы можете добавить TRIGGER в таблицу, чтобы сделать это:

http://msdn.microsoft.com/en-us/library/aa258254%28v=sql.80%29.aspx

person Curt    schedule 02.08.2011
comment
Спасибо. Я должен был уточнить - Price был просто примером, в моей реальной проблеме мне нужно сохранить исходный идентификатор внешнего ключа, и наверняка мне нужен доступ только к текущим и исходным значениям. - person Dan Abramov; 02.08.2011
comment
Ах, в таком случае, посмотрите мою часть ответа о триггерах - person Curt; 02.08.2011
comment
Я думаю, что с этим лучше обращаться как с «медленно меняющимся измерением» типа 2. - person Mitch Wheat; 02.08.2011
comment
@Curt: этот код курсора очень ошибочен: ВСЕ курсоры должны иметь возможность обрабатывать несколько строк. - person Mitch Wheat; 02.08.2011
comment
@Mitch Wheat - Не могли бы вы объяснить подробнее или предоставить статью, на которую я мог бы сослаться? Именно так я использовал триггеры в последние несколько лет, поэтому было бы интересно узнать, что я сделал неправильно. - person Curt; 02.08.2011
comment
@Curt: если это так, боюсь, вы пишете неработающий код. Любой триггер должен иметь возможность обрабатывать несколько строк. msdn.microsoft.com/en-us/library/ms190752.aspx - person Mitch Wheat; 02.08.2011

Это то, с чем я столкнулся, с большой помощью от @gbn, @Mitch и @Curt< /а>:

create trigger TRG_Products_Price_I --common-ish naming style
    on dbo.Products after insert as
begin
    set nocount on

        update m
        set OriginalPrice = i.Price 
        from Products p
        join inserted i on p.ID = i.ID

end

Я также пытался следить за этой статьей.
Спасибо каждый!

person Dan Abramov    schedule 02.08.2011
comment
Вам нужно только одно условие для любого количества строк. И называя - person gbn; 02.08.2011
comment
Спасибо. Я скоро проверю. Еще один вопрос: значит ли это, что я должен разрешить нулевое значение для OriginalPrice? Очевидно, что триггер запускается после вставки, хотя я уверен, что в конце значение установлено. - person Dan Abramov; 02.08.2011
comment
Либо установите значение по умолчанию для столбца (скажем, ноль), либо сделайте его NULL. Лично я бы сделал это NULL - person gbn; 02.08.2011