前言
最近在写项目的时候发现自己的SQL基本功有些薄弱,遂上知乎查询MYSQL关键字,期望得到某些高赞答案的指点,于是乎发现了
https://www.zhihu.com/question/34840297/answer/272185020 这位老兄的建议的书单,根据他的建议首先拜读了《MYSQL必知必会》这本书,整体讲的很基础,页数也不多一共 253 页,适合基础比较薄弱的同学进行食用。然后循序渐进,阅读更深层次的书籍进行自我提升。这里记载了自己在阅读的过程中记录的一些关键内容,分享给大家。书本 PDF 可以在上面的知乎链接获取,或者点击 http://www.notedeep.com/note/38/page/282 前往老哥的深度笔记进行下载。
阅读心得
SQL语句和大小写
SQL语句不区分大小写,并且在 Windows 环境下,4.1.1版本之后(现在常用的都是 5.6/5.7/8.0+),MYSQL表名,字段名也是不区分大小写的,因此我们在命名的时候建议使用单个单词_单个单词的形式命名,如:mysql_crash_course user_role
这里附上阿里代码规范的一条强制要求:
【强制】表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
说明: MySQL 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库名、表名、字段名,都不允许出现任何大写字母,避免节外生枝。
不能部分使用DISTINCT
- DISTINCT 关键字应用于所有列而不仅是前置它的列。如果给出 SELECT DISTINCT vend_id,prod_price ,除非指定的两个列都不同,否则所有行都将被检索出来。
- DISTINCT 不会返回其后面跟的所有字段都相同的列。即两行中SELECT查询的任何一个字段都相同才会被去重。不能通过DISTINCT加括号 () 等方式来对单个字段进行去重。
比如项目中有这么一个需求:
需要分页查询绑了某收费标准的房屋,因为是房屋列表查询,我们默认相同ID的房屋只出现一次,错误的SQL如下:
房屋表
收费标准表
SELECT DISTINCT h.id, h.num, s.name
FROM house h
LEFT JOIN standard s
ON h.id = s.room_id
WHERE s.name = '物业费' OR s.name = '暖气费';
这条语句并不能返回想要的结果,即每套房屋只出现一次,因为不同的收费标准名称不一样,DISTINCT 不能对部分查询条件去重。可以看到房号为1-001的记录出现了两次。
不过其实按照需求的描述我这里仅查询房屋的信息,对于查询结果来说同一条记录的房屋信息肯定完全相同,因此 DISTINCT 在我的业务中满足要求。而有其他业务需要此关键字的时候,请大家慎重使用,切记不能部分使用该关键字
区分大小写和排序顺序
在对文本性的数据进行排序时,A与a相同吗?a位于B之前还是位于Z之后?
在创建字段时可以指定字符集,一般使用 utf8mb4, 此时可以选择相应的排序规则。
- utf8mb4_general_ci ci即大小写不敏感,排序时忽略大小写,A a 视作相同
- utf8mb4_bin / 带 cs 的即大小写敏感,相应的升序排列的话, A~Z 在前,小a~z在后
相应的,在设置大小写敏感后,查询条件 where cs = 'a' 只能查找到表中该字段为小写 a 的行。而不敏感,即ci时,A,a都可以被查询出来。
BETWEEN关键字的注意事项
在区间查询时,我们最关注的不应该是区间内的能否被匹配到,因为这是肯定的。而区间的边界能否被匹配才是我们应该注意的知识点,BETWEEN AND 关键字匹配区间时,包含左右边界条件。如下面的 SQL 执行结果如下:
SELECT prod_name, prod_price
FROM products p
WHERE p.`prod_price` BETWEEN 5.99 AND 10
NULL 与不匹配
在通过过滤选择出不具有特定值的行时,你可能希望返回具有 NULL 值的行。但是,不行。常见的错误会发生在is_xxx 字段上,我经常有这个毛病,
对于 is_delete 字段,我认为为 null 或者 0 都是未删除的房屋,所以当我使用
SELECT * FROM house WHERE is_delete = '0';
查询未删除的房屋时,我只能查到 id 为 3 的房屋,这显然与我的预期是不符的,解决办法是where后面加 or is_delete is null ,或者给 is_delete 列默认值 0;建议使用后者。
这里附上阿里代码手册的一条强制项目:
【强制】表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint(1 表示是,0 表示否)。说明:任何字段如果为非负数,必须是 unsigned 。
解释:tinyint 相当于 Java 中的 byte,取值范围 -128 ~ 127 ,用来表达是否长度已经足够,也可以用来表示人的年龄。而 unsigned 表示无符号的,对于确定为非负数的字段,使用 unsigned 可以将取值范围扩大一倍。
AND 和 OR 的计算次序
举个例子:假如需要列出价格为10美元(含)以上且由 1002 或 1003 制造的所有产品。下面的 SELECT 语句使用 AND 和 OR 操作符的组合建立了一个WHERE 子句:
SELECT *
FROM products
WHERE vend_id = 1002 OR vend_id = 1003 AND prod_price >= 10;
查询结果如下:
而被我用红框标注的行很显然不是我们需要的行,为什么会这样呢?原因在于计算的次序。SQL(像多数语言一样)在处理 OR 操作符前,优先处理 AND 操作符。当SQL看到上述 WHERE 子句时,它理解为由供应商 1003 制造的任何价格为10美元(含)以上的产品,或者由供应商 1002 制造的任何产品,而不管其价格如何。换句话说,由于 AND 在计算次序中优先级更高,操作符被错误地组合了。解决方法就是加 ();正确的 SQL 如下:
SELECT *
FROM products
WHERE (vend_id = 1002 OR vend_id = 1003) AND prod_price >= 10;
建议在任何时候使用具有 AND 和 OR 操作符的 WHERE 子句,都应该使用圆括号明确地分组操作符。不要过分依赖默认计算次序,即使它确实是你想要的东西也是如此。使用圆括号没有什么坏处,它能消除歧义。
UNION 组合查询
- 利用 UNION ,可给出多条SELECT 语句,将它们的结果组合成单个结果集。
- UNION 中的每个查询必须包含相同的列、表达式或聚集函数。
- 在使用UNION 时,重复的行被自动取消。这是 UNION 的默认行为,但是如果需要,可以改变它。事实上,如果想返回所有匹配行,可使用 UNION ALL 而不是 UNION 。
- 在用 UNION 组合查询时,只能使用一条 ORDER BY 子句,它必须出现在最后一条 SELECT 语句之后
INSERT语句总是使用列的列表
一般不要使用没有明确给出列的列表的 INSERT 语句。使用列的列表能使SQL代码继续发挥作用,即使表结构发生了变化。实际开发中有可能由于业务的需要,对表结构进行修改,添加/删除某一列。这时如果代码中使用的SQL语句是没有明确列表的插入语句就会报错。当然一般我们使用逆向工程生成的 insertSelective(POJO) 并不存在这个问题,因为它对应生成的 SQL 会为我们生成列的列表。
小心使用更新和删除语句
MySQL 没有撤销按钮,因此在使用 UPDATE / DELETE 时一定要加上 WHERE 条件,并且在执行更新/删除操作之前先进行 SELECT 操作,开启事务。在执行结束后核对影响的行数和 SELECT 查询出来的行数一致后再 COMMIT;
另外,使用 ALTER TABLE 要极为小心,应该在进行改动前做一个完整的备份(模式和数据的备份)。数据库表的更改不能撤销,如果增加了不需要的列,可能不能删除它们。类似地,如果删除了不应该删除的列,可能会丢失该列中的所有数据。
视图的规则和限制
- 与表一样,视图必须唯一命名(不能给视图取与别的视图或表相同的名字)。
- 对于可以创建的视图数目没有限制。
- 为了创建视图,必须具有足够的访问权限。这些限制通常由数据库管理人员授予。
- 视图可以嵌套,即可以利用从其他视图中检索数据的查询来构造一个视图。
- ORDER BY 可以用在视图中,但如果从该视图检索数据 SELECT 中也含有 ORDER BY ,那么该视图中的 ORDER BY 将被覆盖。
- 视图不能索引,也不能有关联的触发器或默认值。
- 视图可以和表一起使用。例如,编写一条联结表和视图的 SELECT语句。
其中视图不能索引这一点要格外注意,在我开发过程中遇到过这样一个视图:使用 UNION 连结了好几张表进行查询,对查询的结果使用 WHERE 条件再过滤,这里虽然对于被连接的表对于 WHERE 条件后的字段都建立了索引,但是使用 UNION 连结生成视图的临时表并不能拥有索引。因此查询的效率会很慢!所以建议在 UNION 查询中将 WHERE 条件放在每个 SELECT 语句中。
改善性能的一些建议
首先,MySQL(与所有DBMS一样)具有特定的硬件建议。在学习和研究MySQL时,使用任何旧的计算机作为服务器都可以。但
对用于生产的服务器来说,应该坚持遵循这些硬件建议。一般来说,关键的生产DBMS应该运行在自己的专用服务器上。
MySQL是用一系列的默认设置预先配置的,从这些设置开始通常是很好的。但过一段时间后你可能需要调整内存分配、缓冲区大小等。(为查看当前设置,可使用 SHOW VARIABLES; 和 SHOWSTATUS; )
MySQL一个多用户多线程的DBMS,换言之,它经常同时执行多个任务。如果这些任务中的某一个执行缓慢,则所有请求都会执
行缓慢。如果你遇到显著的性能不良,可使用 SHOW PROCESSLIST显示所有活动进程(以及它们的线程ID和执行时间)。你还可以用KILL 命令终结某个特定的进程(使用这个命令需要作为管理员登录)。总是有不止一种方法编写同一条 SELECT 语句。应该试验联结、并、子查询等,找出最佳的方法。
使用 EXPLAIN 语句让MySQL解释它将如何执行一条 SELECT 语句。
一般来说,存储过程执行得比一条一条地执行其中的各条MySQL语句快。但存储过程一般难以调试和扩展,并且没有移植性,因此阿里代码规约里面强制禁止使用存储过程
应该总是使用正确的数据类型。
决不要检索比需求还要多的数据。换言之,不要用 SELECT * (除非你真正需要每个列)。
-
有的操作(包括 INSERT )支持一个可选的 DELAYED 关键字,如果使用它,将把控制立即返回给调用程序,并且一旦有可能就实际执行该操作。
延迟插入,当插入和查询并发执行时,插入被放入等待队列中。直至所有查询执行完毕后执行插入。并且MYSQL会在收到插入请求后直接返回给客户端状态信息,既是INSERT语句还在队列中
在导入数据时,应该关闭自动提交。你可能还想删除索引(包括FULLTEXT 索引),然后在导入完成后再重建它们。
必须索引数据库表以改善数据检索的性能。确定索引什么不是一件微不足道的任务,需要分析使用的 SELECT 语句以找出重复的WHERE 和 ORDER BY 子句。如果一个简单的 WHERE 子句返回结果所花的时间太长,则可以断定其中使用的列(或几个列)就是需要索引的对象。
你的 SELECT 语句中有一系列复杂的 OR 条件吗?通过使用多条SELECT 语句和连接它们的 UNION 语句,你能看到极大的性能改进。
索引改善数据检索的性能,但损害数据插入、删除和更新的性能。如果你有一些表,它们收集数据且不经常被搜索,则在有必要之前不要索引它们。(索引可根据需要添加和删除。)
LIKE 很慢。一般来说,最好是使用 FULLTEXT 而不是 LIKE 。但是 MYSQL FULLTEXT 对汉字并不友好,如果需要使用全文索引,建议使用搜索引擎 ES,可以参考我的另一篇博客: https://www.cnblogs.com/keatsCoder/p/11341835.html
数据库是不断变化的实体。一组优化良好的表一会儿后可能就面目全非了。由于表的使用和内容的更改,理想的优化和配置也会改变。
最重要的规则就是,每条规则在某些条件下都会被打破。
实际开发过程中,我们需要根据业务的需要,开启慢查询日志,然后针对慢SQL,不断地进行 EXPLAIN 与修改SQL和索引,以求达到 ref 级别,至少达到 range 级别。这就需要强大的内功支持而不是每次都通过百度来解决,希望阅读此篇文章的你和我一起不断修炼。加油!
常用的函数
文本处理函数
日期和时间处理函数
数值处理函数
聚合函数
- 虽然 MAX() 一般用来找出最大的数值或日期值,但MySQL允许将它用来返回任意列中的最大值,包括返回文本列中的最大值。在用于文本数据时,如果数据按相应的列排序,则 MAX() 返回最后一行。MIN() 函数类似
- MAX() 函数忽略列值为 NULL 的行。MIN() 函数类似,SUM() 也是
附录
由于数中所附的附件内容(建表语句即数据插入语句)需要外网才能访问,为了方便大家使用。这里我已经下载下来附在了这篇博客里面。如果不需要这些语句,可以直接通过网页右边的目录跳跃到下一章进行浏览
建表语句
########################################
# MySQL Crash Course MYSQL必知必会建表语句
# http://www.forta.com/books/0672327120/
# 提供者博客园:后青春期的Keats 复制请注明出处
########################################
########################
# Create customers table
########################
CREATE TABLE customers
(
cust_id int NOT NULL AUTO_INCREMENT,
cust_name char(50) NOT NULL ,
cust_address char(50) NULL ,
cust_city char(50) NULL ,
cust_state char(5) NULL ,
cust_zip char(10) NULL ,
cust_country char(50) NULL ,
cust_contact char(50) NULL ,
cust_email char(255) NULL ,
PRIMARY KEY (cust_id)
) ENGINE=InnoDB;
#########################
# Create orderitems table
#########################
CREATE TABLE orderitems
(
order_num int NOT NULL ,
order_item int NOT NULL ,
prod_id char(10) NOT NULL ,
quantity int NOT NULL ,
item_price decimal(8,2) NOT NULL ,
PRIMARY KEY (order_num, order_item)
) ENGINE=InnoDB;
#####################
# Create orders table
#####################
CREATE TABLE orders
(
order_num int NOT NULL AUTO_INCREMENT,
order_date datetime NOT NULL ,
cust_id int NOT NULL ,
PRIMARY KEY (order_num)
) ENGINE=InnoDB;
#######################
# Create products table
#######################
CREATE TABLE products
(
prod_id char(10) NOT NULL,
vend_id int NOT NULL ,
prod_name char(255) NOT NULL ,
prod_price decimal(8,2) NOT NULL ,
prod_desc text NULL ,
PRIMARY KEY(prod_id)
) ENGINE=InnoDB;
######################
# Create vendors table
######################
CREATE TABLE vendors
(
vend_id int NOT NULL AUTO_INCREMENT,
vend_name char(50) NOT NULL ,
vend_address char(50) NULL ,
vend_city char(50) NULL ,
vend_state char(5) NULL ,
vend_zip char(10) NULL ,
vend_country char(50) NULL ,
PRIMARY KEY (vend_id)
) ENGINE=InnoDB;
###########################
# Create productnotes table
###########################
CREATE TABLE productnotes
(
note_id int NOT NULL AUTO_INCREMENT,
prod_id char(10) NOT NULL,
note_date datetime NOT NULL,
note_text text NULL ,
PRIMARY KEY(note_id),
FULLTEXT(note_text)
) ENGINE=MyISAM;
#####################
# Define foreign keys
#####################
ALTER TABLE orderitems ADD CONSTRAINT fk_orderitems_orders FOREIGN KEY (order_num) REFERENCES orders (order_num);
ALTER TABLE orderitems ADD CONSTRAINT fk_orderitems_products FOREIGN KEY (prod_id) REFERENCES products (prod_id);
ALTER TABLE orders ADD CONSTRAINT fk_orders_customers FOREIGN KEY (cust_id) REFERENCES customers (cust_id);
ALTER TABLE products ADD CONSTRAINT fk_products_vendors FOREIGN KEY (vend_id) REFERENCES vendors (vend_id);
数据语句
########################################
# MySQL Crash Course MYSQL必知必会数据语句
# http://www.forta.com/books/0672327120/
# 提供者博客园:后青春期的Keats 复制请注明出处
########################################
##########################
# Populate customers table
##########################
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES(10001, 'Coyote Inc.', '200 Maple Lane', 'Detroit', 'MI', '44444', 'USA', 'Y Lee', 'ylee@coyote.com');
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact)
VALUES(10002, 'Mouse House', '333 Fromage Lane', 'Columbus', 'OH', '43333', 'USA', 'Jerry Mouse');
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES(10003, 'Wascals', '1 Sunny Place', 'Muncie', 'IN', '42222', 'USA', 'Jim Jones', 'rabbit@wascally.com');
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES(10004, 'Yosemite Place', '829 Riverside Drive', 'Phoenix', 'AZ', '88888', 'USA', 'Y Sam', 'sam@yosemite.com');
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact)
VALUES(10005, 'E Fudd', '4545 53rd Street', 'Chicago', 'IL', '54545', 'USA', 'E Fudd');
########################
# Populate vendors table
########################
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1001,'Anvils R Us','123 Main Street','Southfield','MI','48075', 'USA');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1002,'LT Supplies','500 Park Street','Anytown','OH','44333', 'USA');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1003,'ACME','555 High Street','Los Angeles','CA','90046', 'USA');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1004,'Furball Inc.','1000 5th Avenue','New York','NY','11111', 'USA');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1005,'Jet Set','42 Galaxy Road','London', NULL,'N16 6PS', 'England');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1006,'Jouets Et Ours','1 Rue Amusement','Paris', NULL,'45678', 'France');
#########################
# Populate products table
#########################
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('ANV01', 1001, '.5 ton anvil', 5.99, '.5 ton anvil, black, complete with handy hook');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('ANV02', 1001, '1 ton anvil', 9.99, '1 ton anvil, black, complete with handy hook and carrying case');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('ANV03', 1001, '2 ton anvil', 14.99, '2 ton anvil, black, complete with handy hook and carrying case');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('OL1', 1002, 'Oil can', 8.99, 'Oil can, red');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('FU1', 1002, 'Fuses', 3.42, '1 dozen, extra long');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('SLING', 1003, 'Sling', 4.49, 'Sling, one size fits all');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('TNT1', 1003, 'TNT (1 stick)', 2.50, 'TNT, red, single stick');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('TNT2', 1003, 'TNT (5 sticks)', 10, 'TNT, red, pack of 10 sticks');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('FB', 1003, 'Bird seed', 10, 'Large bag (suitable for road runners)');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('FC', 1003, 'Carrots', 2.50, 'Carrots (rabbit hunting season only)');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('SAFE', 1003, 'Safe', 50, 'Safe with combination lock');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('DTNTR', 1003, 'Detonator', 13, 'Detonator (plunger powered), fuses not included');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('JP1000', 1005, 'JetPack 1000', 35, 'JetPack 1000, intended for single use');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('JP2000', 1005, 'JetPack 2000', 55, 'JetPack 2000, multi-use');
#######################
# Populate orders table
#######################
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20005, '2005-09-01', 10001);
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20006, '2005-09-12', 10003);
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20007, '2005-09-30', 10004);
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20008, '2005-10-03', 10005);
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20009, '2005-10-08', 10001);
###########################
# Populate orderitems table
###########################
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 1, 'ANV01', 10, 5.99);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 2, 'ANV02', 3, 9.99);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 3, 'TNT2', 5, 10);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 4, 'FB', 1, 10);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20006, 1, 'JP2000', 1, 55);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20007, 1, 'TNT2', 100, 10);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20008, 1, 'FC', 50, 2.50);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 1, 'FB', 1, 10);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 2, 'OL1', 1, 8.99);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 3, 'SLING', 1, 4.49);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 4, 'ANV03', 1, 14.99);
#############################
# Populate productnotes table
#############################
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(101, 'TNT2', '2005-08-17',
'Customer complaint:
Sticks not individually wrapped, too easy to mistakenly detonate all at once.
Recommend individual wrapping.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(102, 'OL1', '2005-08-18',
'Can shipped full, refills not available.
Need to order new can if refill needed.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(103, 'SAFE', '2005-08-18',
'Safe is combination locked, combination not provided with safe.
This is rarely a problem as safes are typically blown up or dropped by customers.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(104, 'FC', '2005-08-19',
'Quantity varies, sold by the sack load.
All guaranteed to be bright and orange, and suitable for use as rabbit bait.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(105, 'TNT2', '2005-08-20',
'Included fuses are short and have been known to detonate too quickly for some customers.
Longer fuses are available (item FU1) and should be recommended.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(106, 'TNT2', '2005-08-22',
'Matches not included, recommend purchase of matches or detonator (item DTNTR).'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(107, 'SAFE', '2005-08-23',
'Please note that no returns will be accepted if safe opened using explosives.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(108, 'ANV01', '2005-08-25',
'Multiple customer returns, anvils failing to drop fast enough or falling backwards on purchaser. Recommend that customer considers using heavier anvils.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(109, 'ANV03', '2005-09-01',
'Item is extremely heavy. Designed for dropping, not recommended for use with slings, ropes, pulleys, or tightropes.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(110, 'FC', '2005-09-01',
'Customer complaint: rabbit has been able to detect trap, food apparently less effective now.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(111, 'SLING', '2005-09-02',
'Shipped unassembled, requires common tools (including oversized hammer).'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(112, 'SAFE', '2005-09-02',
'Customer complaint:
Circular hole in safe floor can apparently be easily cut with handsaw.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(113, 'ANV01', '2005-09-05',
'Customer complaint:
Not heavy enough to generate flying stars around head of victim. If being purchased for dropping, recommend ANV02 or ANV03 instead.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(114, 'SAFE', '2005-09-07',
'Call from individual trapped in safe plummeting to the ground, suggests an escape hatch be added.
Comment forwarded to vendor.'
);