[CSS]解决IE和Mozilla的布局差别的利器:“!important”

mikel阅读(770)

初学div+css网页布局的设计者常常会被一个问题困扰着。
在IE和其他(Mozilla、Opera等)浏览器里显示的效果常常会偏差2px。
这是因为IE对盒之间距离的解释的bug造成的。一个技巧提示帮我们找到了解决的方法:用!important。
!important是CSS1就定义的语法,作用是提高指定样式规则的应用优先权。
语法格式{ sRule!important },即写在定义的最后面,如:

div css xhtml xml Example Source Code Example Source Code [www.52css.com]
box{color:red !important;} 

最重要的一点是:IE一直都不支持这个语法,而其他的浏览器都支持。我们就可以利用这一点来分别给IE和其它浏览器不同的样式定义。如:

div css xhtml xml Example Source Code Example Source Code [www.52css.com]
.colortest { 
border:20px  solid #000 !important;
border:20px  solid #CCC;
padding: 30px;
width : 300px;

在Mozilla中浏览时候,能够理解!important的优先级,因此显示#000的颜色;
在IE中浏览时候,不能够理解!important的优先级,因此显示#CCC的颜色。
可以看到,利用!important,我们可以针对IE和非IE浏览器设置不同的样式,只要在非IE浏览器样式的后面加上!important。 
!important必定成为CSS布局的利器,请记住它、掌握它、并合理的加以利用!

[IIS]使用Microsoft URL Rewrite Module for IIS 7.0修改W

mikel阅读(833)

在IIS5和IIS6时代,我们使用URL REWRITING可实现URL重写,使得WEB程序实现伪静态,但默认情况下只能实现.ASPX的伪静态,如果要实现伪静态*.HTML的页面,需要将 ISAPI里面的*.HTML应用程序映射改为.NET的ISAPI。但在IIS 7时代,这一切已经变得非常简单了,您在WEB.CONFIG中就可以管理这一切了。

 

在服务器上安装好Microsoft URL Rewrite Module,具体下载地址如下:

Microsoft URL Rewrite Module for IIS 7.0 (x86)

下载地址:http://www.iis.net/downloads/default.aspx?tabid=34&g=6&i=1691 

Microsoft URL Rewrite Module for IIS 7.0 (x64)

下载地址:http://www.iis.net/downloads/default.aspx?tabid=34&g=6&i=1692

 

安装好之后如图:

 

 

可以直接在IIS 7里面进行添加重写规则,也可以直接在WEB.CONFIG中设置:

 

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    
<system.webServer>
        
<rewrite>
            
<rules>
                
<rule name="Rewrite to article.aspx">
                    
<match url="^article/([0-9]+).html$" />
                    
<action type="Rewrite" url="article.aspx?newid={R:1}" />
                
</rule>
            
</rules>
        
</rewrite>
    
</system.webServer>
</configuration>

 

然后,访问 /article/366.html 就是 访问/article.aspx?newid=366的结果了,无需像以前IIS 6时代一样去添加*.HTML的ISAPI的应用程序映射了。(这对使用虚拟主机的站点来说可是个福音,不用再叫管理员去帮你加设置了,自己在 WEB.CONFIG中可以设置这一切了)

 

下面还附一个重写的规则转换工具。

可以将您原有的第三方组件或apache的.htaccess规则配置,快速按规范转化为IIS7.0的使用web.config重写规则。

 

 

[Javascript]FileSystemObject 对象

mikel阅读(660)

js用FileSystemObject 对象实现文件控制

一、功能实现核心:FileSystemObject 对象

其实,要在JavaScript中实现文件操作功能,主要就是依靠FileSystemobject对象。在详细介绍FileSystemobject对象的各个属性和方法的使用细节前,先来看看这个对象包括哪些相关对象和集合:

Driver 对象类型。包括收集系统中驱动器相关信息的方法和属性,不如共享名、可用空间等。一个“drive”对象不一定代表一个物理硬盘,还可以是一个CD-ROM驱动器、一个RAM盘或者一个通过网络逻辑连接的资源。
Drivers 集合类型。提供系统中一系列以物理方式或者逻辑方式存在的驱动器对象。
File 对象类型。包括对文件进行创建、删除或者移动操作的相关方法和属性,还包括查询文件名称、路径以及其他文件属性的方法和属性。
Files 集合类型。提供包含在文件夹中的一系列File对象。
Folder 对象类型。包括对文件夹进行创建、删除或者移动操作的相关方法和属性。
Folders 集合类型。提供包含在文件夹中的一系列Folder对象。
TextStream 对象类型。提供文本文件的读写功能。

 

二、FileSystemObject编程三部曲
使用FileSystemObject 对象进行编程很简单,一般要经过如下的步骤: 创建FileSystemObject对象、应用相关方法、访问对象相关属性 。
(一)创建FileSystemObject对象
创建FileSystemObject对象的代码只要1行:
var fso = new ActiveXObject("Scripting.FileSystemObject");
上述代码执行后,fso就成为一个FileSystemObject对象实例。
(二)应用相关方法
创建对象实例后,就可以使用对象的相关方法了。比如,使用CreateTextFile方法创建一个文本文件:
var fso = new ActiveXObject("Scripting.FileSystemObject");
var f1 = fso.createtextfile("c:\\myjstest.txt",true");
(三)访问对象相关属性
要访问对象的相关属性,首先要建立指向对象的句柄,这就要通过get系列方法实现:GetDrive负责获取驱动器信息,GetFolder负责获取文件夹信息,GetFile负责获取文件信息。比如,指向下面的代码后,f1就成为指向文件c:\test.txt的句柄:
var fso = new ActiveXObject("Scripting.FileSystemObject");
var f1 = fso.GetFile("c:\\myjstest.txt");
然后,使用f1访问对象的相关属性。比如:
var fso = new ActiveXObject("Scripting.FileSystemObject");
var f1 = fso.GetFile("c:\\myjstest.txt");
alert("File last modified: " + f1.DateLastModified);
执行上面最后一句后,将显示c:\myjstest.txt的最后修改日期属性值。
但有一点请注意:对于使用create方法建立的对象,就不必再使用get方法获取对象句柄了,这时直接使用create方法建立的句柄名称就可以:
var fso = new ActiveXObject("Scripting.FileSystemObject");
var f1 = fso.createtextfile("c:\\myjstest.txt",true");
alert("File last modified: " + f1.DateLastModified);
三、操作驱动器(Drives)
使用FileSystemObject对象来编程操作驱动器(Drives)和文件夹(Folders)很容易,这就象在Windows文件浏览器中对文件进行交互操作一样,比如:拷贝、移动文件夹,获取文件夹的属性。
(一)Drives对象属性
Drive对象负责收集系统中的物理或逻辑驱动器资源内容,它具有如下属性:
l TotalSize:以字节(byte)为单位计算的驱动器大小。
l AvailableSpace或FreeSpace:以字节(byte)为单位计算的驱动器可用空间。
l DriveLetter:驱动器字母。
l DriveType:驱动器类型,取值为:removable(移动介质)、fixed(固定介质)、network(网络资源)、CD-ROM或者RAM盘。
l SerialNumber:驱动器的系列码。
l FileSystem:所在驱动器的文件系统类型,取值为FAT、FAT32和NTFS。
l IsReady:驱动器是否可用。
l ShareName:共享名称。
l VolumeName:卷标名称。
l Path和RootFolder:驱动器的路径或者根目录名称。
(二)Drive对象操作例程
下面的例程显示驱动器C的卷标、总容量和可用空间等信息:
var fso, drv, s ="";
fso = new ActiveXObject("Scripting.FileSystemObject");
drv = fso.GetDrive(fso.GetDriveName("c:\\"));
s += "Drive C:" + " – ";
s += drv.VolumeName + "\n";
s += "Total Space: " + drv.TotalSize / 1024;
s += " Kb" + "\n";
s += "Free Space: " + drv.FreeSpace / 1024;
s += " Kb" + "\n";
alert(s);
执行后出现下面的信息框:
四、操作文件夹(Folders)
涉及到文件夹的操作包括创建、移动、删除以及获取相关属性。

(一) Folder对象的相关属性和方法列表

FileSystemObjec.CreateFolder 创建文件夹
Folder.Delete 或FileSystemObjec.DeleteFolder 删除文件夹
Folder.Move 或FileSystemObjec.MoveFolder 移动文件夹
Folder.Copy 或FileSystemObjec.CopyFolder 拷贝文件夹
Folder.Name 获得文件夹名称
FileSystemObjec.FolderExists 判断文件夹是否存在
FileSystemObjec.GetFolder 取得存在Folder对象的实例
FileSystemObjec.GetParentFolderName 取得文件夹的父文件夹名称
FileSystemObjec.GetSpecialFolder 取得系统文件夹路径信息

 

(二)Folder对象操作例程
下面的例程将练习获取父文件夹名称、创建文件夹、删除文件夹、判断是否为根目录等操作:
var fso, fldr, s = "";
// 创建FileSystemObject对象实例
fso = new ActiveXObject("Scripting.FileSystemObject");
// 获取Drive 对象
fldr = fso.GetFolder("c:\\");
// 显示父目录名称
alert("Parent folder name is: " + fldr + "\n");
// 显示所在drive名称
alert("Contained on drive " + fldr.Drive + "\n");
// 判断是否为根目录
if (fldr.IsRootFolder)
alert("This is the root folder.");
else
alert("This folder isn't a root folder.");
alert("\n\n");
// 创建新文件夹
fso.CreateFolder ("C:\\Bogus");
alert("Created folder C:\\Bogus" + "\n");
// 显示文件夹基础名称,不包含路径名
alert("Basename = " + fso.GetBaseName("c:\\bogus") + "\n");
// 删除创建的文件夹
fso.DeleteFolder ("C:\\Bogus");
alert("Deleted folder C:\\Bogus" + "\n");
五、操作文件(Files)
对文件进行的操作要比以上介绍的驱动器(Drive)和文件夹(Folder)操作复杂些,基本上分为以下两个类别:对文件的创建、拷贝、移动、删除操作和对文件内容的创建、添加、删除和读取操作。下面分别详细介绍。
(一)创建文件
一共有3种方法可用于创建一个空文本文件,这种文件有时候也叫做文本流(text stream)。
第一种是使用CreateTextFile方法。代码如下:
var fso, f1;
fso = new ActiveXObject("Scripting.FileSystemObject");
f1 = fso.CreateTextFile("c:\\testfile.txt", true);
第二种是使用OpenTextFile方法,并添加上ForWriting属性,ForWriting的值为2。代码如下:
var fso, ts;
var ForWriting= 2;
fso = new ActiveXObject("Scripting.FileSystemObject");
ts = fso.OpenTextFile("c:\\test.txt", ForWriting, true);
第三种是使用OpenAsTextStream方法,同样要设置好ForWriting属性。代码如下:
var fso, f1, ts;
var ForWriting = 2;
fso = new ActiveXObject("Scripting.FileSystemObject");
fso.CreateTextFile ("c:\\test1.txt");
f1 = fso.GetFile("c:\\test1.txt");
ts = f1.OpenAsTextStream(ForWriting, true);
(二)添加数据到文件
当文件被创建后,一般要按照“打开文件->填写数据->关闭文件”的步骤实现添加数据到文件的目的。
打开文件可使用FileSystemObject对象的OpenTextFile方法,或者使用File对象的OpenAsTextStream方法。
填 写数据要使用到TextStream对象的Write、WriteLine或者WriteBlankLines方法。在同是实现写入数据的功能下,这3者 的区别在于:Write方法不在写入数据末尾添加新换行符,WriteLine方法要在最后添加一个新换行符,而WriteBlankLines则增加一 个或者多个空行。
关闭文件可使用TextStream对象的Close方法。
(三)创建文件及添加数据例程
下面的代码将创建文件、添加数据、关闭文件几个步骤结合起来进行应用:
var fso, tf;
fso = new ActiveXObject("Scripting.FileSystemObject");
// 创建新文件
tf = fso.CreateTextFile("c:\\testfile.txt", true);
// 填写数据,并增加换行符
tf.WriteLine("Testing 1, 2, 3.") ;
// 增加3个空行
tf.WriteBlankLines(3) ;
// 填写一行,不带换行符
tf.Write ("This is a test.");
// 关闭文件
tf.Close();
(四)读取文件内容
从 文本文件中读取数据要使用TextStream对象的Read、ReadLine或ReadAll 方法。Read方法用于读取文件中指定数量的字符;ReadLine方法读取一整行,但不包括换行符;ReadAll方法则读取文本文件的整个内容。读取 的内容存放于字符串变量中,用于显示、分析。在使用Read或ReadLine方法读取文件内容时,如果要跳过一些部分,就要用到Skip或 SkipLine方法。
下面的代码演示打开文件、填写数据,然后读取数据:
var fso, f1, ts, s;
var ForReading = 1;
fso = new ActiveXObject("Scripting.FileSystemObject");
// 创建文件
f1 = fso.CreateTextFile("c:\\testfile.txt", true);
// 填写一行数据
f1.WriteLine("Hello World");
f1.WriteBlankLines(1);
// 关闭文件
f1.Close();
// 打开文件
ts = fso.OpenTextFile("c:\\testfile.txt", ForReading);
// 读取文件一行内容到字符串
s = ts.ReadLine();
// 显示字符串信息
alert("File contents = '" + s + "'");
// 关闭文件
ts.Close();
(五)移动、拷贝和删除文件
对 于以上三种文件操作,JavaScript各有两种对应的方法:File.Move 或 FileSystemObject.MoveFile用于移动文件;File.Copy 或 FileSystemObject.CopyFile用于拷贝文件;File.Delete 或 FileSystemObject.DeleteFile用于删除文件。
下面的代码演示在驱动器C的根目录下创建一个文本文件,填写一些内容,然后将文件移动到\tmp目录下,再在目录\temp下面建立一个文件拷贝,最后删除这两个目录的文件:
var fso, f1, f2, s;
fso = new ActiveXObject("Scripting.FileSystemObject");
f1 = fso.CreateTextFile("c:\\testfile.txt", true);
// 写一行
f1.Write("This is a test.");
// 关闭文件
f1.Close();
// 获取C:\根目录下的文件句柄
f2 = fso.GetFile("c:\\testfile.txt");
// 移动文件到\tmp目录下
f2.Move ("c:\\tmp\\testfile.txt");
// 拷贝文件到\temp目录下
f2.Copy ("c:\\temp\\testfile.txt");
// 获取文件句柄
f2 = fso.GetFile("c:\\tmp\\testfile.txt");
f3 = fso.GetFile("c:\\temp\\testfile.txt");
// 删除文件
f2.Delete();

f3.Delete();

[浏览器]兼容多种浏览器“复制到剪贴板”的解决方案

mikel阅读(894)

昨天在也一个php程序的时候,要实现“复制到剪贴板”的功能,由于安全问题,浏览器的限制越来越严,实现的方法也越来越有限,这个简单的功能要想实现兼容不同的浏览器还真不容易,根据网上的一些资料结合我测试的经验,相关解决方案如下:

方案一:利用“window.clipboardData”

代码示例:

if (window.clipboardData) {
window.clipboardData.clearData();
window.clipboardData.setData("Text", txt)

};

评论:这个方案只适合IE浏览器,同时在IE7下有个如下安全提示的对话框。

image

由于IE的浏览器的占有率比较大,这个方案是使用最广的,也是最简单的方法。

支持浏览器:IE5.5 IE6.0 IE7.0

方案二:利用“Components.interfaces.nsIClipboard”

代码示例:

if (window.netscape) {
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
} catch (e) {
if (flag == 0) {
alert("You are using the Firefox browser, copy the function browser refuse!
\nPlease in the browser address bar enter'about:config' and Enter \n and set'signed.applets.codebase_principal_support' to 'true'"
);

} else {
alert("你使用的是Firefox 浏览器,复制功能被浏览器拒绝!\n请在浏览器地址栏输入'about:config'
并回车\n然后将'signed.applets.codebase_principal_support'设置为'true'"
);

}
}
var clip = Components.classes['@mozilla.org/widget/clipboard;1'].createInstance
(Components.interfaces.nsIClipboard);
if (!clip)
return;
var trans = Components.classes['@mozilla.org/widget/transferable;1'].createInstance
(Components.interfaces.nsITransferable);
if (!trans)
return;
trans.addDataFlavor('text/unicode');
var str = new Object();
var len = new Object();
var str = Components.classes["@mozilla.org/supports-string;1"].createInstance
(Components.interfaces.nsISupportsString);
var copytext = txt;
str.data = copytext;
trans.setTransferData("text/unicode", str, copytext.length * 2);
var clipid = Components.interfaces.nsIClipboard;
if (!clip)
return false;
clip.setData(trans, null, clipid.kGlobalClipboard);

}

评论:这个方案只适合Firefox,同时也有安全的问题,如果想使用这个方法,必须手动开启Firefox的 一个配置:signed.applets.codebase_principal_support。方法是:在地址栏输输入 “about:config”,设置“signed.applets.codebase_principal_support”为true,默认该项是false

image

这个方法虽然可以在Firefox使用,但让一个普通用户去开启这个配置似乎太专业了。

把这个方案和方案一结合,可以适用80%以上的用户。

支持浏览器:Firefox1.5  firefox2.0  firefox3.0

方案三:JavaScript + flash

代码示例:

var flashcopier = 'flashcopier';
if (!document.getElementById(flashcopier)) {
var divholder = document.createElement('div');
divholder.id = flashcopier;
document.body.appendChild(divholder);
}
document.getElementById(flashcopier).innerHTML = '';
var divinfo = '<embed src="clipboard.swf" FlashVars="clipboard=' + text2copy + '"
width="0" height="0" type="application/x-shockwave-flash"></embed>'
;
document.getElementById(flashcopier).innerHTML = divinfo;}

评论:这个方案是一个最流行的方法,著名的Clipboard Copy解决方案 利用一个clipboard.swf作为桥梁,复制内容到剪贴板。原理是:创建一个隐藏的flash文件,同时给给flash的变量FlashVars 赋值“clipboard=..”,通过这个赋值flash就会把复制的内容放到剪贴板。这个方法兼容IE、Firefox、OperachromeSafari,真可谓“万能”的解决方案。浏览器Flash的安装率非常高,这几乎是一个完美的解决方案。

clipboard.swf 的下载地址:http://watch-life.googlecode.com/files/clipboard.swf

但是,世界上有完美的事么?这个方法只支持flash 9 ,不支持最新的flash 10。因为flash10中规定了只有在swf上进行了真实的操作(比如鼠标点击)才能访问剪切板,而上述方法只是使用了一个隐藏的swf文件,通过 JavaScript操作flash的剪贴板,用户并没有对swf文件进行真实的操作,因此这个方法也就失效了。

那么如何解决这个“真实操作”的问题呢?可以使用一个JavaScript库:Zero Clipboard, 利用这个js库可以支持利用flash 10 实现复制到剪贴板。这个方法原理是在一个透明的flash(对用户来说是不可见的)上覆盖一个dom元素比如button或div,当点击这个dom时, 实际点击的是flash,从而访问flash的剪贴板。真够曲折的。:-)

