使用默认值将NULL插入NOT NULL列

时间:2014-09-24 17:44:43

标签: php mysql doctrine-orm zend-framework2 default-value

对于一些背景知识,我们在工作中使用 Zend Framework 2 Doctrine 。对于我们自己不填充的值,Doctrine将始终插入NULL。通常这可以,就好像字段有默认值,然后它应该使用此默认值填充字段。

对于我们运行 MySQL 5.6.16 的其中一个服务器,如下所示的查询运行并执行正常。虽然NULL被插入到不可为空的字段中,但MySQL会在插入时使用其默认值填充该字段。

在我们运行 MySQL 5.6.20 的另一台服务器上,我们运行下面的查询,但由于它抱怨' field_with_default_value'不能为空。

INSERT INTO table_name(id, field, field_with_default_value) 
VALUES(id_value, field_value, NULL);

学说本身并不支持通过" DEFAULT"进入它构建的查询,这不是一个选项。我认为这必须是一个MySQL服务器的东西,看起来好像它在一个版本但不是另一个版本中工作正常,但不幸的是我不知道这可能是什么。我们的SQL模式在两个服务器上都是相同的('NO_AUTO_VALUE_ON_ZERO,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION')。

我应该提一下,如果我实际在Workbench中运行上述SQL,它仍然无法以相同的方式工作。所以它不是一个真正的学说问题,但绝对是某种类型的MySQL问题。

对此的任何帮助将不胜感激。

5 个答案:

答案 0 :(得分:1)

根据文档,一切都按预期工作。

测试用例:

mysql> use test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> SELECT VERSION();
+-----------+
| VERSION() |
+-----------+
| 5.6.16    |
+-----------+
1 row in set (0.00 sec)

mysql> SELECT @@GLOBAL.sql_mode 'sql_mode::GLOBAL',
              @@SESSION.sql_mode 'sql_mode::SESSION';
+------------------------+------------------------+
| sql_mode::GLOBAL       | sql_mode::SESSION      |
+------------------------+------------------------+
| NO_ENGINE_SUBSTITUTION | NO_ENGINE_SUBSTITUTION |
+------------------------+------------------------+
1 row in set (0.00 sec)

mysql> SET SESSION sql_mode := 'NO_AUTO_VALUE_ON_ZERO,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @@GLOBAL.sql_mode 'sql_mode::GLOBAL',
              @@SESSION.sql_mode 'sql_mode::SESSION';
+------------------------+-----------------------------------------------------------------------------------------------------------------+
| sql_mode::GLOBAL       | sql_mode::SESSION                                                                                               |
+------------------------+-----------------------------------------------------------------------------------------------------------------+
| NO_ENGINE_SUBSTITUTION | NO_AUTO_VALUE_ON_ZERO,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION |
+------------------------+-----------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> SHOW CREATE TABLE `table_name`;
+------------+----------------------------------------------------------------------------+
| Table      | Create Table                                                               |
+------------+----------------------------------------------------------------------------+
| table_name | CREATE TABLE `table_name` (                                                |
|            |        `id` INT(11) UNSIGNED NOT NULL,                                     |
|            |        `field` VARCHAR(20) DEFAULT NULL,                                   |
|            |        `field_with_default_value` VARCHAR(20) NOT NULL DEFAULT 'myDefault' |
|            | ) ENGINE=InnoDB DEFAULT CHARSET=latin1                                     |
+------------+----------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> INSERT INTO `table_name`(`id`, `field`, `field_with_default_value`)
       VALUES
       (1, 'Value', NULL);
ERROR 1048 (23000): Column 'field_with_default_value' cannot be null

是否可以发布表格结构的相关部分以了解我们如何提供帮助?

<强>更新

MySQL 5.7,使用触发器,可以为问题提供一个可能的解决方案:

  

<强> Changes in MySQL 5.7.1 (2013-04-23, Milestone 11)

     

