TinyTds::Error: невозможно вставить значение NULL в столбец «ID»

Моя система Ruby on Rails переходит с Oracle на Microsoft SQL Server 2012. Внутренняя база данных уже на треть преобразована с Oracle на Microsoft SQL Server. Я не контролирую структуру схемы. Это не может быть изменено.

Используя activerecord-sqlserver-adapter, tiny_tds и Freetds, я могу подключиться к новой базе данных. Большинство запросов к БД работают хорошо.

Однако многие таблицы имеют первичные ключи ID, которые автоматически увеличиваются SQL Server, другими словами, столбцы PK IDENTITY(1,1). Тот же код, который работал с соединениями Oracle, не работает под SQL Server со следующей ошибкой:

TinyTds::Error: невозможно вставить значение NULL в столбец «ID»

Я знаю, почему это делается, поскольку столбец ID действительно является столбцом primary_key и IDENTITY (1,1) и включает ограничение NOT NULL. Это нормально.

Вставка в таблицу работает нормально, если используются необработанные операторы выполнения SQL, где, конечно, я исключаю столбец ID из оператора INSERT.

Однако я потратил несколько дней на гугление и не могу найти способ сказать Ruby on Rails не пытаться сохранить столбец идентификатора.

Итак, у меня есть экземпляр класса @book, который содержит

Library::Book(id: integer, title: string, isbn: string...)

Когда я делаю @book.save! он генерирует ошибку выше.

@book = Library::Book.new( .. )
:
:
@book.save!

TinyTds::Error: невозможно вставить значение NULL в столбец «ID»

Вместо того, чтобы прибегать к голому железу SQL, как мне сделать что-то более Railsy и сказать ему, что я хочу сохранить запись, но не пытаться сохранить поле идентификатора, поскольку оно автоматически увеличивается? Так эффективно я пытаюсь спасти

Library::Book(title: string, isbn: string...), если вставляется новая запись, или Library::Book(id: integer, title: string, isbn: string...), если вы пытаетесь обновить запись.

Из-за наложенных ограничений я использую: Ruby 2.3.3p222 Rails 4.0.13 activerecord (4.0.13) activerecord-sqlserver-adapter (4.0.4) tiny_tds (1.0.2) Freetds 1.00.27


person user321321    schedule 08.02.2019    source источник


Ответы (1)


Вы можете использовать ActiveRecord::Relation#find_or_initialize_by. Это предполагает, что у вас есть достаточно атрибутов, известных на данный момент, чтобы однозначно идентифицировать запись.

Это решит последнюю часть вашего вопроса. Но похоже, что столбец id не настроен на автоинкремент. Вам нужно установить это так, чтобы когда Rails отправлял идентификатор null, БД устанавливала его правильно.

Обновление: чтобы сделать столбец автоинкрементным, вам нужно запустить миграцию. Затем вы можете сделать:

Alter table books modify column id int(11) auto_increment;

Обновление: если мы не можем изменить базу данных, мы можем сделать:

class Book

  private

  def create_or_update(*args, &block)
    self.id ||= Book.maximum(:id) + 1
    super
  rescue ActiveRecord::RecordNotUnique
    self.id = nil
    retry
  end
end

Это довольно дергано, и я настоятельно рекомендую обновить колонку, если это возможно.

person Tom    schedule 08.02.2019
comment
Спасибо, Том. Это то, что я пытался найти, средство сообщить Rails, что столбец ID является автоматически увеличивающимся типом. Я не могу найти оператор или синтаксис для этого. У меня есть идентификатор self.primary_key= в модели. Нужно ли мне изменить это или определить атрибут автоматического увеличения в другом месте? Некоторые из предложений, которые я видел, похоже, не поддерживаются в моей среде. - person user321321; 08.02.2019
comment
@user321321 user321321 Я обновил свой ответ с помощью миграции, чтобы изменить поведение столбцов. - person Tom; 08.02.2019
comment
Привет том. Таблица Microsoft SQL Server фиксирована и имеет такой идентификатор. Я не могу это изменить. [ID] [числовой](18, 0) IDENTITY(1,1) NOT NULL. Изменит ли описанная выше миграция структуру БД? - person user321321; 08.02.2019
comment
@ user321321 Я добавил способ переопределить поведение Rails, если вам не разрешено изменять БД. - person Tom; 08.02.2019
comment
Спасибо, Том. Это, безусловно, остановило ошибку вставки. Мне просто интересно, есть ли при этом очень небольшая вероятность того, что двум отдельным процессам может быть назначен один и тот же идентификатор, первый вставляет новую запись, а второй перезаписывает первый? Я новичок в работе с ActiveRecord. - person user321321; 08.02.2019
comment
@user321321 user321321 Это не перезапишет первое. База данных будет жаловаться на уникальность (при условии, что это уникальный столбец). Я добавил один способ защиты от этого. Если есть условие гонки, которое он проигрывает, он сбросит идентификатор и повторит попытку. - person Tom; 08.02.2019
comment
Спасибо. В конце недели попробую. Большое спасибо. - person user321321; 08.02.2019
comment
Почему бы не self.id ||= Book.maximum(:id).to_i + 1 кажется немного более кратким - person engineersmnky; 08.02.2019
comment
@engineersmnky Потому что раннее утро :). Обновлен с гораздо лучшим запросом. - person Tom; 08.02.2019