具体的实现详见如下链接:

DEMO页面 :  http://bowser.macminicolo.net/~jhuckaby/zeroclipboard/

Zero Clipboard项目主页: http://code.google.com/p/zeroclipboard/

支持浏览器:Firefox1.5  firefox2.0  firefox3.0 IE5.5 IE6.0 IE 7.0 opera 8 opera 9 chorme 1.0 chorme 2.0 safari 3

[JQuery]Ajax+jQuery实现LightBox与服务器通信

mikel阅读(807)

  LightBox效果又叫windows关机效果。就是像windows关机时一样,桌面上的应用失效而只能点击关机等框内的几个按钮。在网页中其实就是利用了两个层。其中一个设置成半透明遮住整个屏幕,另一个放在其上面用来显示内容。

     在写之前看过了cloudgamer的效果,自认JavaScript功力远不及他。参考了他的效果之后写了这个简单版并加入了LightBox与服务器的通信。
     为了方便演示,我把代码都写到一个文件里了,css也直接写在行内。
     首先是覆盖层:

<div id="coverLayer" style=" display:none; background:#000000;  position:absolute; "></div>    

     先不让他显示、黑色背景、绝对定位。

     然后是LightBox:

1    <div id="lightBox" style=" display:none; width:300px; height:200px;position:absolute;z-index:1001; background:#ffffff; left:50%; top:50%; margin-left:-150px; margin-top:-100px; border:#00FFFF double 4px;">
2        <id="discover" href="#" onclick="discover();">关闭</a>
3        <form id="formLogin">
4        <p>用户名:<input id="tbUserName" name="tbUserName" type="text" /></p>
5        <p>密码:<input id="tbPassword" name="tbPassword" type="password" /></p>
6        <p><input id="btnLogin" name="" type="button" value="登录" onclick="Login();" /></p>
7        </form>
8    </div>
9

     要与数据库通信,我放了一个表单在里面获取输入。两个文本框、一个按钮。还有一个关闭LightBox的连接。对于LightBox的样式。首先还是不让其显示,高、宽这样的就不用说了。绝对定位。设置z-index为1001保证他显示在最上面。白色背景。left:50%; top:50%; margin-left:-150px; margin-top:-100px;让其居中。这是css中一种比较常见的设置块状容器居中的方法。当然,这和容器的定位有关。cloudgamer的文章中也讲到了这种方法。要了解更多的关于css的东西可以去标准之路 http://www.aa25.cn/ 或者蓝色理想 http://www.blueidea.com/等地方。当然园子里也很多。(好像有点啰嗦-_-)之后是为了好看设置了一个4像素的边框。

     再是要一个开启LightBox的链接

<id="cover" href="#" onclick="cover();">登录</a>

     单击他就执行cover();覆盖函数。

     最后加了一个select用于在IE中屏蔽的测试

1  <form >     
2     <label>select
3        <select name="select" id="select" >
4            <option>测试Select覆盖</option>
5            <option>测试Select覆盖</option>
6        </select> 
7     </label>
8 </form>

 

     之后是JavaScript
     先看一下cover();覆盖函数,这个函数只是让遮盖层和LightBox显示出来,并影藏所有的select,以免在IE中效果有误。

 1<script type="text/javascript" >
 2    function cover()
 3    {
 4        $("select").each(function(){this.style.visibility="hidden";})
 5        //选择所有的select并设置为隐藏
 6         $("#coverLayer").fadeTo("fast",0.5,function(){$("#coverLayer").css("display","block");})
 7                        .width(Math.max(document.documentElement.scrollWidth, document.documentElement.clientWidth))
 8                        .height(Math.max(document.documentElement.scrollHeight, document.documentElement.clientHeight));
 9        //显示覆盖层 并设置其高和宽
10        $("#lightBox").show();
11        //显示LightBox层
12    }

13</script>

     关于遮盖层的高和宽的设置cloudgamer已经有很详细的讲述了。我也是参考他的做法。这里就不多讲了。有兴趣大家可以直接去看:http://www.cnblogs.com/cloudgamer/archive/2008/09/15/1290954.html

     再是discover();故名思议,这个函数的功能刚好和上一个相反,所以只需要把两个层设成影藏并重新显示刚才影藏的select就可以了。

1    function discover()
2    {
3        $("select").each(function(){this.style.visibility="visible";})
4        $("#coverLayer").fadeOut("normal",function(){$("#coverLayer").css("display","none");})
5        $("#lightBox").fadeOut("normal",function(){$("#lightBox").css("display","none");})
6    }

7
8

 

 

     到这里LightBox效果就基本实现了。接下来要做的是用Ajax让LightBox和服务端通信。首先创建XMLHttpRequest对象:

 1var xmlHttp;
 2
 3function CreateXMLHttpRequest()
 4{
 5    if (window.ActiveXObject)
 6    {
 7        xmlHttp=new ActivXObjct("Microsoft.XMLHTTP");
 8    }

 9    else
10        if (window.XMLHttpRequest)
11        {
12            xmlHttp=new XMLHttpRequest();
13        }

14        
15}

16

    这里的变量xmlHttp是全局的,下面还会再用到。因为IE和其他标准浏览器的XMLHttpRequest对象不一样,所以我用了两个条件判断来兼容IE。

     创建了XMLHttpRequest对象以后就要用它向服务端发送请求了

 1function Login()
 2{
 3    CreateXMLHttpRequest();
 4
 5    var strUserName=$("#tbUserName").val();
 6    var strPassword=$("#tbPassword").val();
 7    var url="/test/Ajax.aspx?userName="+strUserName+"&password="+strPassword;
 8    xmlHttp.open("GET",url,true);                
 9    xmlHttp.onreadystatechange=CallBack;
10    xmlHttp.send(null);
11}

12

     首先获取表单上的值,并根据获得的值构造请求用的url。再用get方法发送请求。这些似乎都是Ajax的标准代码了。发送了请求我们再看服务器要怎么响应了。

 1    protected void Page_Load(object sender, EventArgs e)
 2    {
 3        //string strAction = Request.QueryString["action"];
 4        //switch (strAction)
 5        //{
 6        //    case login:
 7        //        {
 8        string strUserName = Request.QueryString["userName"];
 9        string strPassword = Request.QueryString["password"];
10        LoginCallBack(strUserName,strPassword);
11                    //break;
12        //        }
13        //}
14
15
16    }