...

     
      
  • 如果列声明为NOT NULL,则不允许插入   NULL进入列或将其更新为NULL。但是,这种约束   即使有一个BEFORE INSERT(或更新之前)也被强制执行   trigger)将列设置为非NULL值。现在约束   根据SQL标准在语句末尾检查。 (错误   #6295, Bug#11744964)。
  •   
     

...

可能的解决方案:

mysql> use test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> SELECT VERSION();
+-----------+
| VERSION() |
+-----------+
| 5.7.4-m14 |
+-----------+
1 row in set (0.00 sec)

mysql> DELIMITER $$

mysql> CREATE TRIGGER `trg_bi_set_default_value` BEFORE INSERT ON `table_name`
       FOR EACH ROW
       BEGIN
          IF (NEW.`field_with_default_value` IS NULL) THEN
             SET NEW.`field_with_default_value` := 
                (SELECT `COLUMN_DEFAULT`
                 FROM `information_schema`.`COLUMNS`
                 WHERE `TABLE_SCHEMA` = DATABASE() AND 
                       `TABLE_NAME` = 'table_name' AND
                       `COLUMN_NAME` = 'field_with_default_value');
          END IF;
       END$$

mysql> DELIMITER ;

mysql> INSERT INTO `table_name`(`id`, `field`, `field_with_default_value`)
       VALUES
       (1, 'Value', NULL);
Query OK, 1 row affected (0.00 sec)

mysql> SELECT `id`, `field`, `field_with_default_value` FROM `table_name`;
+----+-------+--------------------------+
| id | field | field_with_default_value |
+----+-------+--------------------------+
|  1 | Value | myDefault                |
+----+-------+--------------------------+
1 row in set (0.00 sec)

答案 1 :(得分:1)

在MySQL升级后,我遇到了同样的问题。原来有一个设置允许对NOT NULL时间戳字段进行NULL插入并获取默认值。

explicit_defaults_for_timestamp=0

此文档记录在https://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_explicit_defaults_for_timestamp

答案 2 :(得分:0)

MySQL实际上按预期工作,and that behavior seems to be there to stayMariaDB also works the same way now

删除"strict mode"STRICT_TRANS_TABLES&amp; STRICT_ALL_TABLES)应该恢复到之前的行为,但我个人对此没有任何好运(也许我正在做某事错误,但我的@@GLOBAL.sql_mode@@SESSION.sql_mode都不包含严格模式。

我认为这个问题的最佳解决方案是依赖PHP级别的默认值,而不是依靠数据库来提供它们。 There is an existing answer that explains it pretty well.评论也很有帮助。

这样,您还可以获得额外的好处,即模型/实体在实例化时将具有默认值,而不是在数据库中插入时。此外,如果您希望在插入后向用户显示这些值,则无需在SELECT后执行额外的INSERT查询。

表示默认值的另一种方法是使用RETURNING clause,如PostgreSQL中可用,但不是在MySQL中。它可能会在将来的某个时候添加,但现在MariaDB only has it for DELETE statements。但是,我相信在PHP级别拥有默认值仍然是优越的;即使您从未插入记录,它仍将包含默认值。我从来没有回过头来使用数据库默认值,因为实际上已经实现了这一点。

答案 3 :(得分:0)

根据我的研究,我会说它可能是一个&#34;你&#34;事情和&#34; MySQL&#34;事情。使用SHOW CREATE TABLE table_name;检查表格定义。记下使用NOT NULL定义的任何字段。

MySQL 5.6 Reference Manual: 13.2.5 INSERT syntax州:

  

将NULL插入已声明为NOT NULL的列中。对于   多行INSERT语句或INSERT INTO ... SELECT语句,   该列设置为列数据的 隐式 默认值   类型。数字类型为0,字符串为空字符串(&#39;&#39;)   类型,以及日期和时间类型的“零”值。插入 ...   SELECT语句的处理方式与多行插入的处理方式相同   因为服务器不会检查SELECT中的结果集   看它是否返回一行。 (对于单行INSERT,没有   将NULL插入NOT NULL列时发生警告。代替,   该陈述失败并出现错误。

这意味着您使用的SQL模式无关紧要。如果您正在执行单行 INSERT(根据您的示例代码)并将NULL值插入到使用NOT NULL定义的列中,则不应该工作。

讽刺的是,讽刺的是,如果你只是忽略值列表中的值,那么MySQL手册会说明以下内容,并且在这种情况下 SQL模式确实重要

  

如果您没有在严格的SQL模式下运行,则任何列都不是显式的   给定值设置为其默认值( 显式 隐式 )值。对于   例如,如果指定的列列表未指定所有列表   表中的列,未命名的列设置为其默认值。   第11.6节“数据类型”中描述了默认值赋值   默认值”。另请参见第1.7.3.3节“无效的约束”   数据”。

因此,你无法获胜! ;-)开玩笑。要做的是接受MySQL表字段上的NOT NULL实际意味着在执行单行INSERT时我不会接受字段的NULL值,而不管SQL模式&#39;

所有这一切,手册中的以下内容也是如此:

  

对于没有显式DEFAULT的NOT NULL列的数据输入   如果INSERT或REPLACE语句不包含任何值,则该子句   列,或UPDATE语句将列设置为NULL,MySQL处理   根据当时生效的 SQL模式的列:

     

如果启用了严格SQL模式,则会发生事务错误   表和语句将回滚。对于非交易表,   发生错误,但如果第二行或后续行发生这种情况   多行语句,前面的行将是   插入

     

如果未启用严格模式,MySQL会将列设置为隐式   列数据类型的默认值

所以,请记住。在业务逻辑(对象)中设置默认值,让数据层从中获取方向。数据库默认值似乎是一个好主意,但如果它们不存在,你会想念它们吗?如果一棵树落在森林里......

答案 4 :(得分:0)

如果您从语句中删除列(名称和值),则将使用默认值。

一些相关建议:

  • 表中没有任何“非空”默认值,并且可空列没有非空默认值。让所有值都可以从应用程序中设置。
  • 不要将业务逻辑放在数据库端。

仅在确实需要时定义默认值,并且仅用于非空列。并在不再需要时删除默认值。 (它们与alter table运行一起派上用场,以设置新列的值,但随后立即运行一个新的(便宜!)alter来删除默认值)

上述“空”与类型有关: -数字列为0, -''用于varchar / varbinary列, -时间戳记为“ 1970-01-01 12:34:56”, -等等

这为应用程序节省了许多往返数据库的行程。如果创建的行是完全可预测的,则应用程序无需在创建后对其进行读取就可以了解其内容。 (假设:没有触发器,没有级联)

对于MySQL,我们仅对那些严格的规则作一些特定的例外:

  • 仅由数据库设置名为mysql_row_foo的列。示例:

      mysql_row_created_at  timestamp(6)  not null  default '1970-01-01 12:34:56.000000',
      mysql_row_updated_at  timestamp(6)  null      default null  on update current_timestamp,
    

  • 欢迎在非空列上使用唯一索引,以防止重复数据。例如,在看起来像(id ++,name)的表lookup.brand中的lookup.brand.name上。

mysql_row_foo列类似于列属性。例如,它们由数据同步工具使用。常规应用程序不读取它们,而是将其应用程序端时间戳存储为时期值。示例:

{
 valid_until_epoch   int unsigned  not null  default 0,
 last_seen_epoch_ms  bigint        not null  default 0,