我有一张桌子EMPLOYEE(EMP_ID, EMP_NAME, DESC, SALARY)
,我想找回所有可以做魔术的员工。能够执行魔术的能力被分类为一种才能,如果每个雇员都存储在每个雇员的每个DESC
条目的末尾,则该才华被分类。
如果某个雇员有才能,那么该雇员的DESC
将以'Talent: __talent_here__'
结尾。例如,乔擅长玩杂耍,那么他的描述将类似于'Joe Doe, works here since 2011. Talent: juggling'
。我可以假设在每个描述中只有一个字符串文字'Talent: '
的实例,人才总是存储在DESC
的末尾,并不是所有的员工都有人才。我还想按姓名对检索到的员工进行排序,而只希望排序的前50个值
这是我目前正在使用的SELECT
语句。
SELECT * FROM (
SELECT E.EMP_NAME FROM EMP E
WHERE INSTR(E.DESC, 'Talent: ') > 0
AND INSTR(SUBSTR(E.DESCRIPTION, INSTR(E.DESC, 'Talent: ')), 'magic') > 0
ORDER BY E.EMP_NAME ASC
) WHERE ROWNUM <= 50
/
此SELECT
语句工作正常。现在,我想在查询中创建索引以缩短运行时间:
CREATE INDEX MAGICIANS ON EMP(
INSTR(DESCRIPTION, 'Talent: '),
INSTR(SUBSTR(DESCRIPTION, INSTR(DESCRIPTION, 'Talent: ')), 'magic'))
/
但是该索引似乎无效。我的表中大约有9000个条目,在创建索引之前,我的select语句的运行时间为00.50秒。创建索引后,它变为约00.49秒。我一直在尝试索引,有时创建索引后的运行时甚至比以前稍微差一点。 (从00.50到00.52秒)
有人知道为什么会这样吗?
谢谢大家。
答案 0 :(得分:5)
索引不是魔术(哦,天哪!)。建立索引不能保证更快的检索时间。要了解为什么索引不能提供更快的结果,您需要了解索引的工作原理。
您的索引按照单词'Talent'的偏移量进行排列。所有没有才能的员工的零偏移,其他所有员工的零偏移。对于他们来说,偏移量是DESCRIPTION
值长度的函数。至关重要的是,DESCRIPTION
的长度与员工是否可以做魔术之间没有任何关系。
因此,要查找有能力的员工,数据库必须访问talent
偏移量不为零的所有索引条目,然后读取magic
偏移量不为零的所有索引条目,然后读取这些条目的表记录。魔术师的索引条目将分散在整个索引中,而这些记录将分散在整个表中。几乎可以肯定,进行全表扫描并从表中提取记录会更快。
另一件事是,即使您的索引适用于魔术师,对于找到魔术师也没有用。
从根本上讲,这是数据模型的失败:将TALENT
嵌入DESCRIPTION
会中断First Normal Form。为什么可以解决该问题-使用Virtual Columns(或建议使用实例化视图,例如@TimBiegeisen)-正确的解决方案是将TALENT
建模为查找表并让EMPLOYEE
引用它通过外键(从而消除了员工练习“魔术”或“马吉克”的可能性)。这样的查找表还可以让您拥有多才多艺的员工:您可以使用交集表EMPLOYEE_TALENT
来建模既是魔术师又是魔术师的员工,以加入EMPLOYEE
和TALENT
表。
答案 1 :(得分:3)
索引方法的问题是B-Tree索引是围绕列中字符串的 start 建立的,而不是围绕埋在中间某个位置的子字符串周围的。为了使用索引,理想情况下,最好有一个单独的善意列,其中包含员工的才能。这里的一种方法是创建一个包含这样的人才专栏的物化视图,然后对其进行索引:
CREATE MATERIALIZED VIEW mv_name
REFRESH ON DEMAND
AS
SELECT e.*,
CASE WHEN INSTR(DESCRIPTION, 'Talent: ') > 0
THEN SUBSTR(DESCRIPTION, INSTR(DESCRIPTION, 'Talent: ') + 8)
ELSE '' END AS Talent
FROM EMPLOYEE e;
CREATE INDEX idx ON mv_name (Talent);
现在,以下查询应相当快:
SELECT *
FROM mv_name
WHERE Talent = 'magic';
答案 2 :(得分:0)
APC正确。数据模型有缺陷。话虽如此,这是两个解决方案。
解决方案1 。创建基于功能的索引,以提取实际人才。这样也可以搜索杂耍演员和小丑。这种方法有两个缺点。为了使Oracle使用该索引,您必须在查询中重复该表达式以完全匹配索引定义。第二个缺点是,除非再次在选择列表中重复该表达式,否则您将无法轻松提取实际才能。
create index employee_ix_talent
on employee(regexp_replace(descr, '^(.*)Talent: (.*)$', '\2'));
select emp_id, emp_name, salary, regexp_replace(descr, '^(.*)Talent: (.*)$', '\2') as talent
from employee e
where regexp_replace(descr, '^(.*)Talent: (.*)$', '\2') = 'magic';
解决方案2(首选)。将虚拟列添加到表中并对该列建立索引。这消除了第一个解决方案的两个缺点。现在,您可以选择“人才”列并对其进行搜索,最重要的是:现在,您的代码已屏蔽了表达式中的更改。
alter table employee
add talent generated always as(
regexp_replace(descr, '^(.*)Talent: (.*)$', '\2')
);
create index employee_ix_talent on employee(talent);
select emp_id, emp_name, talent, salary
from employee e
where talent = 'magic';