17
18    protected void LoginCallBack(string strUserName,string strPassword)
19    {
20        string strCommandText = "Select * FROM admin Where name='" + strUserName + "' AND password='" + strPassword + "'";
21        SQLConnection myConnection = new SQLConnection(strCONNECTION_STRING);
22        SqlCommand myCommand = new SqlCommand(strCommandText, myConnection);
23        SqlDataReader myReader;
24        myConnection.Open();
25        myReader = myCommand.ExecuteReader();
26        if (myReader.Read())
27        {
28            Session["UserName"= myReader.GetString(1);
29            Session["Password"= myReader.GetString(2);
30            Response.ContentType = "text/plain";
31            Response.AppendHeader("Cache-Control""no-cache");
32            Response.AppendHeader("Pragma""no-cache");
33            Response.Write("欢迎:" + Session["UserName"]);
34            Response.End();
35        }

36        else
37        {
38            Response.Write("登录失败");
39        }

40    }

41}
42

     第一步当然是获得查询参数。之后再作为函数的参数用函数对其进行处理。我这里是模仿了一个登录。至于怎么样从数据库中查出数据以及查出什么数据不是我们这篇文章要讨论的问题。这里不多说。关键是下面几句

1             Response.ContentType = "text/plain";
2             Response.AppendHeader("Cache-Control""no-cache");
3             Response.AppendHeader("Pragma""no-cache");
4             Response.Write("欢迎:" + Session["UserName"]);
5             Response.End();
6 

     第一句是设置XMLHttpRequest对象返回的类型。我这里设置的是纯文本。也可以设置成“text/xml”。这样返回的就是 xml类型的。在客户端可以用DOM获取里面的内容。接下去的两句是设置首部,使浏览器不会在本地缓存结果。之所以要设置两个是为了向后兼容。然后那句大 家就应该很熟悉了——输出内容。最后是关闭或者说结束response对象。

     现在服务端已经返回数据了,看在客户端的接收。

 1function CallBack()
 2{
 3    if(xmlHttp.readyState==4)
 4    {
 5        if(xmlHttp.status==200)
 6        {
 7            var strMeesage=xmlHttp.responseText;
 8            InsertMessage(strMeesage);
 9Tag标签: jQuery,JavaScript,C#,LightBox,Ajax

posted @ 2009-03-04 22:34 咖啡不苦

[C#]CodeFx:一站式微软开发技术解决方案

mikel阅读(888)

转载:http://www.cnblogs.com/phinecos/archive/2009/03/04/1403450.html    
晚上在博客堂读完VSTO
写的一篇文章,介绍了CodePlex上面的一个项目,叫做All-In-One Code Framework,代号CodeFx简单的说,就是收集了几乎所有常见的微软开发技术的示例项目,将其打包到这个框架里,而且还使用多各种不同的语言进行实现。比如创建一个ActiveX控件和COM组件,CodeFx里面使用ATLMFCVBC#来实现同样的功能。

适合新手入门,也可以作为一份模板供经验丰富的开发者使用,可节省大量的时间。官方网站上给出了框架的基本结构,如下图所示:

COM组件和ActiveX控件示例

 

数据访问示例

库示例

进程间通信示例 

 

花了2个小时粗略阅读了代码,记录下学习心得:

1)先来说ActiveX这条线,它里面使用了ATL(这里有2种实现,进程内和进程外)MFC,C#,VB四种技术来实现。功能就是四点:一个返回字符串的HelloWorld方法,一个float类型的属性FloatProperty,一个返回进程号和线程号的GetProcessThreadID方法,一个FloatPropertyChanging事件。

2)授权支持是 ActiveX 控件的一项可选功能,它使您得以控制能使用或分发该控件的人。(请参见MFC ActiveX 控件:授权 ActiveX 控件》)。

头文件的修改     

“ActiveX 控件向导将下列代码放置在控件头文件中。声明了 factory 对象的两个成员函数,其中一个成员函数验证控件 .LIC 文件是否存在,而另一个成员函数则对包含该控件的应用程序中使用的许可证密钥进行检索:

    BEGIN_OLEFACTORY(CMFCActiveXCtrl)        // Class factory and guid
        virtual BOOL VerifyUserLicense();
        
virtual BOOL GetLicenseKey(DWORD, BSTR FAR*);
    END_OLEFACTORY(CMFCActiveXCtrl)

 

实现文件的修改

“ActiveX 控件向导将下面两条语句放置在控件实现文件中,以声明许可文件名和许可字符串:

static const TCHAR BASED_CODE _szLicFileName[] = 
   _T(
"License.lic");
static const WCHAR BASED_CODE _szLicString[] =
   L
"Copyright (c) 2000 ";

注意:如果以任何方式修改 szLicString,则必须也修改控件 .LIC 文件的第一行,否则授权将无法正确运行。
“ActiveX 控件向导”将下列代码放置在控件实现文件中,以定义控件类的 VerifyUserLicense 函数和 GetLicenseKey 函数:

// CMFCActiveXCtrl::CMFCActiveXCtrlFactory::VerifyUserLicense –
// Checks for existence of a user license

BOOL CMFCActiveXCtrl::CMFCActiveXCtrlFactory::VerifyUserLicense()
{
    
return AfxVerifyLicFile(AfxGetInstanceHandle(), _szLicFileName,
        _szLicString);
}
// CMFCActiveXCtrl::CMFCActiveXCtrlFactory::GetLicenseKey –
// Returns a runtime licensing key
BOOL CMFCActiveXCtrl::CMFCActiveXCtrlFactory::GetLicenseKey(DWORD dwReserved,
    BSTR FAR
* pbstrKey)
{
    
if (pbstrKey == NULL)
        
return FALSE;
    
*pbstrKey = SysAllocString(_szLicString);
    
return (*pbstrKey != NULL);
}

最后,“ActiveX 控件向导修改控件项目 .IDL 文件。将关键字 licensed 添加到控件的 coclass 声明中,如下例所示:

    [ uuid(E389AD6C4FB647AFB03AA5A5C6B2B820), licensed,
      helpstring(
"MFCActiveX Control"), control ]
    coclass MFCActiveX

  3)作者封装了一个方法AutoWrap来调用COM组件公开出来的属性或方法。

HRESULT AutoWrap(int autoType, VARIANT *pvResult, IDispatch *pDisp, 
                 LPOLESTR ptName, 
int cArgs
{
    
// Begin variable-argument list
    va_list marker;
    va_start(marker, cArgs);
    
if (!pDisp) 
    {
        _putts(_T(
"NULL IDispatch passed to AutoWrap()"));
        _exit(
0);
    }
    
// Variables used
    DISPPARAMS dp = { NULL, NULL, 00 };
    DISPID dispidNamed 
= DISPID_PROPERTYPUT;
    DISPID dispID;
    HRESULT hr;
    
char szName[200];
    
    
// Convert down to ANSI
    WideCharToMultiByte(CP_ACP, 0, ptName, 1, szName, 256, NULL, NULL);
    
    
// Get DISPID for name passed
    hr = pDisp->GetIDsOfNames(IID_NULL, &ptName, 1, LOCALE_USER_DEFAULT,
        
&dispID);
    
if (FAILED(hr))
    {
        _tprintf(_T(
            
"IDispatch::GetIDsOfNames(\"%s\") failed w/err 0x%08lx\n"
            ), szName, hr);
        
return hr;
    }
    
    
// Allocate memory for arguments
    VARIANT *pArgs = new VARIANT[cArgs+1];
    
// Extract arguments
    for(int i=0; i<cArgs; i++
    {
        pArgs[i] 
= va_arg(marker, VARIANT);
    }
    
    
// Build DISPPARAMS
    dp.cArgs = cArgs;
    dp.rgvarg 
= pArgs;
    
    
// Handle special-case for property-puts
    if (autoType & DISPATCH_PROPERTYPUT)
    {
        dp.cNamedArgs 
= 1;
        dp.rgdispidNamedArgs 
= &dispidNamed;
    }
    
    
// Make the call
    hr = pDisp->Invoke(dispID, IID_NULL, LOCALE_SYSTEM_DEFAULT,
        autoType, 
&dp, pvResult, NULL, NULL);
    
if (FAILED(hr)) 
    {
        _tprintf(_T(
            
"IDispatch::Invoke(\"%s\"=%08lx) failed w/err 0x%08lx\n"
            ), szName, dispID, hr);
        
return hr;
    }
    
// End variable-argument section
    va_end(marker);
    
    delete[] pArgs;
    
    
return hr;
}

4)DLL的延迟加载使得我们不需要使用LoadLibrary和GetProcAddress。这样的好处是直到程序调用DLL中的函数时才加载此DLL。

#include <Delayimp.h>

  卸载延迟加载的DLL的代码:

    PCSTR pszDll = "CppDllExport.dll";
    _tprintf(_T(
"__FUnloadDelayLoadedDLL2 => %d\n"),
    __FUnloadDelayLoadedDLL2(pszDll));

posted on 2009-03-04 23:15 Phinecos(洞庭散人)

[C#]请别埋没了URL Routing

mikel阅读(722)

转载:http://www.cnblogs.com/JeffreyZhao/archive/2009/03/05/fully-leverage-url-routing.html

实现分析

  既然Model Binder机制有着明显的缺陷,那么我们又该如何处理这样的问题呢?

   我们再来回顾一下目前问题:对于从URL中表现出来的参数,我们可以把URL Routing捕获到的数据使用Model Binder进行转化(例如上例中的DateTimeModelBinder);但是如果我们在生成URL时直接提供复杂参数,则框架只会把它简单的 ToString后放入URL。这是因为那些与URL有关的HTML Helper会将数据交给URL Ruoting组件来生成URL,而Route规则在生成URL时不知道一个复杂对象该如何转变为URL,因此……

  慢着,你刚才说, 把数据“交给URL Routing组件来生成URL”?URL Routing不是解析URL用的吗?为什么还负责“生成”URL?没错,与Model Binder不同,URL Routing的工作职责是“双向”的。它既负责从URL中提取RouteData,也负责根据Route生成一个URL——可惜微软没有对URL Routing给出足够的资料,有相当多的朋友没有意识到这一点。

  可恶的微软。

  既然问题的原因是Model Binder的“单向性”,那么如果存在一个“双向”的Model Binder就应该可以解决问题。例如,我们可以继承现有的IModelBinder接口进行扩展,那么至少从解析URL到执行Action方法这个流程 中所有的功能都不需要任何额外工作。可惜,这种做法对于大多数HTML Helper来说,我们就必须定义新的扩展,才能利用所谓的“双向Model Binder”。不过其实我们可以有更好的解决方案——成本低廉,通用性强。既然上次提到了传说中的“Model Binder强迫症”,那么我们现在就把目光移到Model Binder以外的地方。

  您一定已经猜到我们要从哪里入手了。没错,就是URL Routing。关于这方面,大名鼎鼎的Scott Hanselman同 学提出将DateTime类型进行分割,也就是将一个DateTime切成年、月、日多个部分进行表示。这个做法老赵颇不赞同,无论从易用性还是通用性等 角度来看,这种做法都是下下之策。说实话,这样的做法其实并没有跳出框架既有功能给定的圈子,它只是通过“迎合框架”来满足自己的需求,而不是让框架为我 们的需求服务。

  那么,我们来分析一下URL Routing组件的运作方式吧,这是必要的预备工作:

  • 首 先,应用程序为RouteCollection类型的RouteTable.Routes集合添加一些Route规则,每个规则即为一个 RouteBase对象。RouteBase是一个抽象类型,其中包含两个抽象方法,GetRouteData和GetVirtualPath。
  • 在 捕获URL中数据的时候,URL Routing组件将调用RouteTable.Routes.GetRouteData方法来获得一个RouteData对象。简单来说,它会依次调用 每个RouteBase对象的GetRouteData方法,直到得到第一个不为null的RouteData对象。
  • 在生成URL 时,URL Routing组件将调用RouteTable.Routes.GetVirtualPath方法来获得一个VirtualPathData对象。简单来 说,它会依次调用每个RouteBase对象的GetVirtualPath方法,直到得到第一个不为null的VirualPathData对象。

   显然,光有RouteBase抽象类型是不足以提供任何有用功能的。因此URL Routing框架还提供了一个具体的Route类型供大家使用。说起Route类,它的功能可谓非常强大。我们在使用ASP.NET MVC框架时用到的MapRoute方法,其实就是在向RouteTable.Routes集合中添加Route对象。而其中的URL占位符,默认值,约 束等功能,实际上完全由Route对象实现了。多么强大的Route类型!如果想要写一个足以匹敌,并且包含额外功能的RouteBase实现可不是一件 容易的事情。幸好我们生活在面向对象的美好世界中,“复用”是我们手中威力非凡的利器。如果我们基于现有的Route类型进行扩展,那么大部分的工作我们 弹指间便可完成。

  现有的Route只能从URL中提取字符串类型的数据,同时也只能把任何对象作为字符串来生成URL。而我们将要构 造RouteBase实现,就要弥补这一缺陷,让Route规则能够直接从URL中提取出复杂对象,并且知道如何将一个复杂对象转化为一个URL。有了前 者,RouteData就能包含复杂类型的对象,以此应对Action方法的参数自然不是问题;有了后者,我们只需要提供一个强类型的复杂对 象,Route规则也能顺利地将其转化为可以识别的URL——多么美好。

Route Formatter

  那么解析字符串,或生成URL的职责由谁来完成呢?于是我们定义一个IRouteFormatter来负责这件事情:

public interface IRouteFormatter
{
bool TryParse(object value, out object output);
bool TryToString(object value, out string output);
}

  TryParse方法负责将一个对象转化为我们需要的复杂类型对象,而TryToString则将一个复杂类型对象转化为字符串(即URL)。 两个方法都返回一个布尔值,以表示这次转化是否合法。您可能会发现,TryToString输出的是一个string,而TryParse……他接受的是 一个object类型的参数,这是怎么回事呢?原因在于Route规则中的“默认值”设置。在Route规则中我们可以为RouteData中的某个“字 段”设定默认值,这样即使URL中无法捕获到这个字段,它也可以出现在RouteData中。从URL中捕获得到的自然是一个字符串,但是默认值则可以设 为任意类型的对象。因此Formatter需要可以接受一个object参数,并设法将其转化为我们需要的复杂类型。

  是不是有点绕?请继续看下去,您会了解它的作用的。虽说TryParse需要接受一个object参数,但是在大多数情况下,我们更多是要处理强类型。因此我们不妨再定一个RouteFormatter抽象类,方便强类型IRouteFormatter对象的编写:

public abstract class RouteFormatter<T> : IRouteFormatter
{
public abstract bool TryParse(string value, out T output);
public abstract bool TryToString(T value, out string output);
bool IRouteFormatter.TryParse(object value, out object output)
{
if (value is T)
{
output = value;
return true;
}
string s = value as string;
if (s == null)
{
output = null;
return false;
}
else
{
T t;
var result = this.TryParse(s, out t);
output = t;
return result;
}
}
bool IRouteFormatter.TryToString(object value, out string output)
{
if (value is T)
{
return this.TryToString((T)value, out output);
}
else
{
output = null;
return false;
}
}
}

  RouteFormater<>类接受一个范型参数,并且准备两个强类型的抽象方法让子类实现。至于接口中的两个类型,它们会处理 一部分逻辑——主要是类型判断——只在合适的时候将操作交给范型方法来实现。TryToString方法朴实无华,而TryParse方法相对较为有趣, 它会首先判断value参数的类型,如果已经符合当前的范型类型,则直接将其转化后返回。这就是为了“默认值”而进行的处理,例如用户准备了一个 DateTime类型的默认值,并被Route规则采纳了,则我们的RouteFormatter<DateTime>就会将其直接返回,不 做任何转化。

  为了解决目前提出的问题,我们会编写一个DateTimeFormatter,它接受一个Format参数表示日期的格式:

public class DateTimeFormatter : RouteFormatter<DateTime>
{
public string Format { get; private set; }
public DateTimeFormatter(string format)
{
this.Format = format;
}
public override bool TryParse(string value, out DateTime output)
{
return DateTime.TryParseExact(value, this.Format, null, DateTimeStyles.None, out output);
}
public override bool TryToString(DateTime value, out string output)
{
output =  value.ToString(this.Format);
return true;
}
}

  那么有没有某个Route Formatter需要直接实现IRouteFormatter接口呢?有。之前提到TryParse方法将在value参数符合范型T的情况下直接返回 “通过”,如果某个Route Formatter不支持这条判断,则自然无法继承于RouteFormatter<>类型。例如下面的RegexFormatter,将使 用正则表达式对某个字段的值进行约束。在我们的RouteBase实现中,RegexFormatter便是Route类中“约束”功能的替代品。如下:

public class RegexFormatter : IRouteFormatter
{
public Regex Regex { get; private set; }
public RegexFormatter(string pattern)
{
this.Regex = new Regex(pattern,
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Compiled);
}
public bool TryParse(object value, out object output)
{
string s;
bool result = this.Try(value, out s);
output = s;
return result;
}
public bool TryToString(object value, out string output)
{
return this.Try(value, out output);
}
private bool Try(object value, out string output)
{
var s = value as string;
if (s != null && this.Regex.IsMatch(s))
{
output = s;
return true;
}
else
{
output = null;
return false;
}
}
}

  RegexFormatter的关键在于Try方法。Try方法首先判断value参数是否为一个字符串,如果是,则使用正则表达式进行验证。当且仅当value为字符串并满足指定的正则表达式时,RegexFormatter才表示“通过”。

FormatRoute实现

  FormatRoute便是我们RouteBase抽象类的实现,它提供了Route类的所有功能,并可以为每个字段设置一个Route Formatter对象,以此对这个字段进行转换或约束。之前提到,我们会将主要功能委托给现有Route类型,这样可以大大简化我们的工作量。因此,我 们会在FormatRoute中包含一个Route类型的对象,此外还会保留所有字段与其Route Formatter的映射关系。请看如下构造函数:

public class FormatRoute : RouteBase
{
private Route m_route;
private IDictionary<string, IRouteFormatter> m_formatters;
public FormatRoute(
string url,
RouteValueDictionary defaults,
IDictionary<string, IRouteFormatter> formatters,
RouteValueDictionary constaints,
RouteValueDictionary dataTokens,
IRouteHandler routeHandler)
{
this.m_formatters = formatters;
this.m_route = new Route(
url,
defaults,
constaints,
dataTokens,
routeHandler);
}
...
}

  RouteBase的关键方法便是GetRouteData和GetVirtualPath。有了Route类型的辅助,这两个方法其实非常简单。如下:

public override RouteData GetRouteData(HttpContextBase httpContext)
{
var result = this.m_route.GetRouteData(httpContext);
if (result == null) return null;
var valuesModified = new Dictionary<string, object>();
foreach (var pair in result.Values)
{
var key = pair.Key;
IRouteFormatter formatter = null;
if (this.m_formatters.TryGetValue(key, out formatter))
{
object o;
if (formatter.TryParse(pair.Value, out o))
{
valuesModified[key] = o;
}
else
{
return null;
}
}
}
foreach (var pair in valuesModified)
{
result.Values[pair.Key] = pair.Value;
}
return result;
}
public override VirtualPathData GetVirtualPath(
RequestContext requestContext, RouteValueDictionary values)
{
var routeValues = new RouteValueDictionary();
foreach (var pair in values)
{
var key = pair.Key;
IRouteFormatter formatter = null;
if (this.m_formatters.TryGetValue(key, out formatter))
{
string s;
if (formatter.TryToString(pair.Value, out s))
{
routeValues[key] = s;
}
else
{
return null;
}
}
else
{
routeValues[key] = pair.Value;
}
}
return this.m_route.GetVirtualPath(requestContext, routeValues);
}

  GetRouteData会接受一个HttpContextBase对象,并调用Route对象的GetRouteData方法获取一个 RouteData对象。如果RouteData不为null,则遍历其中的所有字段,如果指定了对应的Route Formater,则还需要通过Route Formatter的检验及转化——没错,经历了Route Formatter之后的RouteData中已经包含了强类型对象。而GetVirtualPath方法则略有不同,它首先遍历values参数中的所 有字段,将其中的强类型对象转化为字符串,也就是URL片段,这样交给Route对象来生成VirtualPathData时,便可以得到正确的URL 了。

  最后便是FormatRoute的运用:

routes.Add(
"Demo.Date",
new FormatRoute(
"{controller}/{action}/{date}",
new RouteValueDictionary(), // defaults
new Dictionary<string, IRouteFormatter>
{
{"controller", new RegexFormatter("Demo")},
{"action", new RegexFormatter("Date")},
{"date", new DateTimeFormatter("yyyy-MM-dd")}
},
new RouteValueDictionary(), // constaints
new RouteValueDictionary(), // data tokens
new MvcRouteHandler()));

  除了为date字段指定了转化用的DateTimeFormatter之外,我们也为controller和action字段提供了负责约束的 RegexFormatter——这点只是为了演示。更好的做法是直接将URL设为Demo/Date/{date},并在默认值中指定 controller和action的值。此外,您也可以使用传统的方式为字段提供约束,而不是使用RegexFormatter。当然,效果几乎可以说 是一模一样的。

总结

  现在我们完美地解决了之前提出的问题。使用FormatRoute可以轻松地处理URL中特定类型对象的提取,并且可以把特定类型的对象转化为 URL的片段。除了日期时间之外,我们还可以转化语言文化,查询条件等任意复杂类型。而RouteFormatter对象与Route规则的分离,使得我 们可以对RouteFormatter进行独立的单元测试,这也是一件十分理想的事情。这下在视图中,无论是指定Route Values,还是使用强类型的方式,我们都可以正确获得所需的URL了。如下:

<%= Html.ActionLink("Yesterday", "Date", new { date = date.AddDays(-1) }) %>
<span><%= date.ToShortDateString() %></span>
<%= Html.ActionLink<DemoController>(c => c.Date(date.AddDays(1)), "Tomorrow") %>

  那么,从设计上讲,把数据的提取转移到URL Routing上是否合适呢?答案是肯定的。因为URL Routing的职责原本就是从URL中提取数据——任意类型的数据,以及把数据转化为URL,我们现在只是充分利用了URL Routing的功能而已。事实上,我建议任何使用URL表示的数据,都把转化的职责转移到URL Routing这一层,因为这时我们基本上无可避免地需要根据数据来生成URL。一般情况下,我们要尽可能地使用强类型数据。那么Model Binder难道就没有用了吗?当然不是。URL Routing负责从URL中提取数据,而Model Binder则用于从其他方面来获取参数。例如POST来的数据,例如《最佳实践》中的Url Referrer参数。

  打开视野,发挥程序员的敏捷思路,生活就会变得更加美好。

[SQL]通过 SQL Server 2005 索引视图提高性能

mikel阅读(904)

什么是索引视图?

多年以来,Microsoft® SQL Server™ 一直支持创建称为视图的虚拟表。通常,这些视图的主要作用是:

提供一种安全机制,将用户限制到一个或多个基表的某个数据子集中。

提供一种机制,允许开发人员自定义用户通过逻辑方式查看存储在基表中的数据的方式。

通 过 SQL Server 2000,SQL Server 视图的功能得到了扩展,实现了系统性能方面的收益。可在视图上创建唯一的聚集索引及非聚集索引,来提高最复杂的查询的数据访问性能。在 SQL Server 2000 和 2005 中,具有唯一的聚集索引的视图即为索引视图。本文所讨论的内容适用于 SQL Server 2005,其中有许多内容也适用于 SQL Server 2000。

从数据库管理系统 (DBMS) 的角度看来,视图是对数据(一种元数据类型)的一种描述。当创建了一个典型视图时,通过封装一个 Select 语句(定义一个结果集来表示为虚拟表)来定义元数据。当在另一个查询的 FROM 子句中引用视图时,将从系统目录检索该元数据,并替代该视图的引用扩展元数据。视图扩展之后,SQL Server 查询优化器会为执行查询编译一个执行计划。查询优化器会搜索针对某个查询的一组可能的执行计划,并根据对执行每个查询计划所需的实际时间的估计,选择所能 找到的成本最低的计划。

对于非索引视图,解析查询所必需的视图部分会在运行时被具体化。任何计算(比如:联接或聚合)都在每个引用视图的查询执行时完成1。在视图上创建了唯一的聚集索引后,该视图的结果集随即被具体化,并保存在数据库的物理存储中,从而在执行时节省了执行这一高成本操作的开销。

在 查询执行中,可通过两种方式使用索引视图。查询可直接引用索引视图,或者更重要的是,如果查询优化器确定该视图可替换成本最低的查询计划中的部分或全部查 询,那么就可以选定它。在第二种情况中,使用索引视图替代基础表及其一般索引。不必在查询中引用视图以使查询优化器在查询执行时使用该视图。这使得现有的 应用程序可以从新创建的索引视图中受益,而不必进行更改。

注意 索引视图是 SQL Server 2000 和 2005 各版本的一个功能。在 SQL Server 2000 和 2005 的 Developer 和 Enterprise 版本中,查询处理器可使用索引视图来解析结构上与该视图相匹配的查询,即便不按名称来引用视图。在其他版本中,必须按名称来引用视图,并对视图引用使用 NOEXPAND 提示来查询索引视图的内容。

通过索引视图改善性能

运用索引提高查询性能不算是一个新概念;但是,索引视图提供了一些借助标准索引无法取得的性能收益。索引视图可通过以下方式提高查询性能:

可预先计算聚合并将其保存在索引中,从而在查询执行时,最小化高成本的计算。

可预先联接各个表并保存最终获得的数据集。

可保存联接或聚合的组合。

该图说明了当查询优化器使用索引视图时,通常所能取得的性能改进。所列举的查询在复杂性上有所不同(比如:聚合计算的数量、所用表的数量或谓词的数量)并包含来自真实的生产环境的具有数百万行的表。

IPSQL501.gif

在视图上使用非聚集索引

其 次,视图上的非聚集索引可提供更好的查询性能。与表上的非聚集索引类似,视图上的非聚集索引可提供更多选项,供查询优化器在编译过程中选择。例如,如果查 询包含聚集索引所未涉及的列,那么优化器可在计划中选择一个或多个辅助索引,避免对索引视图或基表进行费时的完全扫描。

对架构添加索引会增加数据库的开销,因为索引需要持续的维护。在索引数量和维护开销间寻求适当的平衡点时,应谨慎权衡。

返回页首

应用索引视图的优点

在 实施索引视图前,分析数据库工作负荷。运用查询及各种相关工具(比如:SQL Profiler)方面的知识来确定可从索引视图获益的查询。频繁发生聚合和联接的情况最适合使用索引视图。无论是否频繁发生,只要某个查询需要很长的响 应时间,同时快速获得响应的开销很高,那么就适合使用索引视图。例如,一些开发人员发现为高级主管们在月末运行的报告,创建预先计算和存储查询的应答的索 引视图很有用。

不是所有的查询都能从索引视图中获益。与一般索引类似,如果未使用索引视图,就无法从中受益。在这种情况下,不仅无法实 现性能改善,而且会在磁盘空间、维护和优化方面产生额外的成本。然而,当使用索引视图时,可大大改善(在数量级上)数据访问。这是因为查询优化器使用存储 在索引视图(大幅降低了查询执行的成本)中预先计算的结果。

查询优化器仅考虑对具有高成本的查询使用索引视图。从而避免出现这样的情况:在查询优化成本高于使用索引视图所节约的成本时尝试匹配各种索引视图。在成本少于 1 的查询中很好使用索引视图。

从实施索引视图中获益的应用程序包括:

决策支持工作负荷

数据集市

数据仓库

联机分析处理 (OLAP) 存储和源

数据挖掘工作负荷

从查询类型和模式方面来看,获益的应用程序一般包含:

大型表的联接和聚合

查询的重复模式

几组相同或重叠的列上的重复聚合

相同键上相同表的重复联接

以上各项的组合

相反,执行许多写入操作的联机事务处理 (OLTP) 系统或者频繁更新的数据库应用程序可能无法运用索引视图,因为同时更新视图和底层基表会带来更高的维护成本。

查询优化器如何使用索引视图

SQL Server 查询优化器自动决定何时对给定的查询执行使用索引视图。不必在查询中直接引用视图以供优化器在查询执行计划中使用。所以,现有的应用程序可运用索引视图,而不用更改应用程序本身;只是必须创建索引视图。

优化器考虑事项

查询优化器通过考虑几个条件来决定索引视图能否涵盖整个或部分查询。这些条件对应查询中的一个 FROM 子句并由下列这几个部分组成:

查询 FROM 子句中的表必须是索引视图 FROM 子句中的表的超集。

查询中的联接条件必须是视图中的联接条件的超集。

查询中的聚合列必须可从视图中的聚合列的子集派生。

查询选择列表中的所有表达式必须可从视图选择列表或未包含在视图定义中的表派生。

如果与其他谓词所匹配的行的超集相匹配,那么该谓词将归入另一个谓词。例如,“T.a=10”归入“T.a=10 and T.b=20”。任何谓词都可归入其自身。视图中限制表值的那部分谓词必须归入查询中限制相同表的那部分谓词。此外,必须以 SQL Server 可验证的方式实现这一点。

属于视图定义中的表的查询搜索条件谓词的所有列必须出现在下列视图定义的一项或多项中:

1.

一个 GROUP BY 列表。

2.

视图选择列表(如不存在 GROUP BY)。

3.

视图定义中相同或等价的谓词。

情况 (1) 和 (2) 允许 SQL Server 对视图的列应用查询谓词,以便进一步限制视图的列。情况 (3) 比较特殊。在这种情况下,不需要对列进行筛选,因此该列不必出现在视图中。

如果查询不止包含一个 FROM 子句(子查询、派生表、UNION),优化器可能选择几个索引视图来处理查询,并将它们应用到不同 FROM 子句。2

本文档的末尾提供了涉及这些情况的具体查询。推荐的最佳实务是让查询优化器决定在查询执行计划中使用哪些索引(如果有的话)。

 

使用 NOEXPAND 视图提示

当 SQL Server 处理按名称引用视图的查询时,视图的定义只有在仅引用基表时才会被正常扩展。这个过程称为视图扩展。其属于一种宏扩展形式。

NOEXPAND 视图提示可强制查询优化器将视图视为带有聚集索引的普通表。其可防止视图扩展。只有在 FROM 子句中直接引用索引视图,才会应用 NOEXPAND 提示。例如,

Select Column1, Column2, ...FROM Table1, View1 WITH (NOEXPAND) Where ...

如要确保让 SQL Server 通过自己读取视图而不是从基表读取数据来处理查询,那么可使用 NOEXPAND。如果出于某种原因,SQL Server 选择了一个查询计划来对基表处理查询,而您想让其使用视图,那么可以考虑使用 NOEXPAND。必须在除 Developer 和 Enterprise 版本外的 SQL Server 的所有版本中使用 NOEXPAND 来让 SQL Server 直接对索引视图处理查询。可以看到 SQL Server 为计划的图形表达式选择了一个使用 SQL Server Management Studio 工具的显示预计的执行计划功能的语句。或者,可以看到使用 SHOWPLAN_ALL、SHOWPLAN_TEXT 或 SHOWPLAN_XML 的不同的非图形表达式。参阅 SQL Sever 联机丛书中有关 SHOWPLAN 的不同版本的相关讨论。

使用 EXPAND VIEWS 查询提示

处理按名称引用视图的查询时,除非对视图引用添加 NOEXPAND 提示,否则 SQL Server 总会扩展视图。该提示会尝试匹配索引视图和扩展查询,除非在查询末尾的一个 OPTION 子句中指定 EXPAND VIEWS 查询提示。例如,假设数据库中有一个索引视图 View1。在下方的查询中,根据其逻辑定义(其 Create VIEW 语句)对 View1 进行了扩展,然后 EXPAND VIEWS 选项会阻止在计划中使用 View1 的索引视图来解析该查询。

Select Column1, Column2, ... FROM Table1, View1 Where ...
OPTION (EXPAND VIEWS)

如要确保让 SQL Server 通过从查询所引用的基表直接访问数据来处理该查询,而不必访问索引视图,那么可使用 EXPAND VIEWS。在某些情况下,EXPAND 视图有助于消除因使用索引视图而导致的锁争用。在测试应用程序时,NOEXPAND 和 EXPAND VIEWS 都可帮助用户在使用和不使用索引视图的情况下进行性能评估。

返回页首

SQL Server 2005 的索引视图有哪些新增功能?

与 SQL Server 2000 相比,SQL Server 2005 包含了许多索引视图的改进功能。可索引的视图组已扩展至包含基于下列各项的视图:

标量聚合,包括 SUM 和不带 GROUP BY 的 COUNT_BIG。

标量表达式和用户定义的功能 (UDFs)。例如,给定一个表 T(a int, b int, c int) 和一个标量 UDF dbo.MyUDF(@x int),T 上定义的索引视图可包含一个计算列(比如:a+b 或 dbo.MyUDF(a))。

不精确的永久性列。不精确的列是一种浮型或实型的列,或者是一种派生自浮型或实型列的计算列。在 SQL Server 2000 中,如果不属于索引键的一部分,不精确的列就可用于索引视图的选择列表。不精确的列不能用于视图定义中的其他地方(比如:Where 或 FROM 子句)。如果不精确的列永久保存在基表中,那么 SQL Server 2005 允许其加入键或视图定义。永久性列包含常规列和标记为 PERSISTED 的计算列。

不精确的非永久性列无法加入索引或索引视图的根本原因是:必须使数据库脱离原计算机,然后再附加到另一台计算机。完成转移之后,保存在索引 或索引视图中的所有计算列值在新硬件上的派生方式必须与旧硬件完全相同,精确到每个位。否则,这些索引视图在新硬件上会遭到逻辑破坏。由于这种破坏,在新 硬件上,针对索引视图的查询会根据计划是否使用了索引视图或基表来派生视图数据,返回不同的应答。此外,无法在新计算机上正常维护索引视图。可惜,不同计 算机上的浮点硬件(即便采用相同制造商的相同处理器体系结构)在处理器的版本上并不总是完全相同。对于某些浮点值 a 和 b,固件升级可能导致新硬件上的 (a*b) 不同于旧硬件上的 (a*b)。例如,结果可能非常相近,但仍存在细微差别。在进行索引之前一直保留不精确的计算值可解决这种分离/附加的不一致性问题,因为在进行索引和索引视图的数据库更新和维护期间,在相同的计算机上评估了所有表达式。

通用语言运行时 (CLR) 类型。SQL Server 2005 的一个主要的新功能是支持基于 CLR 的用户定义的类型 (UDT) 和 UDF。假如列或表达式具有确定性或是永久且精确的,或者二者兼具,那么就可在 CLR UDT 列或从这些列派生而来的表达式上定义索引视图。不能在索引视图上使用 CLR 用户定义的聚合。

优化器匹配查询和索引视图(使之可在查询计划中使用)的功能经扩展包含:

新的表达式类型,位于查询或视图的 Select 列表或条件中,涉及:

标量表达式(比如 (a+b)/2)。

标量聚合。

标量 UDF。

间隔归入。优化器可检测索引视图定义中的间隔条件是否覆盖或“归入”查询中的间隔条件。例如,优化器可确定“a>10 and a<20”覆盖“a>12 and a<18”。

表达式等价。某些表达式虽然在语法上有所不同,但最终的结果却相同,那么可以将其视为等价。例如,“a=b and c<>10”与“10<>c and b=a”等价。

另外,如果数据库中存在大量索引视图,那么对比在其上定义视图的表的编译性能,SQL Server 2005 通常要比 SQL Server 2000 快很多。.

返回页首

设计注意事项

对数据库系统确定一组适当的索引可能很复杂。如果在设计一般索引时需要考虑众多可能性,那么对架构添加索引视图会大幅提高设计和潜在结果的复杂性。例如,索引视图可用于:

查询中引用的表的任何子集。

该表子集的查询中的条件的任何子集。

组合的列。

聚合函数(比如:SUM)。

应同时设计表和索引视图上的索引,以便从每个构造中获得最佳结果。由于索引和索引视图对给定查询可能都很有用,因此分开设计会导致多余的建 议,从而产生较高的存储和维护开销。优化数据库的物理设计时,必须权衡一组不同的查询和数据库系统必须支持的更新的性能要求。所以,对索引视图确定一项适 当的物理设计是一种富有挑战性的任务,应尽可能使用数据库优化顾问 (Database Tuning Advisor)。

如果为建立一个特殊的查询,查询优化器考虑了许多索引视图,那么查询优化成本就会显著增加。查询优化器可能会考虑在查询中的表的任何子集上定义的所有索引视图。在拒绝视图之前,必须调查每个视图以便进行替换。这可能要花一些时间,尤其当给定查询存在数百个这类视图时。

在其上创建一个唯一的聚集索引之前,视图必须满足几项要求。在设计阶段,考虑这些要求:

视图以及视图中引用的所有表必须在相同的数据库中,并具有相同的所有者。

索引视图不必包含查询中引用的供优化器使用的所有表。

在视图上创建任何其他的索引之前,必须先创建一个唯一的聚集索引。

在创建基表、视图和索引时,以及基表和视图中的数据被修改时,必须正确设置某些 SET 选项(在本文档后面所有详述)。此外,除非这些 SET 选项正确无误,否则查询优化器不会考虑索引视图。

必须使用架构绑定创建视图,并且还必须通过 SCHEMABINDING 选项创建该视图中引用的任何用户定义的函数。

需要额外的磁盘空间来保存索引视图所定义的数据。

设计方针

设计索引视图时考虑这些指导方针:

设计可供几个查询或多项操作使用的索引视图。

例如,包含列的 SUM 和 COUNT_BIG 的索引视图可供包含函数 SUM、COUNT、COUNT_BIG 或 AVG 的查询使用。查询的速度会更快,因为只需对视图中少量的行进行检索,而不必检索基表中所有的行,而且执行 AVG 函数所需的一部分计算已经完成。

使索引键保持简洁。

通过在索引键中尽可能使用最少的列和字节,可对索引视图的列实现更高效的访问,因为索引视图的列更窄,键比较的速度较更宽的键快一些。另外,在索引视图上定义的每个非聚集索引中,聚集索引键都被用作行定位器。较大的索引键的成本随视图上非聚集索引的数量成比例增长。

考虑最终索引视图的大小。

对于纯聚合,如果索引视图的大小与原始表的大小不相上下,可能就不会实现巨大的性能改善。

设计多个较小的索引视图来局部加速过程。

可能无法总对整个查询设计一个索引视图。如要怎么做,考虑创建若干个索引视图,各执行部分查询。

考虑这几个例子:

经常执行的查询会在一个数据库中聚合数据,并在另一个数据库中聚合数据,然后再联接结果。因为索引视图无法从多个数据库引用表,所以用户不 能设计一个视图来执行整个过程。但是,可以在每个数据库中创建一个索引视图来进行各个数据库的聚合操作。如果优化器可匹配索引视图和现有的查询,那么至少 聚合处理的速度会更快,同时不必对现有的查询进行重新编码。虽然联接处理不会加快,但整个查询将变快,因为其使用存储在索引视图中的聚合。

经常执行的查询聚合来自几个表的数据,然后使用 UNION 合并结果。索引视图不支持 UNION。可设计若干个视图来执行每个聚合操作。而后,优化器可选择索引视图来加快查询,而不必对查询进行重新编码。虽然未改进 UNION 处理,但改善了各个聚合过程。

有能帮助选择索引视图的工具吗?

数据库优化顾问 (DTA3) 是 SQL Server 2005 的一项功能,可帮助管理员优化物理数据库设计。除了建议使用基表上的索引以及表和索引分区策略外,DTA 还推荐使用索引视图。使用 DTA 可加强管理员确定索引、索引视图和分区策略(可优化对数据库执行的查询的典型组合的性能)的组合的能力。DTA 会向用户推荐广泛的索引视图。其中包括运用 SQL Server 2005 的索引视图的新功能(在“SQL Server 2005 的索引视图有哪些新增功能?”一节有所描述)的索引视图。DTA 并没有排除让数据库管理在设计物理存储结构时做出恰当判断的需要。但是,它可以简化物理数据库的设计过程。DTA 通过推荐一组假定的索引,索引视图和分区结果,与基于成本的查询优化器协同工作。DTA 使用优化器来估计当使用和不使用这些结构时的工作负荷成本,并推荐可提供较低的总成本的结构。

因为数据库优化顾问强制执行所有必须的 SET 选项(确保结果集正确无误),所以将成功完成索引视图的创建。然而,如果未能按要求设置选项,用户的应用程序可能无法运用这些视图。如果未按要求指定 SET 选项,对加入索引视图定义的表执行的插入、更新或删除操作就有可能失败。

更新数据时索引视图会有什么变化?

与其他任何索引一样,当基表数据变化时,SQL Server 会自动维护索引视图。对于一般索引,每个索引都直接与一个表相关联。随着在基础表上执行每一项 Insert、Update 或 Delete 操作,索引将被相应地更新,从而使保存在索引中的值总是与表保持一致。

索引视图也得到相同的维护;但是,如果视图引用了若干个表,那么更新任何一个表都需要更新索引视图。不同于一般索引,在任何参与的表中插 入一行都可能导致索引视图中发生多行更改。这是因为所插入的行可能与另一个表的多个行相联接。更新和删除行的情况也一样。因此,索引视图的维护成本可能比 维护表上的索引更高。相反,维护具有高选择性条件的索引视图的成本可能要比维护表上的索引低得多,因为多数对视图所引用的基表的插入、删除和更新操作不会 影响视图。不用访问其他数据库数据就可为索引视图筛选掉这些操作。

在 SQL Server 中,可更新某些视图。当某个视图可更新时,将使用 Insert、Update 和 Delete 语句通过视图直接修改底层基表。在视图上创建索引不会阻止视图的更新。索引视图的更新确实会导致视图下基表的更新。这些更新会作为索引视图维护的一部分自 动传播回索引视图。有关可更新的视图的详细信息,参阅面向 SQL Server 2005 的 SQL Server 联机丛书中的“通过视图修改数据”。

 

维护成本注意事项

设计索引视图时应考虑下面这几点:

索引视图的数据库需要附加存储。索引视图的结果集在物理上通过与典型表存储相似的方式保留在数据库中。

SQL Server 会自动维护视图;因此,对定义了视图的基表进行的任何更改都可能引发对索引视图进行一项或多项更改。所以,将产生额外的维护开销。

视图所获得的净性能提升为其所实现的总查询执行成本节约与存储和维护成本的差值。

比较容易获得接近于视图所需的存储。通过 SQL Server Management Studio 工具——显示预计的执行计划,评估视图定义所封装的 Select 语句。该工具将生成查询所返回的行数和行大小的近似值。通过将这两个值相乘,就可以获得接近于可能的视图大小;但是,只是近似。只有在视图定义中执行查询 或在视图上创建索引,才能确定视图上索引的实际大小。

从 SQL Server 所执行的自动维护注意事项的角度来看,显示预计的执行计划功能可能会让用户在一定程度上了解这一开销的影响。如果通过 SQL Server Management Studio 评估修改视图的语句(视图上的 Update、基表中的 Insert),对该语句显示的执行计划将包括该语句的维护操作。如果就该操作在生产环境中所要执行的次数考虑该成本,那么可能会产生视图维护成本。

通常建议尽可能对视图或其底下的基表成批(而非单独)执行任何修改或更新操作。这样就会降低视图维护开销。

返回页首

创建索引视图

创建索引视图所需的步骤对于视图的成功执行至关重要。

1.

针对将在视图中引用的所有现有表,确认 ANSI_NULLS 的设置正确无误。

2.

创建任何新表之前,确认对下表所示的当前会话正确设置了 ANSI_NULLS。

3.

创建任何新表之前,确认对下表所示的当前会话正确设置了 ANSI_NULLS 和 QUOTED_IDENTIFIER。

4.

确认视图定义具有确定性。

5.

使用 WITH SCHEMABINDING 选项创建视图。

6.

在视图上创建唯一的聚集索引之前,确认会话的 SET 选项的设置正确无误,如下图所示。

7.

在视图上创建唯一的聚集索引。

8.

可用 OBJECTPROPERTY 函数检查现有表或视图上 ANSI_NULLS 和 QUOTED_IDENTIFIER 的值。

使用 SET 选项获得一致的结果

如果在执行查询时对当前会话启用了不同的 SET 选项,评估相同的表达式可在 SQL Server 2005 中产生不同的结果。例如,SET 选项 CONCAT_NULL_YIELDS_NULL 被设为 ON 后,表达式 'abc' + NULL 会返回值 NULL。但当 CONCAT_NULL_YIEDS_NULL 被设为 OFF 后,相同的表达式会生成 'abc'。对于当前会话和视图所引用的对象,索引视图需要几个 SET 选项的固定值,以确保正确维护视图并返回一致的结果。

只要存在下列条件,就必须按下表中“必需的值”一列所示的值对当前会话设置 SET 选项:

创建了索引视图。

在加入索引视图的任何表上执行了任何 Insert、Update 或 Delete 操作。

查询优化器用索引视图生成查询计划。

SET 选项
必需的值
默认服务器值
OLE DB 和 ODBC 值
DB LIB 值

ANSI_NULLS

ON

OFF

ON

OFF

ANSI_PADDING

ON

ON

ON

OFF

ANSI_WARNINGS

ON

OFF

ON

OFF

CONCAT_NULL_YIELDS_NULL

ON

OFF

ON

OFF

NUMERIC_ROUNDABORT

OFF

OFF

OFF

OFF

QUOTED_IDENTIFIER

ON

OFF

ON

OFF

ARITHABORT4 选项必需被设为 ON,以便使当前会话创建索引视图,但是在 SQL Server 2005 中,只要 ANSI_WARNINGS 的值为 ON,该选项就会自动被设为 ON,所以不必对其进行设置。如果使用 OLE DB 或 ODBC 服务器连接,只需修改 ARITHABORT 设置的值。必须使用 sp_configure 在服务器级别或使用 SET 命令从应用程序正确设置所有 DB LIB 值。有关 SET 选项的详细信息,参阅 SQL Server 联机丛书中的“使用选项”。

使用具有确定性的功能

索引视图的定义必须具有确定性。如果选择列表中的所有表达式以及 Where 和 GROUP BY 子句都具有确定性,那么视图就具有确定性。具有确定性的表达式总是在通过一组特定的输入值对其进行评估时,返回相同的结果。只有具有确定性的函数才会加入 具有确定性的表达式。例如,DATEADD 函数具有确定性,因为对于任何给定的一组参数值,该函数总对它的三个参数返回相同的结果。GETDATE 不具有确定性,因为它总调用相同的参数,而其返回的值在每次执行时都会发生变化。详细信息,参阅面向 SQL Server 2005 的 SQL Server 联机丛书中的“具有和不具有确定性的函数”。

即使某个表达式具有确定性(如果其包含浮点表达式),确切的结果可能依处理器体系结构或微码的版本而定。为了在计算机间迁移数据库时确保 SQL Server 2005 中数据的完整性,这种表达式只能作为索引视图的非键列加入。不含浮点表达式的具有确定性的表达式被认为是精确的。只有永久和/或精确的具有确定性的表达式 才可加入键列以及索引视图的 Where 或 GROUP BY 子句。永久性表达式是对已保存列的引用,包括一般列和标为 PERSISTED 的计算列。

用 COLUMNPROPERTY 函数和 IsDeterministic 属性确定视图列是否具有确定性。用 COLUMNPROPERTY 函数和 IsPrecise 属性确定带有 SCHEMABINDING 的视图中的具有确定性的列是精确的。如果属性为 TRUE,COLUMNPROPERTY 将返回 1;如为 FALSE,则返回 0;而如果为 NULL,则表示无效输入。例如,在此脚本中

Create TABLE T(a int, b real, c as getdate(), d as a+b)
Create VIEW VT WITH SCHEMABINDING AS Select a, b, c, d FROM dbo.T
Select object_id('VT'), COLUMNPROPERTY(object_id('VT'),'b','IsPrecise')

Select 对 IsPrecise 返回 0,因为 b 列为实型。可通过 COLUMNPROPERTY 做一些实验,确认 T 的其他列是否具有确定性并是精确的。

其他要求

可索引的视图集合是可能的视图集合的一个子集。任何可索引的视图在有或没有索引的情况下都可存在。

除了设计方针(“使用 SET 选项获得一致的结果”和“使用具有确定性的函数”这两节)中所列的要求外,还必须满足下列要求,以便在视图上创建唯一的聚集索引。

有关基表的要求

视图所引用的基表必须具有在创建表时所设的 SET 选项 ANSI_NULLS 的正确的值。可用 OBJECTPROPERTY 函数检查现有表上的 ANSI_NULLS 的值。

有关函数的要求

必须使用 WITH SCHEMABINDING 选项创建视图所引用的用户定义的函数。

有关视图的要求

必须使用 WITH SCHEMABINDING 选项创建视图。

必须由使用双结构名称 (schemaname.tablename) 的视图引用表。

必须由使用双结构名称 (schemaname.functionname) 的视图引用用户定义的函数。

必须正确设置 SET 选项 ANSI_NULLS 和 QUOTED_IDENTIFIER。

视图限制

如要在 SQL Server 2005 中的视图上创建一个索引,相应的视图定义必须包含:

ANY、NOT ANY

OPENROWSET、OPENQUERY、OPENDATASOURCE

不精确的(浮型、实型)值上的算术

OPENXML

COMPUTE、COMPUTE BY

ORDER BY

CONVERT 生成一个不精确的结果

OUTER 联接

COUNT(*)

引用带有一个已禁用的聚集索引的基表

GROUP BY ALL

引用不同数据库中的表或函数

派生的表(FROM 列表中的子查询)

引用另一个视图

DISTINCT

ROWSET 函数

EXISTS、NOT EXISTS

自联接

聚合结果(比如:SUM(x)+SUM(x))上的表达式

STDEV、STDEVP、VAR、VARP、AVG

全文谓词 (CONTAINS、FREETEXT、CONTAINSTABLE、FREETEXTTABLE)

子查询

不精确的常量(比如:2.34e5)

可为空的表达式上的 SUM

内嵌或表值函数

表提示(比如:NOLOCK)

MIN、MAX

text、ntext、image、filestream 或 XML 列

不具有确定性的表达式

TOP

非 unicode 排序

UNION

SQL Server 2005 可检测到的矛盾情况表示视图将为空(比如,当 0=1 及 …)

注意 索引视图可能包含浮型和实型列;但是,如果这类列为非永久性的计算列,则不能包含在聚集索引键中。

GROUP BY 限制

如果存在 GROUP BY,VIEW 定义为:

一定包含 COUNT_BIG(*)。

一定不包含 HAVING、CUBE、ROLLUP 或 GROUPING()。

这些限制仅适用于索引视图定义。即便不能满足上述 GROUP BY 限制,查询也可以在其执行计划中使用索引视图。

有关索引的要求

执行 Create INDEX 语句的用户必须是视图所有者。

如果视图定义包含 GROUP BY 子句,唯一的聚集索引的键只能引用 GROUP BY 子句所指定的列。

一定不能在启用 IGNORE_DUP_KEY 选项的情况下创建索引。

返回页首

示例

本节中的例子阐述了如何结合两类主要的查询使用索引视图:聚合和联接。同时,说明了查询优化器在确定某个索引视图是否适用时所用的条件。有关完整的条件列表的信息,参阅“查询优化器如何使用索引视图”。

这些查询基于 AdventureWorks 中的表。AdventureWorks 是 SQL Server 2005 所提供的示例数据库,并可作为写入方式来执行。在创建视图前后,用户可能想用 SQL Server Management Studio 中显示预计的执行计划工具,来查看查询优化器所选择的计划。虽然这些例子说明了优化器选择低成本执行计划的方式,但是 AdventureWorks 示例由于太小而无法显示出性能方面的提升。

在开始运用这些示例之前,确保通过运行下列命令对会话设置正确的选项:

设置

SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
SET CONCAT_NULL_YIELDS_NULL ON
SET NUMERIC_ROUNDABORT OFF
SET QUOTED_IDENTIFIER ON
SET ARITHABORT ON

下列查询显示了两种方法用于从 Sales.SalesOrderDetail 表返回具有最大总折扣的五个产品。

查询 1

Select TOP 5 ProductID, Sum(UnitPrice*OrderQty) -
Sum(UnitPrice*OrderQty*(1.00-UnitPriceDiscount)) AS Rebate
FROM Sales.SalesOrderDetail
GROUP BY ProductID
ORDER BY Rebate DESC

查询 2

Select TOP 5 ProductID,
SUM(UnitPrice*OrderQty*UnitPriceDiscount) AS Rebate
FROM Sales.SalesOrderDetail
GROUP BY ProductID
ORDER BY Rebate DESC

查询优化器所选的执行计划包含:

一个聚集索引扫描,位于估计行数为 121,317 的 Sales.SalesOrderDetail 表上。

一个哈希匹配/聚合操作符,用于将所选的行放入基于 GROUP BY 列的哈希表,并计算每行的 SUM 聚合。

一个 TOP 5 分类操作符,基于 orDER BY 子句。

视图 1

添加包含 Rebate 列所需聚合的索引视图将更改“查询 1”的查询执行计划。在大型表(含数百万行)上,查询的性能也会得到大幅提升。

Create VIEW Vdiscount1 WITH SCHEMABINDING AS
Select SUM(UnitPrice*OrderQty) AS SumPrice,
SUM(UnitPrice*OrderQty*(1.00-UnitPriceDiscount)) AS SumDiscountPrice,
COUNT_BIG(*) AS Count, ProductID
FROM Sales.SalesOrderDetail
GROUP BY ProductID
GO
Create UNIQUE CLUSTERED INDEX VDiscountInd ON Vdiscount1 (ProductID)

第一个查询的执行计划显示 Vdiscount1 视图被优化器所用。然而,该视图将不被第二个查询所用,因为其不包含 SUM(UnitPrice*OrderQty*UnitPriceDiscount) 聚合。可再创建一个索引视图,来同时应付这两个查询。

视图 2

Create VIEW Vdiscount2 WITH SCHEMABINDING AS
Select SUM(UnitPrice*OrderQty)AS SumPrice,
SUM(UnitPrice*OrderQty*(1.00-UnitPriceDiscount))AS SumDiscountPrice,
SUM(UnitPrice*OrderQty*UnitPriceDiscount)AS SumDiscountPrice2,
COUNT_BIG(*) AS Count, ProductID
FROM Sales.SalesOrderDetail
GROUP BY ProductID
GO
Create UNIQUE CLUSTERED INDEX VDiscountInd ON Vdiscount2 (ProductID)

使用这个索引视图,在丢弃 Vdiscount1 后,这两个查询的查询执行计划现在包含:

一个聚集索引扫描,位于估计行数为 266 的 Vdiscount2 视图上

一个 TOP 5 分类函数,基于 orDER BY 子句

查询优化器选择了该视图,因为虽然没有在查询中引用该视图,但其提供了最低的执行成本。

查询 3

“查询 3”与上述查询类似,但 ProductID 被列 SalesOrderID (未包含在视图定义中)所替换。这违反了条件:视图定义中表上的选择列表中的所有表达式必须派生自视图选择列表,以便使用查询计划中的索引视图。

Select TOP 3 SalesOrderID,
SUM(UnitPrice*OrderQty*UnitPriceDiscount) orderRebate
FROM Sales.SalesOrderDetail
GROUP BY SalesOrderID
ORDER BY orderRebate DESC

必须用一个单独的索引视图来应付该查询。可修改 Vdiscount2 以包含 SalesOrderID;但是,结果视图将和原始表包含同样多的行,并不会通过使用基表提高性能。

查询 4

该查询可生成每个产品的平均价格。

Select p.Name, od.ProductID,
AVG(od.UnitPrice*(1.00-od.UnitPriceDiscount)) AS AvgPrice,
SUM(od.OrderQty) AS Units
FROM Sales.SalesOrderDetail AS od, Production.Product AS p
Where od.ProductID=p.ProductID
GROUP BY p.Name, od.ProductID

复杂的聚合(比如:STDEV、VARIANCE、AVG)不能包含在索引视图的定义中。然而,通过包含(经组合)执行复杂聚合的一些简单的聚合函数,索引视图可用以执行含 AVG 的查询。

视图 3

该索引视图包含执行 AVG 函数所需的简单聚合函数。在创建“视图 3”后执行“查询 4”时,执行计划将显示所用的视图。优化器可从视图的简单聚合列 PriceCount 派生 AVG 表达式。

Create VIEW View3 WITH SCHEMABINDING AS
Select ProductID, SUM(UnitPrice*(1.00-UnitPriceDiscount)) AS Price,
COUNT_BIG(*) AS Count, SUM(OrderQty) AS Units
FROM Sales.SalesOrderDetail
GROUP BY ProductID
GO
Create UNIQUE CLUSTERED INDEX iv3 ON View3 (ProductID)

查询 5

该查询与“查询 4”相同,但包含一个附加的搜索条件。即使附加的搜索条件只从未包含在视图定义中的表引用列,“视图 3”也将作用于该查询。

Select p.Name, od.ProductID,
AVG(od.UnitPrice*(1.00-UnitPriceDiscount)) AS AvgPrice,
SUM(od.OrderQty) AS Units
FROM Sales.SalesOrderDetail AS od, Production.Product AS p
Where od.ProductID=p.ProductID AND p.Name like '%Red%'
GROUP BY p.Name, od.ProductID

查询 6

查询优化器无法对该查询使用“视图 3”。添加的搜索条件 od.UnitPrice>10 包含来自视图定义中表的列,但该列不显示在 GROUP BY 列表中,而搜索谓词也不显示在视图定义中。

Select p.Name, od.ProductID,
AVG(od.UnitPrice*(1.00-UnitPriceDiscount)) AS AvgPrice,
SUM(od.OrderQty) AS Units
FROM Sales.SalesOrderDetail AS od, Production.Product AS p
Where od.ProductID=p.ProductID AND p.Name like '%Red%'
GROUP BY p.Name, od.ProductID

查询 7

相反,查询优化器可对“查询 7”使用“视图 3”,因为新的搜索条件 od.ProductID in (1,2,13,41) 中定义的列包含在视图定义的 GROUP BY 子句中。

Select p.Name, od.ProductID,
AVG(od.UnitPrice*(1.00-UnitPriceDiscount)) AS AvgPrice,
SUM(od.OrderQty) AS Units
FROM Sales.SalesOrderDetail AS od, Production.Product AS p
Where od.ProductID = p.ProductID AND od.UnitPrice > 10
GROUP BY p.Name, od.ProductID

视图 4

通过包含视图定义中的 SumPrice 和 Count 列以便计算查询中的 AVG,该视图将满足“查询 6”的条件。

Create VIEW View4 WITH SCHEMABINDING AS
Select p.Name, od.ProductID,
SUM(od.UnitPrice*(1.00-UnitPriceDiscount)) AS SumPrice,
SUM(od.OrderQty) AS Units, COUNT_BIG(*) AS Count
FROM Sales.SalesOrderDetail AS od, Production.Product AS p
Where od.ProductID = p.ProductID AND od.UnitPrice > 10
GROUP BY p.Name, od.ProductID
GO
Create UNIQUE CLUSTERED INDEX VdiscountInd on View4 (Name, ProductID)

查询 8

“视图 4”上相同的索引也将用于在其中添加对表 Sales.SalesOrderHeader 的联接的查询。该查询满足条件:查询 FROM 子句中所列的表是索引视图的 FROM 子句中的表的超集。

Select p.Name, od.ProductID,
AVG(od.UnitPrice*(1.00-UnitPriceDiscount)) AS AvgPrice,
SUM(od.OrderQty) AS Units
FROM Sales.SalesOrderDetail AS od, Production.Product AS p,
Sales.SalesOrderHeader AS o
Where od.ProductID = p.ProductID AND o.SalesOrderID = od.SalesOrderID
AND od.UnitPrice > 10
GROUP BY p.Name, od.ProductID

最后两个查询在“查询 8”的基础上进行了修改。每个修改后的查询都违反了优化器的条件之一,并且不同于“查询 8”,无法使用“视图 4”。

查询 8a

“查询 8a”(Q8a) 无法使用索引视图,因为 Where 子句无法将视图定义中的 UnitPrice > 10 与查询中的 UnitPrice > 25 相匹配,而且 UnitPrice 未出现在视图中。查询搜索条件谓词必须是视图定义中的搜索条件谓词的超集。

Select p.Name, od.ProductID, AVG(od.UnitPrice*(1.00-UnitPriceDiscount))
AvgPrice, SUM(od.OrderQty) AS Units
FROM Sales.SalesOrderDetail AS od, Production.Product AS p,
Sales.SalesOrderHeader AS o
Where od.ProductID = p.ProductID AND o.SalesOrderID = od.SalesOrderID
AND od.UnitPrice > 25
GROUP BY p.Name, od.ProductID

查询 8b

注意,表 Sales.SalesOrderHeader 不加入索引视图 V4 定义。尽管这样,在该表上添加一个谓词将不允许使用索引视图,因为所添加的谓词可能会更改或消除加入下方“查询 8b”所示的聚合的其他行。

Select p.Name, od.ProductID, AVG(od.UnitPrice*(1.00-UnitPriceDiscount))
AS AvgPrice, SUM(od.OrderQty) AS Units
FROM Sales.SalesOrderDetail AS od, Production.Product AS p,
Sales.SalesOrderHeader AS o
Where od.ProductID = p.ProductID AND o.SalesOrderID = od.SalesOrderID
AND od.UnitPrice > 10 AND o.OrderDate > '20040728'
GROUP BY p.Name, od.ProductID

视图 4a

“视图 4a”通过将 UnitPrice 列包含在选择列表和 GROUP BY 子句中,扩展了“视图 4”。“查询 8a”可使用“视图 4a”,因为将进一步筛选 UnitPrice 值(已知大于 10)以便只留下大于 25 的值。以下是间隔归入的一个例子。

Create VIEW View4a WITH SCHEMABINDING AS
Select p.Name, od.ProductID, od.UnitPrice,
SUM(od.UnitPrice*(1.00-UnitPriceDiscount)) AS SumPrice,
SUM(od.OrderQty) AS Units, COUNT_BIG(*) AS Count
FROM Sales.SalesOrderDetail AS od, Production.Product AS p
Where od.ProductID = p.ProductID AND od.UnitPrice > 10
GROUP BY p.Name, od.ProductID, od.UnitPrice
GO
Create UNIQUE CLUSTERED INDEX VdiscountInd
ON View4a (Name, ProductID, UnitPrice)

视图 5

“视图 5”在其选择和 GROUP BY 列表中包含一个表达式。请注意,LineTotal 是一个计算列,因此本身是一个表达式。反过来,该表达式嵌套在对 FLOOR 函数的调用中。

Create VIEW View5 WITH SCHEMABINDING AS
Select FLOOR(LineTotal) FloorTotal, COUNT_BIG(*) C
FROM Sales.SalesOrderDetail
GROUP BY FLOOR(LineTotal)
GO
Create UNIQUE CLUSTERED INDEX iView5 ON View5(FloorTotal)

查询 9

“查询 9”在其选择和 GROUP BY 列表中包含表达式 FLOOR(LineTotal)。通过对 SQL Server 2005 中表达式的视图匹配的新扩展,该查询使用“视图 5”上的索引。

Select TOP 5 FLOOR(LineTotal), Count(*)
FROM Sales.SalesOrderDetail
GROUP BY FLOOR(LineTotal)
ORDER BY COUNT(*) DESC

视图 6

“视图 6”存储月末三天中有关线项目的信息。这样可将这些行聚集在少量页面上,从而可以迅速应对这些天里对 Sales.SalesOrderDetail 的查询。

Create VIEW View6 WITH SCHEMABINDING AS
Select SalesOrderID, SalesOrderDetailID, CarrierTrackingNumber, orderQty,
ProductID, SpecialOfferID, UnitPrice, UnitPriceDiscount, rowguid,
ModifiedDate
FROM Sales.SalesOrderDetail
Where ModifiedDate IN ( convert(datetime, '2004-07-31', 120),
convert(datetime, '2004-07-30', 120),
convert(datetime, '2004-07-29', 120) )
GO
Create UNIQUE CLUSTERED INDEX VEndJulyO4Ind
ON View6(SalesOrderID, SalesOrderDetailID)
GO

查询 10

下面的查询可匹配“视图 6”,同时系统可生成一个计划,用于扫描视图上的 VendJuly04Ind 索引,但不扫描整个 Sales.SalesOrderDetail 表。此查询还说明了表达式等价(由于查询中日期的顺序不同于视图,而且数据格式也不同)和谓词归入(由于查询要求将结果的子集保存在视图中)。

Select h.*, SalesOrderDetailID, CarrierTrackingNumber, orderQty,
ProductID, SpecialOfferID, UnitPrice, UnitPriceDiscount, d.rowguid,
d.ModifiedDate
FROM Sales.SalesOrderHeader as h, Sales.SalesOrderDetail as d
Where (d.ModifiedDate = '20040729' or d.ModifiedDate = '20040730')
and d.SalesOrderID=h.SalesOrderID

视图 7

开发人员有时还会发现使用索引视图强制专门的完整性约束很方便。例如,可通过索引视图强制约束:“除非列中存在多个 0 值,否则表 T 的列 a 就是唯一的”。下方索引视图“视图 7”就强制了这一约束。如果运行下面的脚本,其将成功运行直至最终的插入操作。该语句被禁止,因为其添加了一个非零重复值。

USE tempdb
GO
Create TABLE T(a int)
GO
Create VIEW View7 WITH SCHEMABINDING
AS Select a
FROM dbo.T
Where a <> 0
GO
Create UNIQUE CLUSTERED INDEX IV on View7(a)
GO
-- legal:
Insert INTO T VALUES(1)
Insert INTO T VALUES(2)
Insert INTO T VALUES(0)
Insert INTO T VALUES(0) -- duplicate 0
 
-- dissalowed:
Insert INTO T VALUES(2)

返回页首

有关索引视图的常见问题

问:为何对可创建索引的视图类型存在限制?

答:为了确保在逻辑上可对视图进行增量维护,限制创建维护成本较高的视图,并限制 SQL Server 实施的复杂性。较大的视图集不具有确定性并与内容相关;其内容的“更改”独立于 DML 操作。无法对这些内容进行索引。在其定义中调用 GETDATE 或 SUSER_SNAME 的任何视图就属于这类视图。

问:视图上的第一个索引为何必须为 CLUSTERED 和 UNIQUE?

答:必须为 UNIQUE 以便在维护索引视图期间,轻松地按键值查找视图中的记录,并阻止创建带有重复项目的视图(要求维护特殊的逻辑)。必须为 CLUSTERED,因为只有聚集索引才能在强制唯一性的同时存储行。

问:为何查询优化器不选取我的索引视图用于查询计划?

答:优化器不选取索引视图主要有三种原因:

(1) 使用 SQL Server Enterprise 或 Developer 版本之外的其他版本。只有 Enterprise 和 Developer 版本才支持自动的查询对索引视图匹配。按名称引用索引视图并包含 NOEXPAND 提示,让查询处理器使用所有其他版本中的索引视图。

(2) 使用索引视图的成本可能超出从基表获取数据的成本,或者查询过于简单,使得针对基表的查询的速度既快又容易查找。当在较小的表上定义索引视图时,经常会发 生这种情况。如要强制查询处理器使用索引视图,那么可使用 NOEXPAND 提示。如果最初不通过显式的方式引用视图,这样做就可能要求重新编写查询。您可获得带有 NOEXPAND 的查询的实际成本,并将之与不引用该视图的查询计划的实际成本相比较。如果两者的成本相近,那么您就可以认定用不用索引视图都不重要。

(3) 查询优化器不将查询与索引视图相匹配。重新检查视图和查询的定义,确保两者在结构上可相匹配。CASTS、converts 以及其他在逻辑上不会更改查询结果的表达式可能会阻止匹配。另外,表达式规范化和等价以及 SQL Server 执行的归入测试方面存在一些限制。可能无法显示某些等价表达式是相同的,或者逻辑上被其他表达式归入的表达式被真正归入,因此可能会错失匹配。

问:我每周更新一次数据仓库。索引视图使查询速度大大提升,却降低了每周更新的速度?该怎么办呢?

答:可以考虑在每周更新前丢弃索引视图,更新完后再重新创建。

问:我的视图存在重复项目,而我确实想对其进行维护。该怎么办呢?

答:可以考虑创建一个视图,按您所要的视图中的所有列和表达式进行分组,并添加一个 COUNT_BIG(*) 列,然后在组合的列上创建一个唯一的聚集索引。分组过程可确保唯一性。虽然不是完全相同的视图,但可以满足您的需要。

问:我在一个视图上定义了另一个视图。SQL Server 不让我索引顶级视图。该怎么办呢?

答:可以考虑手动将嵌套视图的定义扩展到顶级视图,然后对其进行索引(索引最低层的视图,或者不索引该视图)。

问:为何一定要对索引视图定义 WITH SCHEMABINDING?

答:为了

使用 schemaname.objectname 明确识别视图所引用的所有对象,而不管是哪个用户访问该视图,同时

不会以导致视图定义非法或强制 SQL Server 在该视图上重新创建索引的方式,更改视图定义中所引用的对象。

问:为何不能在索引视图中使用 OUTER JOIN?

答:当将数据插入基表时,行会在逻辑上从基于 OUTER JOIN 的索引视图上消失。这会使执行 OUTER JOIN 视图的增量更新变得相对复杂,而执行性能将比基于标准 (INNER) JOIN 的视图慢一些。

返回页首

如需获得更多信息

Microsoft SQL Server 2005 联机丛书包含更多有关索引视图的信息。如需其他信息,参见下列资源:

Microsoft SQL Server 网站http://www.microsoft.com/sql.

Microsoft SQL Server 开发中心:http://msdn.microsoft.com/sqlserver.

SQL Server 杂志http://www.sqlmag.com.

microsoft.public.SQLServer.server 和 microsoft.public.SQLServer.datawarehouse 新闻组:news://news.microsoft.com.

有关 SQL Server 的 Microsoft 官方课程。有关最新的课程信息,请访问http://www.microsoft.com/trainingandservices

1
该视图不一定非得被完全具体化。查询可包含其他可应用于该视图所引用的表和视图的谓词、联接或聚合。该视图不再需要被完全具体化

2
当优化器将两个 FROM 子句合并成一个(联接子查询或联接转换派生表)时,存在一些例外情况。如果发生这类情况,原始查询中涉及索引视图替换的 FROM 子句可能不止一个。

3
DTA 在 SQL Server 2000 的索引优化向导上进行了改进。

4
在 SQL Server 2000 中创建索引视图之前确保将 ARITHABORT 设为 ON。

[SQL]数据库查询优化

mikel阅读(952)

1         使用SET NOCOUNT ON 选项:

缺 省地,每次执行SQL语句时,一个消息会从服务端发给客户端以显示SQL语句影响的行数。这些信息对客户端来说很少有用。通过关闭这个缺省值,你能减少在 服务端和客户端的网络流量,帮助全面提升服务器和应用程序的性能。为了关闭存储过程级的这个特点,在每个存储过程的开头包含“SET NOCOUNT ON”语句。

2         正确使用UNION和UNION ALL:

    许 多人没完全理解UNION和UNION Select是怎样工作的,因此,结果浪费了大量不必要的SQLServer资源。当使用UNION时,它相当于在结果集上执行Select DISTINCT。换句话说,UNION将联合两个相类似的记录集,然后搜索重复的记录并排除。如果这是你的目的,那么使用UNION是正确的。但如果你 使用UNION联合的两个记录集没有重复记录,那么使用UNION会浪费资源,因为它要寻找重复记录,即使你确定它们不存在。

所以如果你知道你要联合的记录集里没有重复,那么你要使用UNION ALL,而不是UNION。UNION ALL联合记录集,但不搜索重复记录,这样减少SQLServer资源的使用,从而提升性能。

3         尽量不用Select * :

    绝 大多数情况下,不要用 * 来代替查询返回的字段列表,用 * 的好处是代码量少、就算是表结构或视图的列发生变化,编写的查询SQL语句也不用变,都返回所有的字段。但数据库服务器在解析时,如果碰到 *,则会先分析表的结构,然后把表的所有字段名再罗列出来。这就增加了分析的时间。

4         慎用Select DISTINCT:

    DISTINCT子句仅在特定功能的时候使用,即从记录集中排除重复记录的时候。这是因为DISTINCT子句先获取结果集然后去重,这样增加SQLServer有用资源的使用。当然,如果你需要去做,那就只有去做了。

当如果你知道Select语句将从不返回重复记录,那么使用DISTINCT语句对SQLServer资源不必要的浪费。

5         少用游标:

    任何一种游标都会降低SQLServer性能。有些情况不能避免,大多数情况可以避免。所以如果你的应用程序目前正在使用TSQL游标,看看这些代码是否能够重写以避免它们。如果你需要一行一行的执行操作,考虑下边这些选项中的一个或多个来代替游标的使用:

使用临时表

使用WHILE循环

使用派生表

使用相关子查询

使用CASE语句

使用多个查询

上面每一个都能取代游标并且执行更快。 如果你不能避免使用游标,至少试着提高它们的速度,找出加速游标的方法。

6         选择最有效率的表名顺序:

    SQLSERVER的 解析器按照从右到左的顺序处理FROM子句中的表名,因此FROM子句中写在最后的表(基础表driving table)将被最先处理,在FROM子句中包含多个表的情况下,必须选择记录条数最少的表作为基础表,当SQLSERVER处理多个表时,会运用排序及 合并的方式连接它们。首先,扫描第一个表(FROM子句中最后的那个表)并对记录进行排序;然后扫描第二个表(FROM子句中最后第二个表);最后将所有 从第二个表中检索出的记录与第一个表中合适记录进行合并。

例如: 表 TAB1有 16384 条记录,表 TAB2 有5条记录,选择TAB2作为基础表 (最好的方法):

select count(*) from TAB1 a, TAB2 b

选择TAB1作为基础表 (不佳的方法):

select count(*) from TAB2 a, TAB1 b

如果有3个以上的表连接查询,那就需要选择交叉表(intersection table)作为基础表,交叉表是指那个被其他表所引用的表。

7         使用表的别名(Alias):

当在SQL语句中连接多个表时,请使用表的别名并把别名前缀于每个Column上,这样可以减少解析的时间并减少那些由Column歧义引起的语法错误。

8         SARG你的Where条件:

    ARGE来 源于"Search Argument"(搜索参数)的首字母拼成的"SARG",它是指Where子句里,列和常量的比较。如果Where子句是sargable(可 SARG的),这意味着它能利用索引加速查询的完成。如果Where子句不是可SARG的,这意味着Where子句不能利用索引(或至少部分不能利用), 执行的是全表或索引扫描,这会引起查询的性能下降。

在Where子句里不可 SARG的搜索条件如"IS NULL", "<>", "!=", "!>", "!<", "NOT", "NOT EXISTS", "NOT IN", "NOT LIKE"和"LIKE '%500'",通常(但不总是)会阻止查询优化器使用索引执行搜索。另外在列上使用包括函数的表达式、两边都使用相同列的表达式、或和一个列(不是常 量)比较的表达式,都是不可SARG的。

并不是每一个不可SARG的Where子句都注定要全表扫描。如果Where子句包括两个可SARG和一个不可SARG的子句,那么至少可SARG的子句能使用索引(如果存在的话)帮助快速访问数据。

大多数情况下,如果表上 有包括查询里所有Select、JOIN、Where子句用到的列的覆盖索引,那么覆盖索引能够代替全表扫描去返回查询的数据,即使它有不可SARG的 Where子句。但记住覆盖索引尤其自身的缺陷,如此经常产生宽索引会增加读磁盘I/O。某些情况下,可以把不可SARG的Where子句重写成可 SARG的子句。例如:

Where SUBSTRING(firstname,1,1) = 'm'

可以写成:

Where firstname like 'm%'

这两个Where子句有相同的结果,但第一个是不可SARG的(因为使用了函数)将运行得慢些,而第二个是可SARG的,将运行得快些。

如果你不知道特定的Where子句是不是可SARG的,在查询分析器里检查查询执行计划。这样做,你能很快的知道查询是使用了索引还是全表扫描来返回的数据。仔细分析,许多不可SARG的查询能写成可SARG的查询。下面分几点讲解Where条件的SARG。

8.1       Where子句中的连接顺序

SQLSERVER采用自下而上的顺序解析Where子句,根据这个原理,表之间的连接必须写在其他Where条件之前,那些可以过滤掉最大数量记录的条件必须写在Where子句的末尾。例如:

(低效)

Select *  FROM EMP E

Where SAL > 50000

AND JOB = ‘MANAGER’

AND 25 < (Select COUNT(*) FROM EMP Where MGR=E.EMPNO)

 

(高效)

Select * FROM EMP E

Where 25 < (Select COUNT(*) FROM EMP Where MGR=E.EMPNO)

AND SAL > 50000

AND JOB = ‘MANAGER’

8.2       避免困难的正规表达式:

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子句中采用了非开始子串,因而这个语句也不会使用索引。

8.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 ALL

Select * FROM orders Where order_num=1008

这样就能利用索引路径处理查询。

8.4       EXISTS和IN的使用:

    在 许多基于基础表的查询中,为了满足一个条件,往往需要对另一个表进行联接。   在这种情况下,使用EXISTS(或NOT EXISTS)通常将提高查询的效率。在子查询中,NOT IN子句将执行一个内部的排序和合并。无论在哪种情况下,NOT IN都是最低效的,因为它对子查询中的表执行了一个全表遍历。为了避免使用NOT IN,我们可以把它改写成外连接(Outer Joins)或NOT EXISTS。

8.5       避免在索引列上使用IS NULL和IS NOT NULL:

    避免在索引中使用任何可以为空的列,SQLSERVER将无法使用该索引。对于单列索引,如果列包含空值,索引中将不存在此记录;对于复合索引,如果每个列都为空,索引中同样不存在此记录。如果至少有一个列不为空,则记录存在于索引中。   

  如果唯一性索引建立在表的A列和B列上,并且表中存在一条记录的A,B值为(123,null),SQLSERVER将不接受下一条具有相同A,B值(123,null)的记录插入。   

  如果所有的索引列都为空,SQLSERVER将认为整个键值为空,而空不可能 等于空,因此你可以插入1000条具有相同键值的记录,当然它们都是空!因为空值不存在于索引列中,所以Where子句中对索引列进行空值比较将使 SQLSERVER停用该索引。下面的代码将会很低效(索引失效):

Select … FROM DEPARTMENT Where DEPT_CODE IS NOT NULL

8.6       避免在索引列上使用计算:

    Where子句中,如果索引列是函数的一部分,优化器将不使用索引而使用全表扫描。   例如下面的语句低效 :

Select … FROM DEPT Where SAL * 12 > 25000

    而下面的语句将是高效的:

Select … FROM DEPT Where SAL > 25000/12

请务必注意,查询中不要对索引列进行处理,如:TRIM,substring,convert等等操作。

8.7       用Where子句替换HAVING子句:

    避免使用HAVING子句,HAVING只会在检索出所有记录之后才对结果集进行过滤,这个处理需要排序、统计等操作。如果能通过Where子句限制记录的数目,那就能减少这方面的开销。

9         避免或简化排序:

应当简化或避免对大型表进行重复的排序。当能够利用索引自动以适当的次序产生输出时,优化器就避免了排序的步骤。以下是一些影响因素:

l         索引中不包括一个或几个待排序的列;

l         group by或order by子句中列的次序与索引的次序不一样;

l         排序的列来自不同的表。

为了避免不必要的排序,就要正确地增建索引,合理地合并数据库表(尽管有时可能影响表的规范化,但相对于效率的提高是值得的)。如果排序不可避免,那么应当试图简化它,如缩小排序的列的范围等。

10    临时表的使用:

    临 时表有很多特殊的用途,象用来替代游标,不过它们仍能引起性能问题,如果这个问题能消除,SQLServer将执行得更快。在永久表和临时表的数据行相同 的条件下,使用临时表没有永久表快。但有时还必须得使用临时表,如先从存储大量数据的永久表中提取符全条件的存放到临时表,然后在临时表上执行操作。如果 是直接在存储大量数据的永久表上执行操作(如:统计、循环等),其性能将大打折扣。所以,使不使用临时表,何时使用临时表,需要具体情况决定。

11    是否使用视图:

视图最大的用途是处理安 全相关的问题,而不是一些懒惰的开发人员用来存储经常使用的查询的方法。例如,如果你需要允许用户访问特定SQLServer的数据,那么你也许可以考虑 为用户(或组)创建一个视图,然后给用户访问视图而不是基表的权限。另一方面,在应用程序里,从视图选择数据没有好的理由,相反,绕过视图直接从需要的表 里获取数据。原因是许多视图(当然不是全部)返回比Select语句所需更多的数据,增加不必要的开销。

例如,假定有一个视图从 两个连接表里返回10列。你想要从视图里使用Select语句返回其中7列。实际上发生的情况是基于视图的查询先运行,返回数据,然后你的查询针对这些数 据运行。既然你仅需要7列,而不是视图返回的10列,更多不必要的数据被返回。浪费SQLServer的资源。

长久以来,大家在争论是查询视图速度快还是直接查询快,本人也不敢轻易下结论,因此作了多次试验,其结果是:基于视图查询,性能确实不会比直接写查询语句快,对于简单的查询,最多是在同一水平上。

当然,上面的测试是在没有为视图创建索引的情况下,SQLServer2000以上可以为视图创建索引,视图索引与表的索引在作用方式上非常相似。与表一样,视图可以有一个集簇索引(clustered index)和多个非集簇索引。创建视图索引后能够提高视图的性能。

如果视图不包含索引,则数据库中不保存视图返回的结果集。有的时候,我们可能要创建涉及大量记录或必须进行复杂计算的视图,比如要进行聚合分组处理或多重连接操作。如果每次引用这些视图的时候让sql server重新生成结果集,数据库开销将非常大。

12    让事务尽可能的短:

    保持TSQL事务尽可能的短。这会帮助减少锁(所有类型的锁)的数量,有助于全面提升SQLServer的性能。如果有经验,你也许要将长事务分成更小的事务组。

13    用存储过程代替直接写查询语句:

    存储过程为开发人员提供了很多好处,包括:

n         减少网络流量和响应时间,提升应用程序性能。例如,通过网络发送一个存储过程调用,而不是发送500行的TSQL将更快,资源使用更少。当每次执行SQL时,都会执行解析SQL语句、估算索引的利用率、绑定变量、读数据块等等工作。

n         存储过程执行计划能够重用,驻留在SQLServer内存的缓存里,减少服务器开销。

n         客 户端执行请求更有效率。例如,如果应用程序需要插入大量的二进制值到一个image数据列而不使用存储过程,它必须转化二进制为字符串(大小会增加一 倍),然后发送给SQLServer。当SQLServer接收到后,它必须把字符串值转回二进制格式。大量的浪费开销。存储过程能消除这个问题通过将应 用程序传给SQLServer的二进制格式作为参数,从而减少开销提升性能。

n         存储过程帮助提供代码重用。虽然这些不直接提升应用程序的性能,通过减少代码量和减少调试时间来提升开发人员的效率。

n         存储过程能封装逻辑。你能够改变存储过程代码而不影响客户端(假定你保持参数相同也不移除任何结果集的列)。这节约开发人员的时间。

n         存储过程为你的数据提供更好的安全性。如果你仅使用存储过程,你可以移除直接对表的Select、Insert、Update和Delete权限从而强迫开发人员使用存储过程访问数据。这会节约DBA的时间。

n         作为首要的常规,所有的TSQL代码都应该通过存储过程调用。

13.1   存储过程名不要以 sp_ 开头:

    对 这一准则,可能很多人会感觉纳闷,是的,我开始也纳闷过。如果创建的存储过程不是运行在Master数据库里,不要使用以sp_为前缀的名称。这个特别的 前缀是为系统存储过程保留的。尽管使用这个前缀不会禁止用户定义的存储过程的运行,但会稍微降低一些执行效率。这是因为SQLServer在执行以sp_ 为前缀的任何一个存储过程时缺省地首先试图在Master数据库里寻找,尽管那儿没有,这就浪费了寻找存储过程的时间。如果SQLServer在 Master数据库里不能找到存储过程,那么接下来会将存储过程的拥有者作为DBO去解析。如果存储过程在目前的数据库里,那么它会执行。为了避免不必要 的延迟,不要用前缀为sp_命名你的任何一个存储过程。

13.2   存储过程的拥有者要相同:

    为 了最好的性能,同一个存储过程里调用的所有对象的拥有者都应该相同,DBO更适宜。如果不是那样,即对象名相同而拥有者不同,那么SQLServer必须 执行名称判断。当发生这样的情形时,SQLServer不能使用存储过程里在内存里的执行计划,相反,它必须重新编译存储过程,从而影响性能。当从应用程 序里调用存储过程时,使用分隔符名称来调用也是重要的。如:

EXEC dbo.myProcedure

代替:

        EXEC myProcedure

这样做有两个原因,其中 一个和性能有关。首先,使用完全有分隔符的名称有助于消除那些和你要运行的存储过程有潜在的混淆,有助于禁止BUG和潜在的问题。但更重要的是,这样做 SQLServer能更直接的访问存储过程执行计划,而不是轮流访问,从而加速了存储过程的性能。当然性能提升很小,但如果你的服务器每小时要运行成千上 万或更多的存储过程,这些节约的小段时间加起来就很可观了。

14    完整性使用下的约束和触发器:

    数据库里不要执行多余的完整性特点。例如,如果你正使用主键和外键约束来强迫引用完整性,则不要添加触发器来实现相同的功能而增加不必要的开销。同样既使用约束又使用默认值或既使用约束又使用规则也会执行多余的工作。

15    在SQL中捕捉异常:

这一条准则应该不能算是优化方面的,只是编写要求。现在SQLServer2005中,新增了BEGIN TRY…END TRY BEGIN CATCH…END CATCH二个成对语句,用于捕捉运行时出现的异常。在Oracle中,可用 BEGIN…EXCEPTION…END 语句捕捉异常。

SQL代码块中加入捕捉异常的语句内,有二个好处:一是可以在SQL语句内部得到异常并作错误处理,如在错误代码块内返回自定义错误信息、ROLBACK等。这样可减少应用程序捕捉异常带来的资源开销;另外一个好处就是可以防止死锁情况的发生,当出现死锁时,SQLServer2005会抛出异常,我们就可捕捉到。

 

 

下面列出一些索引的概念,有助于设计表结构和编写SQL语句:

按照存储规则来分:

l         聚集索引:该索引中键值的逻辑顺序决定了表中相应行的物理顺序。因此一个表只能包含一个聚集索引,但该索引可以包含多个列(组合索引)。检索效率比普通索引高,但对数据新增/修改/删除的影响比较大。

l         非聚集索引:与聚集索引相对,不影响表中的数据存储顺序,检索效率比聚集索引低,对数据新增/修改/删除的影响很少。

按照维护与管理的角度来分:

l         唯一索引:惟一索引可以确保索引列不包含重复的值,可以用多个列,但是索引可以确保索引列中每个值组合都是唯一的。

l         主键索引:在数据库关系图中为表定义一个主键将自动创建主键索引,主键索引是唯一索引的特殊类型。主键索引要求主键中的每个值是唯一的。当在查询中使用主键索引时,它还允许快速访问数据。

l         普通索引:由关键字KEYINDEX定义的索引,唯一任务是加快对数据的访问速度。因此,应该只为那些最经常出现在查询条件或排序条件中的数据列创建索引。只要有可能,就应该选择一个数据最整齐、最紧凑的数据列(如整数类型的数据列)来创建索引。允许有重复的列存在。

l         复合索引:如果在两上以上的列上创建的索引,则称为复合索引。

[SQL]SQL Server 之 UNION(原创)

mikel阅读(986)

转载:http://www.cnblogs.com/netserver/archive/2008/12/25/1362409.html
UNION 将两个或更多查询的结果合并为单个结果集,该结果集包含联合查询中的所有查询的全部行。UNION 运算不同于使用联接合并两个表中的列的运算。
下面列出了使用 UNION 合并两个查询结果集的基本规则:
   1、所有查询中的列数和列的顺序必须相同。
   2、数据类型必须兼容(相同或可隐式转换)。
语法:
查询表达式1 UNION [ALL] 查询表达式2
select fields from tableA UNION select fields from tableB
查询表达式:
查询表达式或查询规范,用以返回与另一个查询规范或查询表达式所返回的数据合并的数据。作为 UNION 运算一部分的列定义可以不相同,但它们必须通过隐式转换实现兼容。如果数据类型不同,则根据数据类型优先顺序规则确定所产生的数据类型。如果类型相同,但 精度、小数位数或长度不同,则根据用于合并表达式的相同规则来确定结果。
UNION:
指定合并多个结果集并将其作为单个结果集返回。使用UNION会自动排除相同的记录,删除重复行。(类似与单一查询的DISTINCT。)
ALL:
将全部行并入结果中。其中包括重复行。如果未指定该参数,则删除重复行。

例表A:UA 例表B:UB

注意:SQL UNION会自动排除相同的记录,删除重复行。这里的相同记录指的是要用UNION合并两个查询结果的所有字段的值都相同。

select AID,ANAME,ASEX from UA union select BID,BNAME,BSEX from UB
说明:
1、UA表中有三个字段,UB表中有四个三字,但UNION考虑前后两个查询果(并不是实际表中的数据)的列数要一致,即都是三个字段,且字段的数据类型一致就行了,结果如左图所示。
如果用:select * from UA union select * from UB
或 select AID,ANAME,ASEX from UA union select BID,BNAME,BSEX,BAddress from UB
因为列数不一致,将抛出错误。
2、UNION会自动删除重复行。结果中姓名为"马艳艳"的,虽然性别、姓名都一样,但因为ID不同,则不认为是重复的记录,也就是说这里的重复记录指的是两个查询结果中的所有字段的值都是一样的。如:姓名为"邓事文"的记录则认为重复记录,只保留一条。

UNION会自动删除重复行(记录)的演示:
select AID from UA union select bid from UB
select ANAME from UA union select BNAME from UB
select ASEX from UA union select BSEX from UB
注意:因为前面的是主表,所以结果中的字段名都是UA表中的字段名。

再来看字段数据类型要一致的演示:
select AID,ANAME,ASEX from UA union select BID,BNAME,BAddress from UB
说明:
因为UA表中的ASEX与UB表中的BAddress都为varchar数据类型,所以结果如左图所示。
如果类型相同,但精度、小数位数或长度不同,则根据用于合并表达式的相同规则来确定结果。
select ANAME,ASEX from UA union select BNAME,BID from UB
select ANAME,AID from UA union select BNAME,BSEX from UB
错误提示:在将 varchar 值 '女' 转换成数据类型 int 时失败。
可以对数据类型进行转换后再UNION
select ANAME,cast(AID as varchar) from UA union select BNAME,BSEX from UB
如果用:select ANAME,AID from UA union select BNAME,BSEX from UB
不进行数据类型的转换,将会出现错误。
最后看看UNION ALL:
select AID,ANAME,ASEX from UA union all select BID,BNAME,BSEX from UB
说明:
UNION ALL 也是要将两个 SQL 语句的结果合并在一起。 UNION ALL 和 UNION 不同之处在于 UNION ALL 会将左右两个查询的结果的所有的资料都列出来,无论资料值有无重复。
左图结果中,姓名为"邓事文"的记录,在两个查询中是完全相同的。对比上面第一次试验的结果(9条记录),现在这次因为用了UNION ALL 姓名为"邓事文"的记录不管它是否重复,都分别从两个查询结果中取了出来(结果为10条记录)。