经典SQL语句
获取GUID: SELECT NEWID()
说明:复制表(只复制结构,源表名:a 新表名:b)
SQL: select * into b from a where 1<>1
说明:拷贝表(拷贝数据,源表名:a 目标表名:b)
insert into b(a, b, c) select d,e,f from b;
把所有姓名相同的只取出一个
select a.name from table_name a where a.id in
(select b.id from table_name b where a.id<>b.id)
同理删除荣誉数据
delete from table_name where table_name.id in
(select b.id from table_name b where table_name.id<>b.id)
select a.title,a.username,b.adddate from table a,(select max(adddate) adddate from table where table.title=a.title) b
随机取出10条数据
select top 10 * from tablename order by newid()
Microsoft SQL Server是如何加密口令的?未公开的加密函数?
如果对MSSQL的用户信息有兴趣的,可能会发现master.dbo.sysxlogins里面存放着用户的口令,可是呢,password字段如果不是null就是一堆看不懂的binary,这个口令是怎么加密的呢?
其实只要仔细看看master.dbo.sp_addlogin就知道了,MSSQL的sp都可以看到代码,真是不错。
让我们来看看它是怎么做的,注意这一行select @passwd = pwdencrypt(@passwd),这个时后@passwd就被加密了,让我们也来试一下
DECLARE @ClearPWD varchar(255)
DECLARE @EncryptedPWD varbinary(255)
SELECT @ClearPWD = 'test'
SELECT @EncryptedPWD = CONVERT(varbinary(255), pwdencrypt(@ClearPWD))
SELECT @EncryptedPWD
看上去不错,确实被加密了,可是我怎么还原呢?
呵呵,这就没戏了,口令加密都是单向的,用加密后的密文来比较就可以了。
继续看看其它用户相关的sp,可以发现master.dbo.sp_password里面有口令比较的内容。
pwdcompare(@old, password, (CASE WHEN xstatus&2048 = 2048 THEN 1 ELSE 0 END))
不用去理会xstatus,这是一个状态掩码,一般我们用的时候就直接用0就可以了
DECLARE @ClearPWD varchar(255)
DECLARE @EncryptedPWD varbinary(255)
SELECT @ClearPWD = 'test'
SELECT @EncryptedPWD = CONVERT(varbinary(255), pwdencrypt(@ClearPWD))
SELECT pwdcompare(@ClearPWD, @EncryptedPWD, 0)
SELECT pwdcompare('ErrorPassword', @EncryptedPWD, 0)
这样我们就可以使用这两个函数来加密自己的密码了,怎么样,还不错吧?
1.如果 T 表还有一字段 F0 数据类型为自动增量整型(唯一,不会重复),
而且 T 表中含有除 F0 字段外,请删除其它字段完全相同的重复多余的脏记录数据:
delete from t where f0 in(select max(f0) from t group by f1,f2,f3,f4,f5,f6,f7 having count(f0)>1)
上面这个sql有问题
正确的如下
DELETE Legal_Dispute_Lawyer WHERE Lawyer_Record_No IN(SELECT Lawyer_Record_No FROM Legal_Dispute_Lawyer LDL WHERE Lawyer_Record_No(SELECT TOP 1 Lawyer_Record_No FROM Legal_Dispute_Lawyer WHERE LD=LDL.LD AND Name=LDL.Name AND Email=LDL.Email AND Phone_No=LDL.Phone_No AND Fax_No=LDL.Fax_No))
消除Legal_Dispute_Lawyer 表中除Lawyer_Record_No(自增字段)外其余数据完全相同的记录.
表结构如下
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[Legal_Dispute_Lawyer]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[Legal_Dispute_Lawyer]
GO
CREATE TABLE [dbo].[Legal_Dispute_Lawyer] (
[Lawyer_Record_No] [int] IDENTITY (1, 1) NOT NULL ,
[LD] [int] NOT NULL ,
[Name] [int] NOT NULL ,
[Phone_No] [varchar] (255) COLLATE Chinese_PRC_CI_AS NULL ,
[Fax_No] [varchar] (255) COLLATE Chinese_PRC_CI_AS NULL ,
[Email] [varchar] (255) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]
GO
分页语句
select top 100 * from 表名 where id not in (select top page_no*100 * from 表名)
page_no是程序中的变量
从数据库中去一年的各单位电话费统计(电话费定额贺电化肥清单两个表来源)
SELECT a.userper, a.tel, a.standfee, TO_CHAR(a.telfeedate, 'yyyy') AS telyear,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '01', a.factration)) AS JAN,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '02', a.factration)) AS FRI,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '03', a.factration)) AS MAR,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '04', a.factration)) AS APR,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '05', a.factration)) AS MAY,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '06', a.factration)) AS JUE,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '07', a.factration)) AS JUL,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '08', a.factration)) AS AGU,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '09', a.factration)) AS SEP,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '10', a.factration)) AS OCT,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '11', a.factration)) AS NOV,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '12', a.factration)) AS DEC
FROM (SELECT a.userper, a.tel, a.standfee, b.telfeedate, b.factration
FROM TELFEESTAND a, TELFEE b
WHERE a.tel = b.telfax) a
GROUP BY a.userper, a.tel, a.standfee, TO_CHAR(a.telfeedate, 'yyyy')
个人认为,偶写了这么久的SQL,只说语法很无聊,语句的涵义与灵活的组合很重要,下面这个是写的一个药品管理的存储过程的一句,很经典:
UPDATE 药房库存 SET 库存数量=库存数量-B.用量 FROM [药房库存] A ,
( Select sum(用量) as 用量,药品价码 FROM 处方项
WHERE 处方号=@RecipeNo AND 处方序号=@RecipeXNo
GROUP BY 药品价码
) B WHERE A.药品价码=B.药品价码 AND A.库房名称=@DepotName
这是一个参照B表将A表中库存一一修改,而B表中存在一个求和,A表也与B表关联,同时要满足A表条件。
两台SQL服务器上的一个数据表同步!
drop procedure dbSync
GO
/* 数据同步 */
CREATE PROCEDURE dbSync
@sTabelName varchar(255), --要同步的表名
@sKeyField varchar(255), --关键字段
@sServer varchar(255), --服务器名称或IP
@sUserName varchar(255), --登录到服务器的用户名,一般为sa
@sPassWord varchar(32) --用户登录到服务器的密码
AS
/*删除临时表*/
if exists (select * from dbo.sysobjects where id = object_id(N'tempTbl') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table tempTbl
declare @sql VARCHAR(2000)
/*把表@sTabelName[远程]的数据拷贝到临时表*/
set @sql='select * into tempTbl from '
set @sql=@sql + ' OPENDATASOURCE( '
set @sql=@sql + '''SQLOLEDB.1'','
set @sql=@sql + '''Persist Security Info=True;User ID=' + @sUserName
set @sql=@sql + ';Password=' + @sPassWord
set @sql=@sql + ';Initial Catalog=toys;Data Source=' + @sServer
set @sql=@sql + ''').toys.dbo.'+@sTabelName
EXEC(@sql)
/* 把@sTabelName[本地]中的@sTabelName[远程]表中没有的数据插入到临时表中*/
set @sql='insert into tempTbl select * from '+@sTabelName+' where ['+@sKeyField+'] not in (select ['+@sKeyField+'] from tempTbl)'
EXEC(@sql)
/*清空表@sTabelName[本地]*/
set @sql='truncate table '+@sTabelName
EXEC(@sql)
--取得列名
declare @MySql VARCHAR(2000)
set @MySql=''
declare @title varchar(20)
DECLARE titles_cursor CURSOR FOR
SELECT name from syscolumns where id=object_id(@sTabelName)
OPEN titles_cursor
FETCH NEXT FROM titles_cursor INTO @title
WHILE @@FETCH_STATUS = 0
BEGIN
if @title<>'id'
begin
if @MySql = ''
set @MySql = @MySql + @title
else
set @MySql = @MySql + ',' + @title
end
FETCH NEXT FROM titles_cursor INTO @title
END
CLOSE titles_cursor
DEALLOCATE titles_cursor
--取列名结束
/*把临时表的内容插入到表@sTabelName[本地]*/
set @sql='insert into '+@sTabelName+' select '+@MySql+' from tempTbl'
EXEC(@sql)
/*删除临时表*/
if exists (select * from dbo.sysobjects where id = object_id(N'tempTbl') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table tempTbl
GO
select a,b,c from a where a IN (select d from b )
这是一个可以优化的sql语句,给a表中的a和b表中的d使用索引,然后改为
select a,b,c from a where exists (select * from b where b.d=a.a)
总结说一点:
1、要合理使用索引
索引是数据库一个重要的构成部分,很多人都会忽略它,其实索引的根本目的就是
为了提高查询效率。
使用原则如下:
在经常进行连接,但是没有指定为外键的列上建立索引,而不经常连接的字段则
由优化器自动生成索引。
在频繁进行排序或分组(即进行group by或order by操作)的列上建立索引。
在条件表达式中经常用到的不同值较多的列上建立检索,在不同值少的列上不要
建立索引。比如在雇员表的“性别”列上只有“男”与“女”两个不同值,因此就
无必要建立索引。如果建立索引不但不会提高查询效率,反而会严重降低更新速度
。
如果待排序的列有多个,可以在这些列上建立复合索引(compound index)。
在写sql语句时就必须注意有些写法是会使得数据库无法使用索引的,比如IS NULL
IS NOT NULL,IN ,NOT IN 等。。。
2.避免或简化排序
应当简化或避免对大型表进行重复的排序。当能够利用索引自动以适当的次序产生
输出时,优化器就避免了排序的步骤。以下是一些影响因素:
●索引中不包括一个或几个待排序的列;
●group by或order by子句中列的次序与索引的次序不一样;
●排序的列来自不同的表。
为了避免不必要的排序,就要正确地增建索引,合理地合并数据库表(尽管有时可
能影响表的规范化,但相对于效率的提高是值得的)。如果排序不可避免,那么应
当试图简化它,如缩小排序的列的范围等。
3.消除对大型表行数据的顺序存取
在嵌套查询中,对表的顺序存取对查询效率可能产生致命的影响。比如采用顺序存
取策略,一个嵌套3层的查询,如果每层都查询1000行,那么这个查询就要查询10
亿行数据。避免这种情况的主要方法就是对连接的列进行索引。例如,两个表:学
生表(学号、姓名、年龄……)和选课表(学号、课程号、成绩)。如果两个表要
做连接,就要在“学号”这个连接字段上建立索引。
还可以使用并集来避免顺序存取。尽管在所有的检查列上都有索引,但某些形式的
where子句强迫优化器使用顺序存取。下面的查询将强迫对orders表执行顺序操作
:
SELECT * FROM orders WHERE (customer_num=104 AND order_num>1001) OR
order_num=1008
虽然在customer_num和order_num上建有索引,但是在上面的语句中优化器还是使
用顺序存取路径扫描整个表。因为这个语句要检索的是分离的行的集合,所以应该
改为如下语句:
SELECT * FROM orders WHERE customer_num=104 AND order_num>1001
UNION
SELECT * FROM orders WHERE order_num=1008
这样就能利用索引路径处理查询。
4.避免相关子查询
一个列的标签同时在主查询和where子句中的查询中出现,那么很可能当主查询中
的列值改变之后,子查询必须重新查询一次。查询嵌套层次越多,效率越低,因此
应当尽量避免子查询。如果子查询不可避免,那么要在子查询中过滤掉尽可能多的
行。
5.避免困难的正规表达式
MATCHES和LIKE关键字支持通配符匹配,技术上叫正规表达式。但这种匹配特别耗
费时间。例如:SELECT * FROM customer WHERE zipcode LIKE “98_ _ _”
即使在zipcode字段上建立了索引,在这种情况下也还是采用顺序扫描的方式。如
果把语句改为SELECT * FROM customer WHERE zipcode >“98000”,在执行查询
时就会利用索引来查询,显然会大大提高速度。
另外,还要避免非开始的子串。例如语句:SELECT * FROM customer WHERE
zipcode[2,3] >“80”,在where子句中采用了非开始子串,因而这个语句也不会
使用索引。
6.使用临时表加速查询
把表的一个子集进行排序并创建临时表,有时能加速查询。它有助于避免多重排序
操作,而且在其他方面还能简化优化器的工作。例如:
SELECT cust.name,rcvbles.balance,……other columns
FROM cust,rcvbles
WHERE cust.customer_id = rcvlbes.customer_id
AND rcvblls.balance>0
AND cust.postcode>“98000”
ORDER BY cust.name
如果这个查询要被执行多次而不止一次,可以把所有未付款的客户找出来放在一个
临时文件中,并按客户的名字进行排序:
SELECT cust.name,rcvbles.balance,……other columns
FROM cust,rcvbles
WHERE cust.customer_id = rcvlbes.customer_id
AND rcvblls.balance>0
ORDER BY cust.name
INTO TEMP cust_with_balance
然后以下面的方式在临时表中查询:
SELECT * FROM cust_with_balance
WHERE postcode>“98000”
临时表中的行要比主表中的行少,而且物理顺序就是所要求的顺序,减少了磁盘
I/O,所以查询工作量可以得到大幅减少。
注意:临时表创建后不会反映主表的修改。在主表中数据频繁修改的情况下,注意
不要丢失数据。
7.用排序来取代非顺序存取
非顺序磁盘存取是最慢的操作,表现在磁盘存取臂的来回移动。SQL语句隐藏了这
一情况,使得我们在写应用程序时很容易写出要求存取大量非顺序页的查询。
有些时候,用数据库的排序能力来替代非顺序的存取能改进查询。
说明:复制表(只复制结构,源表名:a 新表名:b)
SQL: select * into b from a where 1<>1
说明:拷贝表(拷贝数据,源表名:a 目标表名:b)
insert into b(a, b, c) select d,e,f from b;
把所有姓名相同的只取出一个
select a.name from table_name a where a.id in
(select b.id from table_name b where a.id<>b.id)
同理删除荣誉数据
delete from table_name where table_name.id in
(select b.id from table_name b where table_name.id<>b.id)
select a.title,a.username,b.adddate from table a,(select max(adddate) adddate from table where table.title=a.title) b
随机取出10条数据
select top 10 * from tablename order by newid()
Microsoft SQL Server是如何加密口令的?未公开的加密函数?
如果对MSSQL的用户信息有兴趣的,可能会发现master.dbo.sysxlogins里面存放着用户的口令,可是呢,password字段如果不是null就是一堆看不懂的binary,这个口令是怎么加密的呢?
其实只要仔细看看master.dbo.sp_addlogin就知道了,MSSQL的sp都可以看到代码,真是不错。
让我们来看看它是怎么做的,注意这一行select @passwd = pwdencrypt(@passwd),这个时后@passwd就被加密了,让我们也来试一下
DECLARE @ClearPWD varchar(255)
DECLARE @EncryptedPWD varbinary(255)
SELECT @ClearPWD = 'test'
SELECT @EncryptedPWD = CONVERT(varbinary(255), pwdencrypt(@ClearPWD))
SELECT @EncryptedPWD
看上去不错,确实被加密了,可是我怎么还原呢?
呵呵,这就没戏了,口令加密都是单向的,用加密后的密文来比较就可以了。
继续看看其它用户相关的sp,可以发现master.dbo.sp_password里面有口令比较的内容。
pwdcompare(@old, password, (CASE WHEN xstatus&2048 = 2048 THEN 1 ELSE 0 END))
不用去理会xstatus,这是一个状态掩码,一般我们用的时候就直接用0就可以了
DECLARE @ClearPWD varchar(255)
DECLARE @EncryptedPWD varbinary(255)
SELECT @ClearPWD = 'test'
SELECT @EncryptedPWD = CONVERT(varbinary(255), pwdencrypt(@ClearPWD))
SELECT pwdcompare(@ClearPWD, @EncryptedPWD, 0)
SELECT pwdcompare('ErrorPassword', @EncryptedPWD, 0)
这样我们就可以使用这两个函数来加密自己的密码了,怎么样,还不错吧?
1.如果 T 表还有一字段 F0 数据类型为自动增量整型(唯一,不会重复),
而且 T 表中含有除 F0 字段外,请删除其它字段完全相同的重复多余的脏记录数据:
delete from t where f0 in(select max(f0) from t group by f1,f2,f3,f4,f5,f6,f7 having count(f0)>1)
上面这个sql有问题
正确的如下
DELETE Legal_Dispute_Lawyer WHERE Lawyer_Record_No IN(SELECT Lawyer_Record_No FROM Legal_Dispute_Lawyer LDL WHERE Lawyer_Record_No(SELECT TOP 1 Lawyer_Record_No FROM Legal_Dispute_Lawyer WHERE LD=LDL.LD AND Name=LDL.Name AND Email=LDL.Email AND Phone_No=LDL.Phone_No AND Fax_No=LDL.Fax_No))
消除Legal_Dispute_Lawyer 表中除Lawyer_Record_No(自增字段)外其余数据完全相同的记录.
表结构如下
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[Legal_Dispute_Lawyer]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[Legal_Dispute_Lawyer]
GO
CREATE TABLE [dbo].[Legal_Dispute_Lawyer] (
[Lawyer_Record_No] [int] IDENTITY (1, 1) NOT NULL ,
[LD] [int] NOT NULL ,
[Name] [int] NOT NULL ,
[Phone_No] [varchar] (255) COLLATE Chinese_PRC_CI_AS NULL ,
[Fax_No] [varchar] (255) COLLATE Chinese_PRC_CI_AS NULL ,
[Email] [varchar] (255) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]
GO
分页语句
select top 100 * from 表名 where id not in (select top page_no*100 * from 表名)
page_no是程序中的变量
从数据库中去一年的各单位电话费统计(电话费定额贺电化肥清单两个表来源)
SELECT a.userper, a.tel, a.standfee, TO_CHAR(a.telfeedate, 'yyyy') AS telyear,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '01', a.factration)) AS JAN,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '02', a.factration)) AS FRI,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '03', a.factration)) AS MAR,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '04', a.factration)) AS APR,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '05', a.factration)) AS MAY,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '06', a.factration)) AS JUE,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '07', a.factration)) AS JUL,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '08', a.factration)) AS AGU,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '09', a.factration)) AS SEP,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '10', a.factration)) AS OCT,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '11', a.factration)) AS NOV,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '12', a.factration)) AS DEC
FROM (SELECT a.userper, a.tel, a.standfee, b.telfeedate, b.factration
FROM TELFEESTAND a, TELFEE b
WHERE a.tel = b.telfax) a
GROUP BY a.userper, a.tel, a.standfee, TO_CHAR(a.telfeedate, 'yyyy')
个人认为,偶写了这么久的SQL,只说语法很无聊,语句的涵义与灵活的组合很重要,下面这个是写的一个药品管理的存储过程的一句,很经典:
UPDATE 药房库存 SET 库存数量=库存数量-B.用量 FROM [药房库存] A ,
( Select sum(用量) as 用量,药品价码 FROM 处方项
WHERE 处方号=@RecipeNo AND 处方序号=@RecipeXNo
GROUP BY 药品价码
) B WHERE A.药品价码=B.药品价码 AND A.库房名称=@DepotName
这是一个参照B表将A表中库存一一修改,而B表中存在一个求和,A表也与B表关联,同时要满足A表条件。
两台SQL服务器上的一个数据表同步!
drop procedure dbSync
GO
/* 数据同步 */
CREATE PROCEDURE dbSync
@sTabelName varchar(255), --要同步的表名
@sKeyField varchar(255), --关键字段
@sServer varchar(255), --服务器名称或IP
@sUserName varchar(255), --登录到服务器的用户名,一般为sa
@sPassWord varchar(32) --用户登录到服务器的密码
AS
/*删除临时表*/
if exists (select * from dbo.sysobjects where id = object_id(N'tempTbl') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table tempTbl
declare @sql VARCHAR(2000)
/*把表@sTabelName[远程]的数据拷贝到临时表*/
set @sql='select * into tempTbl from '
set @sql=@sql + ' OPENDATASOURCE( '
set @sql=@sql + '''SQLOLEDB.1'','
set @sql=@sql + '''Persist Security Info=True;User ID=' + @sUserName
set @sql=@sql + ';Password=' + @sPassWord
set @sql=@sql + ';Initial Catalog=toys;Data Source=' + @sServer
set @sql=@sql + ''').toys.dbo.'+@sTabelName
EXEC(@sql)
/* 把@sTabelName[本地]中的@sTabelName[远程]表中没有的数据插入到临时表中*/
set @sql='insert into tempTbl select * from '+@sTabelName+' where ['+@sKeyField+'] not in (select ['+@sKeyField+'] from tempTbl)'
EXEC(@sql)
/*清空表@sTabelName[本地]*/
set @sql='truncate table '+@sTabelName
EXEC(@sql)
--取得列名
declare @MySql VARCHAR(2000)
set @MySql=''
declare @title varchar(20)
DECLARE titles_cursor CURSOR FOR
SELECT name from syscolumns where id=object_id(@sTabelName)
OPEN titles_cursor
FETCH NEXT FROM titles_cursor INTO @title
WHILE @@FETCH_STATUS = 0
BEGIN
if @title<>'id'
begin
if @MySql = ''
set @MySql = @MySql + @title
else
set @MySql = @MySql + ',' + @title
end
FETCH NEXT FROM titles_cursor INTO @title
END
CLOSE titles_cursor
DEALLOCATE titles_cursor
--取列名结束
/*把临时表的内容插入到表@sTabelName[本地]*/
set @sql='insert into '+@sTabelName+' select '+@MySql+' from tempTbl'
EXEC(@sql)
/*删除临时表*/
if exists (select * from dbo.sysobjects where id = object_id(N'tempTbl') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table tempTbl
GO
select a,b,c from a where a IN (select d from b )
这是一个可以优化的sql语句,给a表中的a和b表中的d使用索引,然后改为
select a,b,c from a where exists (select * from b where b.d=a.a)
总结说一点:
1、要合理使用索引
索引是数据库一个重要的构成部分,很多人都会忽略它,其实索引的根本目的就是
为了提高查询效率。
使用原则如下:
在经常进行连接,但是没有指定为外键的列上建立索引,而不经常连接的字段则
由优化器自动生成索引。
在频繁进行排序或分组(即进行group by或order by操作)的列上建立索引。
在条件表达式中经常用到的不同值较多的列上建立检索,在不同值少的列上不要
建立索引。比如在雇员表的“性别”列上只有“男”与“女”两个不同值,因此就
无必要建立索引。如果建立索引不但不会提高查询效率,反而会严重降低更新速度
。
如果待排序的列有多个,可以在这些列上建立复合索引(compound index)。
在写sql语句时就必须注意有些写法是会使得数据库无法使用索引的,比如IS NULL
IS NOT NULL,IN ,NOT IN 等。。。
2.避免或简化排序
应当简化或避免对大型表进行重复的排序。当能够利用索引自动以适当的次序产生
输出时,优化器就避免了排序的步骤。以下是一些影响因素:
●索引中不包括一个或几个待排序的列;
●group by或order by子句中列的次序与索引的次序不一样;
●排序的列来自不同的表。
为了避免不必要的排序,就要正确地增建索引,合理地合并数据库表(尽管有时可
能影响表的规范化,但相对于效率的提高是值得的)。如果排序不可避免,那么应
当试图简化它,如缩小排序的列的范围等。
3.消除对大型表行数据的顺序存取
在嵌套查询中,对表的顺序存取对查询效率可能产生致命的影响。比如采用顺序存
取策略,一个嵌套3层的查询,如果每层都查询1000行,那么这个查询就要查询10
亿行数据。避免这种情况的主要方法就是对连接的列进行索引。例如,两个表:学
生表(学号、姓名、年龄……)和选课表(学号、课程号、成绩)。如果两个表要
做连接,就要在“学号”这个连接字段上建立索引。
还可以使用并集来避免顺序存取。尽管在所有的检查列上都有索引,但某些形式的
where子句强迫优化器使用顺序存取。下面的查询将强迫对orders表执行顺序操作
:
SELECT * FROM orders WHERE (customer_num=104 AND order_num>1001) OR
order_num=1008
虽然在customer_num和order_num上建有索引,但是在上面的语句中优化器还是使
用顺序存取路径扫描整个表。因为这个语句要检索的是分离的行的集合,所以应该
改为如下语句:
SELECT * FROM orders WHERE customer_num=104 AND order_num>1001
UNION
SELECT * FROM orders WHERE order_num=1008
这样就能利用索引路径处理查询。
4.避免相关子查询
一个列的标签同时在主查询和where子句中的查询中出现,那么很可能当主查询中
的列值改变之后,子查询必须重新查询一次。查询嵌套层次越多,效率越低,因此
应当尽量避免子查询。如果子查询不可避免,那么要在子查询中过滤掉尽可能多的
行。
5.避免困难的正规表达式
MATCHES和LIKE关键字支持通配符匹配,技术上叫正规表达式。但这种匹配特别耗
费时间。例如:SELECT * FROM customer WHERE zipcode LIKE “98_ _ _”
即使在zipcode字段上建立了索引,在这种情况下也还是采用顺序扫描的方式。如
果把语句改为SELECT * FROM customer WHERE zipcode >“98000”,在执行查询
时就会利用索引来查询,显然会大大提高速度。
另外,还要避免非开始的子串。例如语句:SELECT * FROM customer WHERE
zipcode[2,3] >“80”,在where子句中采用了非开始子串,因而这个语句也不会
使用索引。
6.使用临时表加速查询
把表的一个子集进行排序并创建临时表,有时能加速查询。它有助于避免多重排序
操作,而且在其他方面还能简化优化器的工作。例如:
SELECT cust.name,rcvbles.balance,……other columns
FROM cust,rcvbles
WHERE cust.customer_id = rcvlbes.customer_id
AND rcvblls.balance>0
AND cust.postcode>“98000”
ORDER BY cust.name
如果这个查询要被执行多次而不止一次,可以把所有未付款的客户找出来放在一个
临时文件中,并按客户的名字进行排序:
SELECT cust.name,rcvbles.balance,……other columns
FROM cust,rcvbles
WHERE cust.customer_id = rcvlbes.customer_id
AND rcvblls.balance>0
ORDER BY cust.name
INTO TEMP cust_with_balance
然后以下面的方式在临时表中查询:
SELECT * FROM cust_with_balance
WHERE postcode>“98000”
临时表中的行要比主表中的行少,而且物理顺序就是所要求的顺序,减少了磁盘
I/O,所以查询工作量可以得到大幅减少。
注意:临时表创建后不会反映主表的修改。在主表中数据频繁修改的情况下,注意
不要丢失数据。
7.用排序来取代非顺序存取
非顺序磁盘存取是最慢的操作,表现在磁盘存取臂的来回移动。SQL语句隐藏了这
一情况,使得我们在写应用程序时很容易写出要求存取大量非顺序页的查询。
有些时候,用数据库的排序能力来替代非顺序的存取能改进查询。