уникальный по нескольким столбцам и значениям NULL - PullRequest
1 голос
/ 05 ноября 2010

Я занимаюсь разработкой с использованием symfony 1.4 (и Doctrine) и имею таблицу базы данных MySQL с уникальным индексом на нескольких столбцах. Во-первых, определение таблицы YAML до сих пор:

Campaign:
  actAs:
    Sluggable:
      fields: [name]
      canUpdate: true
      uniqueBy: [merchant_id, deleted_at]
    Timestampable: ~
    SoftDelete: ~
  columns:
    merchant_id:      { type: integer, notnull: true }
    name:             { type: string(255), notnull: true, notblank: true }
    start_date:       { type: date, notnull: true, notblank: true }
    end_date:         { type: date, notnull: true, notblank: true }
  indexes:
    unique_name:  { fields: [name, merchant_id, deleted_at], type: unique }
  relations:
    Merchant: { local: merchant_id, foreign: id }

Как видите, мне приходится иметь дело с кампаниями, принадлежащими торговцам. Кампания знает своего продавца и имеет имя (а также дату начала и дату окончания). Название кампании должно быть уникальным - не глобально, а для конкретного продавца. Для этого мне понадобится уникальный индекс названия кампании и соответствующего продавца. Но, поскольку таблица «действует как SoftDelete» и пользователь должен иметь возможность создавать новую кампанию с именем, которое уже существует для кампании «мягкого удаления», столбец deleted_at также должен быть частью уникального индекса , Видите ли, уникальность названия кампании касается только не удаленных кампаний соответствующего продавца.

Теперь перейдем к актуальной проблеме : поскольку столбец deleted_at равен NULL для всех не удаленных кампаний, а значения NULL в уникальном индексе всегда рассматриваются как уникальные, все кампании могут иметь не уникальные имена - в истинном смысле. Я знаю, что это относится к таблицам MyISAM и InnoDB, но не к таблицам BDB. Однако, переход на BDB не мой любимый вариант, если вы понимаете, о чем я.

Теперь перейдем к актуальному вопросу : Какие есть другие возможные варианты, кроме изменения движка MySQL на BDB? Обходной путь может состоять в том, чтобы переименовать кампанию с мягким удалением, например, name = 'DELETED AT ' + deleted_at + ': ' + name. Это, с одной стороны, будет иметь то преимущество, что все кампании с мягким удалением будут иметь уникальные имена даже в том случае, если они будут восстановлены (сброс deleted_at обратно на NULL). Столбец deleted_at больше не должен был бы быть частью уникального индекса, и, таким образом, все кампании (не удаленные, не удаленные, а восстановленные один раз) будут иметь уникальное имя - относительно соответствующего продавца. Но, с другой стороны, я не думаю, что это будет самое элегантное решение. Каковы ваши мнения и опыт по этому поводу?

Я очень благодарен вам и рад вашему вкладу.
Flinsch.

1 Ответ

1 голос
/ 05 ноября 2010

Я думаю, что вы можете сохранить свою базовую структуру, вам просто нужен способ сделать delete_at NOT NULL.Это означает, что вам нужно установить его по умолчанию.Хорошее значение по умолчанию - 0 или 0000-00-00 00: 00: 00.

Моя рекомендация - добавить новый столбец, чтобы указать, были ли строки удалены логически.Вы можете назвать это "IS_DELETED".Затем добавьте значение по умолчанию для delete_at и сделайте его ненулевым, и включите is_deleted в свой уникальный индекс.

Вот очень простой пример такого подхода в действии:

mysql> create table merchant(
    -> id int unsigned not null auto_increment,
    -> name varchar(50) not null, 
    -> is_deleted tinyint not null default 0, 
    -> deleted_at datetime not null default 0,
    -> primary key (id),
    -> unique key name_and_deleted_at (name,is_deleted,deleted_at)
    -> ) ENGINE = InnoDB;
Query OK, 0 rows affected (0.08 sec)

mysql> 
mysql> -- successful inserts
mysql> insert into merchant (name,is_deleted) values ('foo',0);
Query OK, 1 row affected (0.00 sec)

mysql> insert into merchant (name,is_deleted) values ('bar',0);
Query OK, 1 row affected (0.00 sec)

mysql> 
mysql> -- insert failure due to duplicate name
mysql> insert into merchant (name,is_deleted) values ('foo',0);
ERROR 1062 (23000): Duplicate entry 'foo-0-0000-00-00 00:00:00' for key 'name_and_deleted_at'
mysql> -- logical delete
mysql> update merchant set is_deleted = true, deleted_at = now() where name = 'foo';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> -- now the insert succeeds
mysql> insert into merchant (name,is_deleted) values ('foo',0);
Query OK, 1 row affected (0.01 sec)

mysql> 
mysql> -- show data
mysql> select id,name,is_deleted,deleted_at 
    -> from merchant
    -> order by id;
+----+------+------------+---------------------+
| id | name | is_deleted | deleted_at          |
+----+------+------------+---------------------+
|  1 | foo  |          1 | 2010-11-05 13:54:17 |
|  2 | bar  |          0 | 0000-00-00 00:00:00 |
|  4 | foo  |          0 | 0000-00-00 00:00:00 |
+----+------+------------+---------------------+
3 rows in set (0.00 sec)
...