我遇到了由于查询错误导致数据丢失的问题 数据已恢复,但现在我想了解问题。
我在SQL Server 2014上遇到了问题,但我在SQL Server 2000和PostgreSQL上复制了它。具体来说,有一个删除。在以下场景中,我使用SELECT。
为sql server 2014创建表:
CREATE TABLE [dbo].[tmp_color](
[color_id] [int] NOT NULL,
[color_name] [nvarchar](50) NOT NULL,
[color_cat] [int] NOT NULL,
CONSTRAINT [PK_tmp_color] PRIMARY KEY CLUSTERED (
[color_id] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF
, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE TABLE [dbo].[tmp_color_cat](
[catid] [int] NOT NULL,
[catname] [nvarchar](50) NOT NULL,
CONSTRAINT [PK_tmp_color_cat] PRIMARY KEY CLUSTERED (
[catid] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF
, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
和Postgres版本:
CREATE TABLE tmp_color (
color_id integer NOT NULL,
color_name text,
color_cat integer,
CONSTRAINT tmp_color_pkey PRIMARY KEY (color_id)
);
CREATE TABLE tmp_color_cat (
catid integer NOT NULL,
catname text,
CONSTRAINT tmp_color_cat_pkey PRIMARY KEY (catid)
);
数据填充(适用于两个RDBMS):
INSERT INTO tmp_color_cat (catid, catname) VALUES (1, 'magic color');
INSERT INTO tmp_color_cat (catid, catname) VALUES (2, 'normal color');
INSERT INTO tmp_color (color_id, color_name, color_cat) VALUES (1, 'red', 1);
INSERT INTO tmp_color (color_id, color_name, color_cat) VALUES (2, 'green', 2);
INSERT INTO tmp_color (color_id, color_name, color_cat) VALUES (3, 'black', 1);
以下SELECT 错误 :
SELECT color_cat
FROM tmp_color_cat;
color_cat
中不存在tmp_color_cat
但是,你在子查询中采取这一点的那一刻:
SELECT * FROM tmp_color
WHERE color_cat IN(
SELECT color_cat
FROM tmp_color_cat
WHERE catname = 'magic color'
);
从tmp_color
返回每一条记录
脚本中的逻辑错误是显而易见的:开发人员写错了列来识别类别。如果要删除记录而不是选择它们,则将删除整个表。不好。
这是理想的行为吗?或者它是子查询设计的结果?
通过观察SQL Server的执行计划,逻辑操作是左半连接。
我找到了几个帖子,一个for PostgreSQL和一个for SQL Server。是否有任何好的文档可以发送给开发人员小组解释为什么这不是错误?
我怎样才能避免这种问题?我的第一个想法是使用别名。别名很好。
答案 0 :(得分:1)
这是SQL Server的已知行为。使用别名将阻止此
SELECT * FROM tmp_color
WHERE color_cat IN(
SELECT A.color_cat
FROM tmp_color_cat As A
WHERE A.catname = 'magic color'
);
以上查询将引发错误
Msg 207, Level 16, State 1, Line 3
Invalid column name 'color_cat'.
答案 1 :(得分:1)
您的案例中的别名可以解决问题,因为它的编写方式只是引用外部查询tmp_color
中的表,从而返回所有内容。
所以你会按照你的建议改写这个:
SELECT * FROM tmp_color t1
WHERE t1.color_cat IN(
SELECT t2.color_cat
FROM tmp_color_cat t2
WHERE t2.catname = 'magic color'
);
这表明您的逻辑中存在错误:
列名无效
另一种安全的写作方式是使用JOIN
。请注意,由于JOIN
规范没有任何冲突列,因此我在下面留下了别名。如果表中的任何列名相同,那么您将得到Ambiguous column
错误。最佳做法是始终使用别名来保持清晰。
SELECT *
FROM #tmp_color
INNER JOIN #tmp_color_cat ON color_cat = catid
WHERE catname = 'magic color'
equivelant DELETE
将是:
DELETE t1
FROM #tmp_color t1
INNER JOIN #tmp_color_cat ON color_cat = catid
Where catname = 'magic color'
完整的可运行样本:
CREATE TABLE #tmp_color
(
color_id INT ,
color_name NVARCHAR(50) ,
color_cat INT
)
CREATE TABLE #tmp_color_cat
(
catid INT ,
catname NVARCHAR(50) NOT NULL,
)
INSERT INTO #tmp_color_cat (catid, catname) VALUES (1, 'magic color');
INSERT INTO #tmp_color_cat (catid, catname) VALUES (2, 'normal color');
INSERT INTO #tmp_color (color_id, color_name, color_cat) VALUES (1, 'red', 1);
INSERT INTO #tmp_color (color_id, color_name, color_cat) VALUES (2, 'green', 2);
INSERT INTO #tmp_color (color_id, color_name, color_cat) VALUES (3, 'black', 1);
DELETE t1
FROM #tmp_color t1
INNER JOIN #tmp_color_cat ON color_cat = catid
Where catname = 'magic color'
SELECT *
FROM #tmp_color
DROP TABLE #tmp_color
DROP TABLE #tmp_color_cat
生成剩余的行:
color_id color_name color_cat
2 green 2
答案 2 :(得分:1)
正如在注释和其他答案中已经阐明的那样,子查询中列名的范围包括外部查询的所有可见列。首先将非限定名称解析为内部查询,然后向外扩展搜索 使用表别名并使用别名对列名进行表格限定以消除任何歧义 - 正如您自己暗示的那样。
这是example in the Postgres manual with a definitive statement explaining the scope:
SELECT ... FROM fdt WHERE c1 IN (SELECT c3 FROM t2 WHERE c2 = fdt.c1 + 10)
[...]
仅当
c1
也是一个名称时,才需要将c1
限定为fdt.c1 子查询的派生输入表中的列。但是排位赛 列名称即使在不需要时也会增加清晰度。这个例子 显示外部查询的列命名范围如何扩展到其内部查询。
大胆强调我的。
除了:
在本手册的同一章节的示例列表中还有一个EXISTS
半连接的示例。这通常是优越的选择到WHERE x IN (subquery)
。但在这种特殊情况下,你也不需要。见下文。
一个示例:
这次灾难是因为列名混淆而发生的。表定义中的清晰一致的命名约定将大大有助于减少发生的可能性。对于 任何 RDBMS都是如此。只要有必要使它们清除,但尽可能短。无论您的政策是什么,都要保持一致。
对于Postgres,我建议:
CREATE TABLE colorcat (
colorcat_id integer NOT NULL PRIMARY KEY,
colorcat text UNIQUE NOT NULL
);
CREATE TABLE color (
color_id integer NOT NULL PRIMARY KEY,
color text NOT NULL,
colorcat_id integer REFERENCES colorcat -- assuming an FK
);
您已经拥有合法的小写不带引号的标识符。那是好。
使用一致政策。不一致的政策比糟糕的政策更糟糕。不是color_name
(带下划线)与catname
。
我很少使用' name'在标识符中。它没有添加信息,只是让它们更长。所有标识符都是名称。您选择了cat_name
,离开了color
,其中实际上包含了信息,并添加了name
,其中没有。如果您有其他"类别"在您的数据库中,这是常见的,您将有多个cat_name
,它们容易在更大的查询中发生冲突。我宁愿使用colorcat
(就像表名一样)。
使名称指明列中的内容。对于颜色类别的ID,colorcat_id
是一个不错的选择。 id
不具有描述性,colorcat
会产生误导。
FK列colorcat_id
可以与引用列具有相同的名称。两者都完全相同的内容。还允许在连接中使用USING
的短语法。
相关答案以及更多细节:
以我所谓的设计为基础:
SELECT c.*
FROM colorcat cc
JOIN color c USING (colorcat_id)
WHERE cc.colorcat = 'magic color';
这假设colorcat
和color
之间存在1:n关系(您没有指定,但似乎很可能)。
鲜为人知(因为其他RDBMS(如SQL Server )的语法不同),您也可以join in additional tables in a DELETE
:
DELETE FROM color c
USING colorcat cc
WHERE cc.colorcat = 'magic color'
AND cc.colorcat_id = c.colorcat_id;
答案 3 :(得分:0)
服务器试图找出在SQL语句范围内的任何表/视图/子查询中存在的列名。
事实上,最好使用别名来避免这样的错误和误解:
SELECT * FROM tmp_color tc
WHERE color_cat IN(
SELECT tcc.catid
FROM tmp_color_cat tcc
WHERE catname = 'magic color'
);
所以,如果你尝试使用这样的结构:
SELECT * FROM tmp_color tc
WHERE color_cat IN(
SELECT tcc.color_cat
FROM tmp_color_cat tcc
WHERE catname = 'magic color'
);
您将收到错误消息:
Msg 207,Level 16,State 1,Line 3 列名称'color_cat'无效。