﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-Welcome to 陈俊峰's ---BeetleHeaded Man Blog !-随笔分类-DataBase</title><link>http://www.cppblog.com/Jeff-Chen/category/1417.html</link><description /><language>zh-cn</language><lastBuildDate>Sat, 31 May 2008 17:21:59 GMT</lastBuildDate><pubDate>Sat, 31 May 2008 17:21:59 GMT</pubDate><ttl>60</ttl><item><title>让你的SQL运行速度明显提高</title><link>http://www.cppblog.com/Jeff-Chen/archive/2006/05/15/7159.html</link><dc:creator>Jeff-Chen</dc:creator><author>Jeff-Chen</author><pubDate>Mon, 15 May 2006 02:18:00 GMT</pubDate><guid>http://www.cppblog.com/Jeff-Chen/archive/2006/05/15/7159.html</guid><wfw:comment>http://www.cppblog.com/Jeff-Chen/comments/7159.html</wfw:comment><comments>http://www.cppblog.com/Jeff-Chen/archive/2006/05/15/7159.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Jeff-Chen/comments/commentRss/7159.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Jeff-Chen/services/trackbacks/7159.html</trackback:ping><description><![CDATA[人们在使用SQL时往往会陷入一个误区，即太关注于所得的结果是否正确，而忽略了不同的实现方法之间可能存在的性能差异，这种性能差异在大型的或是复杂的数据库环境中（如联机事务处理OLTP或决策支持系统DSS）中表现得尤为明显。笔者在工作实践中发现，不良的SQL往往来自于不恰当的索引设计、不充份的连接条件和不可优化的where子句。在对它们进行适当的优化后，其运行速度有了明显地提高！下面我将从这三个方面分别进行总结：（为了更直观地说明问题，所有实例中的SQL运行时间均经过测试，不超过１秒的均表示为（&lt; 1秒）。 ）<br /><br />一、不合理的索引设计 <br /><br />　　例：表record有620000行，试看在不同的索引下，下面几个 SQL的运行情况： <br /><br /><br /><br />　　1.在date上建有一非个群集索引<br />select count(*) from record where date &gt; <br />''19991201'' and date &lt; ''19991214''and amount &gt; <br />2000 (25秒) <br />select date,sum(amount) from record group by date <br />(55秒) <br />select count(*) from record where date &gt; <br />''19990901'' and place in (''BJ'',''SH'') (27秒)<br /><br />　　分析： <br />　　date上有大量的重复值，在非群集索引下，数据在物理上随机存放在数据页上，在范围查找时，必须执行一次表扫描才能找到这一范围内的全部行。 <br /><br /><br />　　2.在date上的一个群集索引<br />select count(*) from record where date &gt; <br />''19991201'' and date &lt; ''19991214'' and amount &gt; <br />2000 （14秒） <br />select date,sum(amount) from record group by date <br />（28秒） <br />select count(*) from record where date &gt; <br />''19990901'' and place in (''BJ'',''SH'')（14秒）<br /><br />　　分析： <br />　　在群集索引下，数据在物理上按顺序在数据页上，重复值也排列在一起，因而在范围查找时，可以先找到这个范围的起末点，且只在这个范围内扫描数据页，避免了大范围扫描，提高了查询速度。 <br /><br /><br />　　3.在place，date，amount上的组合索引 <br />select count(*) from record where date &gt; <br />''19991201'' and date &lt; ''19991214'' and amount &gt; <br />2000 （26秒） <br />select date,sum(amount) from record group by date <br />（27秒） <br />select count(*) from record where date &gt; <br />''19990901'' and place in (''BJ, ''SH'')（&lt; 1秒）<br /><br />　　分析： <br />　　这是一个不很合理的组合索引，因为它的前导列是place，第一和第二条SQL没有引用place，因此也没有利用上索引；第三个SQL使用了place。<br /><br /><br />　　4.在date，place，amount上的组合索引 <br />select count(*) from record where date &gt; <br />''19991201'' and date &lt; ''19991214'' and amount &gt; <br />2000(&lt; 1秒) <br />select date,sum(amount) from record group by date <br />（11秒） <br />select count(*) from record where date &gt; <br />''19990901'' and place in (''BJ'',''SH'')（&lt; 1秒）<br /><br />　　分析： <br />　　这是一个合理的组合索引。它将date作为前导列，使每个SQL都可以利用索引，并且在第一和第三个SQL中形成了索引覆盖，因而性能达到了最优。 <br /><br /><br />　　5.总结： <br />　　缺省情况下建立的索引是非群集索引，但有时它并不是最佳的；合理的索引设计要建立在对各种查询的分析和预测上。一般来说： <br /><br />　　①.有大量重复值、且经常有范围查询 <br /><br />　　（between, &gt;,&lt; ，&gt;=,&lt; =）和order by、group by发生的列，可考虑建立群集索引； <br /><br />　　②.经常同时存取多列，且每列都含有重复值可考虑建立组合索引； <br /><br />　　③.组合索引要尽量使关键查询形成索引覆盖，其前导列一定是使用最频繁的列。<br />二、不充份的连接条件 <br /><br />　　例：表card有7896行，在card_no上有一个非聚集索引，表account有191122行，在 account_no上有一个非聚集索引，试看在不同的表连接条件下，两个SQL的执行情况： <br />select sum(a.amount) from account a, <br />card b where a.card_no = b.card_no（20秒）<br /><br /><br />　　将SQL改为： <br />select sum(a.amount) from account a, <br />card b where a.card_no = b.card_no and a. <br />account_no=b.account_no（&lt; 1秒）<br /><br />　　分析： <br />　　在第一个连接条件下，最佳查询方案是将account作外层表，card作内层表，利用card上的索引，其I/O次数可由以下公式估算为： <br />　　外层表account上的22541页+（外层表account的191122行*内层表card上对应外层表第一行所要查找的3页）=595907次I/O <br /><br />　　在第二个连接条件下，最佳查询方案是将card作外层表，account作内层表，利用account上的索引，其I/O次数可由以下公式估算为： <br />　　外层表card上的1944页+（外层表card的7896行*内层表account上对应外层表每一行所要查找的4页）= 33528次I/O <br /><br />　　可见，只有充份的连接条件，真正的最佳方案才会被执行。 <br /><br /><br />　　总结： <br />　　1.多表操作在被实际执行前，查询优化器会根据连接条件，列出几组可能的连接方案并从中找出系统开销最小的最佳方案。连接条件要充份考虑带有索引的表、行数多的表；内外表的选择可由公式：外层表中的匹配行数*内层表中每一次查找的次数确定，乘积最小为最佳方案。 <br /><br />　　2.查看执行方案的方法-- 用set showplanon，打开showplan选项，就可以看到连接顺序、使用何种索引的信息；想看更详细的信息，需用sa角色执行dbcc(3604,310,302)。 <br /><br /><br />三、不可优化的where子句 <br /><br />　　1.例：下列SQL条件语句中的列都建有恰当的索引，但执行速度却非常慢： <br />select * from record where <br />substring(card_no,1,4)=''5378''(13秒) <br />select * from record where <br />amount/30&lt; 1000（11秒） <br />select * from record where <br />convert(char(10),date,112)=''19991201''（10秒）<br /><br />　　分析： <br />　　where子句中对列的任何操作结果都是在SQL运行时逐列计算得到的，因此它不得不进行表搜索，而没有使用该列上面的索引；如果这些结果在查询编译时就能得到，那么就可以被SQL优化器优化，使用索引，避免表搜索，因此将SQL重写成下面这样： <br />select * from record where card_no like <br />''5378%''（&lt; 1秒） <br />select * from record where amount <br />&lt; 1000*30（&lt; 1秒） <br />select * from record where date= ''1999/12/01'' <br />（&lt; 1秒）<br /><br />　　你会发现SQL明显快起来！ <br /><br />　　2.例：表stuff有200000行，id_no上有非群集索引，请看下面这个SQL： <br />select count(*) from stuff where id_no in(''0'',''1'')（23秒）<br /><br />　　分析： <br />　　where条件中的''in''在逻辑上相当于''or''，所以语法分析器会将in (''0'',''1'')转化为id_no =''0'' or id_no=''1''来执行。我们期望它会根据每个or子句分别查找，再将结果相加，这样可以利用id_no上的索引；但实际上（根据showplan）,它却采用了"OR策略"，即先取出满足每个or子句的行，存入临时数据库的工作表中，再建立唯一索引以去掉重复行，最后从这个临时表中计算结果。因此，实际过程没有利用id_no上索引，并且完成时间还要受tempdb数据库性能的影响。 <br /><br />　　实践证明，表的行数越多,有620000行时，执行时间竟达到220秒！还不如将or子句分开： <br />select count(*) from stuff where id_no=''0'' <br />select count(*) from stuff where id_no=''1''<br /><br />　　得到两个结果，再作一次加法合算。因为每句都使用了索引，执行时间只有3秒，在620000行下，时间也只有4秒。或者，用更好的方法，写一个简单的存储过程： <br />create proc count_stuff as <br />declare @a int <br />declare @b int <br />declare @c int <br />declare @d char(10) <br />begin <br />select @a=count(*) from stuff where id_no=''0'' <br />select @b=count(*) from stuff where id_no=''1'' <br />end <br />select @c=@a+@b <br />select @d=convert(char(10),@c) <br />print @d<br /><br />　　直接算出结果，执行时间同上面一样快！ <br /><br />　　总结：<br /><br />　　可见，所谓优化即where子句利用了索引，不可优化即发生了表扫描或额外开销。 <br /><br />　　1.任何对列的操作都将导致表扫描，它包括数据库函数、计算表达式等等，查询时要尽可能将操作移至等号右边。 <br /><br />　　2.in、or子句常会使用工作表，使索引失效；如果不产生大量重复值，可以考虑把子句拆开；拆开的子句中应该包含索引。 <br /><br />　　3.要善于使用存储过程，它使SQL变得更加灵活和高效。 <br /><br />　　从以上这些例子可以看出，SQL优化的实质就是在结果正确的前提下，用优化器可以识别的语句，充份利用索引，减少表扫描的I/O次数，尽量避免表搜索的发生。其实SQL的性能优化是一个复杂的过程，上述这些只是在应用层次的一种体现，深入研究还会涉及数据库层的资源配置、网络层的流量控制以及操作系统层的总体设计。<br /><font color="#002c99">来源：http://edu.chinaz.com</font>  <img src ="http://www.cppblog.com/Jeff-Chen/aggbug/7159.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Jeff-Chen/" target="_blank">Jeff-Chen</a> 2006-05-15 10:18 <a href="http://www.cppblog.com/Jeff-Chen/archive/2006/05/15/7159.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>五种提高SQL Server性能的方法</title><link>http://www.cppblog.com/Jeff-Chen/archive/2006/05/15/7157.html</link><dc:creator>Jeff-Chen</dc:creator><author>Jeff-Chen</author><pubDate>Mon, 15 May 2006 02:02:00 GMT</pubDate><guid>http://www.cppblog.com/Jeff-Chen/archive/2006/05/15/7157.html</guid><wfw:comment>http://www.cppblog.com/Jeff-Chen/comments/7157.html</wfw:comment><comments>http://www.cppblog.com/Jeff-Chen/archive/2006/05/15/7157.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Jeff-Chen/comments/commentRss/7157.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Jeff-Chen/services/trackbacks/7157.html</trackback:ping><description><![CDATA[有时，为了让应用程序运行得更快，所做的全部工作就是在这里或那里做一些很小调整。啊，但关键在于确定如何进行调整！迟早您会遇到这种情况：应用程序中的 SQL 查询不能按照您想要的方式进行响应。它要么不返回数据，要么耗费的时间长得出奇。如果它降低了报告或您的企业应用程序的速度，用户必须等待的时间过长，他们就会很不满意。就像您的父母不想听您解释为什么在深更半夜才回来一样，用户也不会听你解释为什么查询 耗费这么长时间。（“对不起，妈妈，我使用了太多的 LEFT JOIN。”）用户希望应用程序响应迅速，他们的报告能够在瞬间之内返回分析数据。就我自己而言，如果在 Web 上冲浪时某个页面要耗费十多秒才能加载（好吧，五秒更实际一些），我也会很不耐烦。<br /><br />为了解决这些问题，重要的是找到问题的根源。那么，从哪里开始呢？根本原因通常在于<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>设计和访问它的查询。在本月的专栏中，我将讲述四项技术，这些技术可用于提高基于 <a href="http://www.chinahtml.com/databases/2/" target="_blank">SQL Server</a>? 的应用程序的性能或改善其可伸缩性。我将仔细说明 LEFT JOIN、CROSS JOIN 的使用以及 IDENTITY 值的检索。请记住，根本没有神奇的解决方案。调整您的<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>及其查询需要占用时间、进行分析，还需要大量的测试。这些技术都已被证明行之有效，但对您的应用程序而言，可能其中一些技术比另一些技术更适用。<br /><br />从 INSERT 返回 IDENTITY <br /><br />我决定从遇到许多问题的内容入手：如何在执行 SQL INSERT 后检索 IDENTITY 值。通常，问题不在于如何编写检索值的查询，而在于在哪里以及何时进行检索。在 <a href="http://www.chinahtml.com/databases/2/" target="_blank">SQL Server</a> 中，下面的语句可用于检索由最新在活动<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>连接上运行的 SQL 语句所创建的 IDENTITY 值：<br /><br />SELECT @@IDENTITY<br /><br />这个 SQL 语句并不复杂，但需要记住的一点是：如果这个最新的 SQL 语句不是 INSERT，或者您针对非 INSERT SQL 的其他连接运行了此 SQL，则不会获得期望的值。您必须运行下列代码才能检索紧跟在 INSERT SQL 之后且位于同一连接上的 IDENTITY，如下所示：<br /><br />INSERT INTO Products (ProductName) VALUES ('Chalk')<br /><br />SELECT @@IDENTITY<br /><br />在一个连接上针对 Northwind <a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>运行这些查询将返回一个名称为 Chalk 的新产品的 IDENTITY 值。所以，在使用 ADO 的 Visual Basic? 应用程序中，可以运行以下语句：<br /><br />Set oRs = oCn.Execute("SET NOCOUNT ON;INSERT INTO Products _<br /><br />(ProductName) VALUES ('Chalk');SELECT @@IDENTITY")<br /><br />lProductID = oRs(0)　<br /><br />　　此代码告诉 <a href="http://www.chinahtml.com/databases/2/" target="_blank">SQL Server</a> 不要返回查询的行计数，然后执行 INSERT 语句，并返回刚刚为这个新行创建的 IDENTITY 值。SET NOCOUNT ON 语句表示返回的记录集有一行和一列，其中包含了这个新的 IDENTITY 值。如果没有此语句，则会首先返回一个空的记录集（因为 INSERT 语句不返回任何数据），然后会返回第二个记录集，第二个记录集中包含 IDENTITY 值。这可能有些令人困惑，尤其是因为您从来就没有希望过 INSERT 会返回记录集。之所以会发生此情况，是因为 <a href="http://www.chinahtml.com/databases/2/" target="_blank">SQL Server</a> 看到了这个行计数（即一行受到影响）并将其解释为表示一个记录集。因此，真正的数据被推回到了第二个记录集。当然您可以使用 ADO 中的 NextRecordset 方法获取此第二个记录集，但如果总能够首先返回该记录集且只返回该记录集，则会更方便，也更有效率。<br /><br />此方法虽然有效，但需要在 SQL 语句中额外添加一些代码。获得相同结果的另一方法是在 INSERT 之前使用 SET NOCOUNT ON 语句，并将 SELECT @@IDENTITY 语句放在表中的 FOR INSERT 触发器中，如下面的代码片段所示。这样，任何进入该表的 INSERT 语句都将自动返回 IDENTITY 值。<br /><br />CREATE TRIGGER trProducts_Insert ON Products FOR INSERT AS <br /><br />SELECT @@IDENTITY <br /><br />GO<br /><br />触发器只在 Products 表上发生 INSERT 时启动，所以它总是会在成功 INSERT 之后返回一个 IDENTITY。使用此技术，您可以始终以相同的方式在应用程序中检索 IDENTITY 值。<br /><br />内嵌视图与临时表 <br /><br />某些时候，查询需要将数据与其他一些可能只能通过执行 GROUP BY 然后执行标准查询才能收集的数据进行联接。例如，如果要查询最新五个定单的有关信息，您首先需要知道是哪些定单。这可以使用返回定单 ID 的 SQL 查询来检索。此数据就会存储在临时表（这是一个常用技术）中，然后与 Products 表进行联接，以返回这些定单售出的产品数量：<br /><br />CREATE TABLE #Temp1 (OrderID INT NOT NULL, _<br /><br />OrderDate DATETIME NOT NULL)<br /><br />INSERT INTO #Temp1 (OrderID, OrderDate)<br /><br />SELECT TOP 5 o.OrderID, o.OrderDate<br /><br />FROM Orders o ORDER BY o.OrderDate DESC<br /><br />SELECT p.ProductName, SUM(od.Quantity) AS ProductQuantity<br /><br />FROM #Temp1 t <br /><br />INNER JOIN [Order Details] od ON t.OrderID = od.OrderID<br /><br />INNER JOIN Products p ON od.ProductID = p.ProductID <br /><br />GROUP BY p.ProductName<br /><br />ORDER BY p.ProductName<br /><br />DROP TABLE #Temp1<br /><br />这些 SQL 语句会创建一个临时表，将数据插入该表中，将其他数据与该表进行联接，然后除去该临时表。这会导致此查询进行大量 I/O 操作，因此，可以重新编写查询，使用内嵌视图取代临时表。内嵌视图只是一个可以联接到 FROM 子句中的查询。所以，您不用在 tempdb 中的临时表上耗费大量 I/O 和磁盘访问，而可以使用内嵌视图得到同样的结果：<br /><br />SELECT p.ProductName, <br /><br />SUM(od.Quantity) AS ProductQuantity<br /><br />FROM (<br /><br />SELECT TOP 5 o.OrderID, o.OrderDate<br /><br />FROM Orders o <br /><br />ORDER BY o.OrderDate DESC<br /><br />) t <br /><br />INNER JOIN [Order Details] od ON t.OrderID = od.OrderID<br /><br />INNER JOIN Products p ON od.ProductID = p.ProductID<br /><br />GROUP BY<br /><br />p.ProductName<br /><br />ORDER BY<br /><br />p.ProductName <br /><br />此查询不仅比前面的查询效率更高，而且长度更短。临时表会消耗大量资源。如果只需要将数据联接到其他查询，则可以试试使用内嵌视图，以节省资源。<br /><br />避免 LEFT JOIN 和 NULL <br /><br />当然，有很多时候您需要执行 LEFT JOIN 和使用 NULL 值。但是，它们并不适用于所有情况。改变 SQL 查询的构建方式可能会产生将一个花几分钟运行的报告缩短到只花几秒钟这样的天壤之别的效果。有时，必须在查询中调整数据的形态，使之适应应用程序所要求的显示方式。虽然 TABLE 数据类型会减少大量占用资源的情况，但在查询中还有许多区域可以进行优化。SQL 的一个有价值的常用功能是 LEFT JOIN。它可以用于检索第一个表中的所有行、第二个表中所有匹配的行、以及第二个表中与第一个表不匹配的所有行。例如，如果希望返回每个客户及其定单，使用 LEFT JOIN 则可以显示有定单和没有定单的客户。<br /><br />此工具可能会被过度使用。LEFT JOIN 消耗的资源非常之多，因为它们包含与 NULL（不存在）数据匹配的数据。在某些情况下，这是不可避免的，但是代价可能非常高。LEFT JOIN 比 INNER JOIN 消耗资源更多，所以如果您可以重新编写查询以使得该查询不使用任何 LEFT JOIN，则会得到非常可观的回报。<br /><br />加快使用 LEFT JOIN 的查询速度的一项技术涉及创建一个 TABLE 数据类型，插入第一个表（LEFT JOIN 左侧的表）中的所有行，然后使用第二个表中的值更新 TABLE 数据类型。此技术是一个两步的过程，但与标准的 LEFT JOIN 相比，可以节省大量时间。一个很好的规则是尝试各种不同的技术并记录每种技术所需的时间，直到获得用于您的应用程序的执行性能最佳的查询。<br /><br />测试查询的速度时，有必要多次运行此查询，然后取一个平均值。因为查询（或存储过程）可能会存储在 <a href="http://www.chinahtml.com/databases/2/" target="_blank">SQL Server</a> 内存中的过程缓存中，因此第一次尝试耗费的时间好像稍长一些，而所有后续尝试耗费的时间都较短。另外，运行您的查询时，可能正在针对相同的表运行其他查询。当其他查询锁定和解锁这些表时，可能会导致您的查询要排队等待。例如，如果您进行查询时某人正在更新 此表中的数据，则在更新提交时您的查询可能需要耗费更长时间来执行。<br /><br />避免使用 LEFT JOIN 时速度降低的最简单方法是尽可能多地围绕它们设计<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>。例如，假设某一产品可能具有类别也可能没有类别。如果 Products 表存储了其类别的 ID，而没有用于某个特定产品的类别，则您可以在字段中存储 NULL 值。然后您必须执行 LEFT JOIN 来获取所有产品及其类别。您可以创建一个值为“No Category”的类别，从而指定外键关系不允许 NULL 值。通过执行上述操作，现在您就可以使用 INNER JOIN 检索所有产品及其类别了。虽然这看起来好像是一个带有多余数据的变通方法，但可能是一个很有价值的技术，因为它可以消除 SQL 批处理语句中消耗资源较多的 LEFT JOIN。在<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>中全部使用此概念可以为您节省大量的处理时间。请记住，对于您的用户而言，即使几秒钟的时间也非常重要，因为当您有许多用户正在访问同一个联机<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>应用程序时，这几秒钟实际上的意义会非常重大。<br /><br />灵活使用笛卡尔乘积 <br /><br />对于此技巧，我将进行非常详细的介绍，并提倡在某些情况下使用笛卡尔乘积。出于某些原因，笛卡尔乘积 (CROSS JOIN) 遭到了很多谴责，开发人员通常会被警告根本就不要使用它们。在许多情况下，它们消耗的资源太多，从而无法高效使用。但是像 SQL 中的任何工具一样，如果正确使用，它们也会很有价值。例如，如果您想运行一个返回每月数据（即使某一特定月份客户没有定单也要返回）的查询，您就可以很方便地使用笛卡尔乘积。<br /><br />虽然这看起来好像没什么神奇的，但是请考虑一下，如果您从客户到定单（这些定单按月份进行分组并对销售额进行小计）进行了标准的 INNER JOIN，则只会获得客户有定单的月份。因此，对于客户未订购任何产品的月份，您不会获得 0 值。如果您想为每个客户都绘制一个图，以显示每个月和该月销售额，则可能希望此图包括月销售额为 0 的月份，以便直观标识出这些月份。如果使用 Figure 2（最后一页） 中的 SQL，数据则会跳过销售额为 0 美元的月份，因为在定单表中对于零销售额不会包含任何行（假设您只存储发生的事件）。<br /><br />Figure 3（最后一页）中的代码虽然较长，但是可以达到获取所有销售数据（甚至包括没有销售额的月份）的目标。首先，它会提取去年所有月份的列表，然后将它们放入第一个 TABLE 数据类型表 (@tblMonths) 中。下一步，此代码会获取在该时间段内有销售额的所有客户公司的名称列表，然后将它们放入另一个 TABLE 数据类型表 (@tblCus-tomers) 中。这两个表存储了创建结果集所必需的所有基本数据，但实际销售数量除外。 第一个表中列出了所有月份（12 行），第二个表中列出了这个时间段内有销售额的所有客户（对于我是 81 个）。并非每个客户在过去 12 个月中的每个月都购买了产品，所以，执行 INNER JOIN 或 LEFT JOIN 不会返回每个月的每个客户。这些操作只会返回购买产品的客户和月份。<br /><br />笛卡尔乘积则可以返回所有月份的所有客户。笛卡尔乘积基本上是将第一个表与第二个表相乘，生成一个行集合，其中包含第一个表中的行数与第二个表中的行数相乘的结果。因此，笛卡尔乘积会向表 @tblFinal 返回 972 行。最后的步骤是使用此日期范围内每个客户的月销售额总计更新 @tblFinal 表，以及选择最终的行集。<br /><br />如果由于笛卡尔乘积占用的资源可能会很多，而不需要真正的笛卡尔乘积，则可以谨慎地使用 CROSS JOIN。例如，如果对产品和类别执行了 CROSS JOIN，然后使用 WHERE 子句、DISTINCT 或 GROUP BY 来筛选出大多数行，那么使用 INNER JOIN 会获得同样的结果，而且效率高得多。如果需要为所有的可能性都返回数据（例如在您希望使用每月销售日期填充一个图表时），则笛卡尔乘积可能会非常有帮助。但是，您不应该将它们用于其他用途，因为在大多数方案中 INNER JOIN 的效率要高得多。<br /><br />拾遗补零 <br /><br />这里介绍其他一些可帮助提高 SQL 查询效率的常用技术。假设您将按区域对所有销售人员进行分组并将他们的销售额进行小计，但是您只想要那些<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>中标记为处于活动状态的销售人员。您可以按区域对销售人员分组，并使用 HAVING 子句消除那些未处于活动状态的销售人员，也可以在 WHERE 子句中执行此操作。在 WHERE 子句中执行此操作会减少需要分组的行数，所以比在 HAVING 子句中执行此操作效率更高。HAVING 子句中基于行的条件的筛选会强制查询对那些在 WHERE 子句中会被去除的数据进行分组。<br /><br />另一个提高效率的技巧是使用 DISTINCT 关键字查找数据行的单独报表，来代替使用 GROUP BY 子句。在这种情况下，使用 DISTINCT 关键字的 SQL 效率更高。请在需要计算聚合函数（SUM、COUNT、MAX 等）的情况下再使用 GROUP BY。另外，如果您的查询总是自己返回一个唯一的行，则不要使用 DISTINCT 关键字。在这种情况下，DISTINCT 关键字只会增加系统开销。<br /><br />您已经看到了，有大量技术都可用于优化查询和实现特定的业务规则，技巧就是进行一些尝试，然后比较它们的性能。最重要的是要测试、测试、再测试。在此专栏的将来各期内容中，我将继续深入讲述 <a href="http://www.chinahtml.com/databases/2/" target="_blank">SQL Server</a> 概念，包括<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>设计、好的索引实践以及 <a href="http://www.chinahtml.com/databases/2/" target="_blank">SQL Server</a> 安全范例。<br /><br />Figure 2 Returning All Customers and Their Sales <br /><br />set nocount on<br /><br />DECLARE @dtStartDate DATETIME, <br /><br />@dtEndDate DATETIME,<br /><br />@dtDate DATETIME<br /><br />SET @dtEndDate = '5/5/1997'<br /><br />SET @dtEndDate = DATEADD(DD, -1, CAST(CAST((MONTH(@dtEndDate) + 1) <br /><br />AS VARCHAR(2)) + '/01/' + CAST(YEAR(@dtEndDate) AS VARCHAR(4)) + ' <br /><br />23:59:59' AS DATETIME))<br /><br />SET @dtStartDate = DATEADD(MM, -1 * 12, @dtEndDate)<br /><br />SELECT CAST(YEAR(o.OrderDate) AS VARCHAR(4)) + '-' +<br /><br />CASE <br /><br />WHEN MONTH(o.OrderDate) &lt; 10 <br /><br />THEN '0' + CAST(MONTH(o.OrderDate) AS VARCHAR(2))<br /><br />ELSE CAST(MONTH(o.OrderDate) AS VARCHAR(2))<br /><br />END AS sMonth,<br /><br />c.CustomerID,<br /><br />c.CompanyName,<br /><br />c.ContactName,<br /><br />SUM(od.Quantity * od.UnitPrice) AS mSales<br /><br />FROM Customers c<br /><br />INNER JOIN Orders o ON c.CustomerID = o.CustomerID<br /><br />INNER JOIN [Order Details] od ON o.OrderID = od.OrderID<br /><br />WHERE o.OrderDate BETWEEN @dtStartDate AND @dtEndDate<br /><br />GROUP BY<br /><br />CAST(YEAR(o.OrderDate) AS VARCHAR(4)) + '-' +<br /><br />CASE <br /><br />WHEN MONTH(o.OrderDate) &lt; 10 <br /><br />THEN '0' + CAST(MONTH(o.OrderDate) AS VARCHAR(2))<br /><br />ELSE CAST(MONTH(o.OrderDate) AS VARCHAR(2))<br /><br />END,<br /><br />c.CustomerID,<br /><br />c.CompanyName,<br /><br />c.ContactName<br /><br />ORDER BY<br /><br />c.CompanyName,<br /><br />sMonth<br /><br /><br /><br /><br /><br /><br /><br />___________________________________________________________________________<br /><br />Figure 3 Cartesian Product at Work<br /><br />DECLARE @tblMonths TABLE (sMonth VARCHAR(7))<br /><br />DECLARE @tblCustomers TABLE ( CustomerID CHAR(10),<br /><br />CompanyName VARCHAR(50),<br /><br />ContactName VARCHAR(50))<br /><br />DECLARE @tblFinal TABLE ( sMonth VARCHAR(7), <br /><br />CustomerID CHAR(10),<br /><br />CompanyName VARCHAR(50),<br /><br />ContactName VARCHAR(50),<br /><br />mSales MONEY)<br /><br />DECLARE @dtStartDate DATETIME, <br /><br />@dtEndDate DATETIME,<br /><br />@dtDate DATETIME,<br /><br />@i INTEGER<br /><br />SET @dtEndDate = '5/5/1997'<br /><br />SET @dtEndDate = DATEADD(DD, -1, CAST(CAST((MONTH(@dtEndDate) + 1) AS <br /><br />VARCHAR(2)) + '/01/' + CAST(YEAR(@dtEndDate) AS VARCHAR(4)) + ' <br /><br />23:59:59' AS DATETIME))<br /><br />SET @dtStartDate = DATEADD(MM, -1 * 12, @dtEndDate)<br /><br />— Get all months into the first table<br /><br />SET @i = 0<br /><br />WHILE (@i &lt; 12)<br /><br />BEGIN<br /><br />SET @dtDate = DATEADD(mm, -1 * @i, @dtEndDate)<br /><br />INSERT INTO @tblMonths SELECT CAST(YEAR(@dtDate) AS VARCHAR(4)) + '-' +<br /><br />CASE <br /><br />WHEN MONTH(@dtDate) &lt; 10 <br /><br />THEN '0' + CAST(MONTH(@dtDate) AS VARCHAR(2))<br /><br />ELSE CAST(MONTH(@dtDate) AS VARCHAR(2))<br /><br />END AS sMonth<br /><br />SET @i = @i + 1<br /><br />END<br /><br />— Get all clients who had sales during that period into the "y" table<br /><br />INSERT INTO @tblCustomers<br /><br />SELECT DISTINCT<br /><br />c.CustomerID,<br /><br />c.CompanyName,<br /><br />c.ContactName<br /><br />FROM Customers c<br /><br />INNER JOIN Orders o ON c.CustomerID = o.CustomerID<br /><br />WHERE o.OrderDate BETWEEN @dtStartDate AND @dtEndDate<br /><br />INSERT INTO @tblFinal<br /><br />SELECT m.sMonth,<br /><br />c.CustomerID,<br /><br />c.CompanyName,<br /><br />c.ContactName,<br /><br />0<br /><br />FROM @tblMonths m CROSS JOIN @tblCustomers c<br /><br /><br /><br />UPDATE @tblFinal SET <br /><br />mSales = mydata.mSales<br /><br />FROM @tblFinal f INNER JOIN <br /><br />(<br /><br />SELECT c.CustomerID,<br /><br />CAST(YEAR(o.OrderDate) AS VARCHAR(4)) + '-' +<br /><br />CASE WHEN MONTH(o.OrderDate) &lt; 10 <br /><br />THEN '0' + CAST(MONTH(o.OrderDate) AS VARCHAR(2))<br /><br />ELSE CAST(MONTH(o.OrderDate) AS VARCHAR(2))<br /><br />END AS sMonth,<br /><br />SUM(od.Quantity * od.UnitPrice) AS mSales<br /><br />FROM Customers c<br /><br />INNER JOIN Orders o ON c.CustomerID = o.CustomerID<br /><br />INNER JOIN [Order Details] od ON o.OrderID = od.OrderID<br /><br />WHERE o.OrderDate BETWEEN @dtStartDate AND @dtEndDate<br /><br />GROUP BY<br /><br />c.CustomerID,<br /><br />CAST(YEAR(o.OrderDate) AS VARCHAR(4)) + '-' +<br /><br />CASE WHEN MONTH(o.OrderDate) &lt; 10 <br /><br />THEN '0' + CAST(MONTH(o.OrderDate) AS VARCHAR(2))<br /><br />ELSE CAST(MONTH(o.OrderDate) AS VARCHAR(2))<br /><br />END<br /><br />) mydata on f.CustomerID = mydata.CustomerID AND f.sMonth = <br /><br />mydata.sMonth <br /><br />SELECT f.sMonth,<br /><br />f.CustomerID,<br /><br />f.CompanyName,<br /><br />f.ContactName,<br /><br />f.mSales<br /><br />FROM @tblFinal f<br /><br />ORDER BY<br /><br />f.CompanyName,<br /><br />f.sMonth<!-- / message --><br /><br />来源：http://www.chinahtml.com<img src ="http://www.cppblog.com/Jeff-Chen/aggbug/7157.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Jeff-Chen/" target="_blank">Jeff-Chen</a> 2006-05-15 10:02 <a href="http://www.cppblog.com/Jeff-Chen/archive/2006/05/15/7157.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>搜索引擎设计</title><link>http://www.cppblog.com/Jeff-Chen/archive/2006/05/15/7153.html</link><dc:creator>Jeff-Chen</dc:creator><author>Jeff-Chen</author><pubDate>Mon, 15 May 2006 01:51:00 GMT</pubDate><guid>http://www.cppblog.com/Jeff-Chen/archive/2006/05/15/7153.html</guid><wfw:comment>http://www.cppblog.com/Jeff-Chen/comments/7153.html</wfw:comment><comments>http://www.cppblog.com/Jeff-Chen/archive/2006/05/15/7153.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Jeff-Chen/comments/commentRss/7153.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Jeff-Chen/services/trackbacks/7153.html</trackback:ping><description><![CDATA[不知你上网时注意到没有：一些内容丰富的网站，总建有一个内容搜索引擎；一些大型的商业网站或者综合网站，都配有强大的网站搜索引擎，比如搜狐、新浪、雅虎等等。其方便的搜索查询功能至今给人们留下难以磨灭的印象，你只要输入你想浏览资料的关键字（比如：网页），一按“搜索”按钮，关于“网页”的资料列表就呈现在你的眼前；这只是搜索引擎的基本功能 --搜索查询。 <br /><br />　　笔者现在专门负责网上山东（http://www.china-sd.net）中山东搜索版块的建设，利用最常用的开发语言--asp开发搜索引擎的强大功能！下面我就搜索引擎主要组成部分和其中一些功能的实现代码讲解给大家，有什么不足之处请大家给予指出！ <br /><br />　　在这一章节中，我介绍一下搜索引擎的组成部分和主要功能。 <br /><br />　　搜索引擎的组成，大致分为三部分： <br /><br />　　1、界面：这一部分主要是面向客户的，是可以看到的部分，比如您打开search.sina.com.cn事后看到的页面。 <br /><br />　　2、程序：这一部分主要是执行代码，根据客户的搜索要求去执行代码从而获得搜索结果；这些是我们看不到的。 <br /><br />　　3、数据库：所有的搜索引擎都离不开数据库，连著名的google.com也不例外；数据库是储存搜索资料的仓库，储存的越多，搜索得到的资料就会越多，这也是搜索引擎是否强大的闪耀点之一。 <br /><br />　　　那么搜索引擎是否强大还有其他的闪耀点喽，是什么呢？对，搜索引擎数据库中的存储资料再多，我们不能够方便的去查找搜索，甚至查找不到所需的资料，那么这个数据库就是“死”的，毫无用处可言，所以程序代码起到非常重要的作用。 <br /><br />　　　讲到这里，我应该给大家介绍一下搜索引擎的几个主要功能： <br /><br />　　　1、搜索查询：毫无疑问这是最基本的功能了，根据关键字找到符合关键字的相关资料。 <br /><br />　　　2、分页显示：如果你搜索到的资料非常多，都放在一个页面里，那样给人的感觉就会使乱糟糟的一片；分页显示根据人们看书的习惯，将一部分内容放到第一页，其他的内容放到第二页、第三页等等。 <br /><br />　　　3、搜索统计：一般包括查询资料的数量，分几个页面，每个页面含几个资料，当前页面资料范围等等。 <br /><br />　　　4、搜索结果编排：搜索排名我想大家不会陌生，这些是综合网站搜索引擎挣money的一项措施，这也是强大搜索引擎中不可缺少的功能；比如根据点击量排名，根据收费排名、根据等级排名。 <br /><br />　　　5、多个关键字查询：“如果要查找包含多个关键词的信息，可以用空格把关键词隔开”这是新浪搜索引擎版面中的一段文字，在一个文本框中打上多个关键字搜索查询资料。 <br /><br />　　　6、整体统计：这个是对搜索引擎开通至今一些数据统计，包含“热门查询关键字”统计、数据库中资料整体统计、每个类别所含资料统计等等。 <br /><br />　　　以上功能代码主要根据笔者的要求来书写，大家可以在我写的代码上做修改，以成为你自己需要的功能代码。 <br /><br />　　好了，经过了一节的热身后，我们也可以进入实战阶段，首先，我们先建一个数据库，作为资料的存储，这里我将数据库的文件名为information.mdb，使用access创建数据库，当然你也可以使用sqlserver创建。 <br /><br />　　建立四个表：www(存储资料),sort(大类)，samll(小类),key(查询关键字) <br /><br />　　 　1、www表：id---自动编号，sitename---站点名称，url---站点链接，faq---简要说明，key---关键字，time---添加时间,level---站点等级，sortid---大类id，smallid---小类id，hot---站点点击次数。 <br /><br />　　　　2、sort表：id---自动编号,sort <br /><br />　　　　3、small表：id---自动编号,small <br /><br />　　　　4、key表：keyname---关键字；keyhot---出现的次数 <br /><br />　　　　用asp建立与数据库的连接：conn.asp <br /><br />　　 <br /><br />　　说明：使用Server.MapPath（）表示的路径为文件的相对路径，我这里conn.asp和information.mdb在同一个目录下。我想这些都很简单，大家很容易理解和接受的！好，将数据库建立起来后，我们就可以建设强大功能的搜索引擎。请期待哦！嘻嘻~~~~ <br /><br />　　用asp制作强大的搜索引擎 -- 模糊搜索 <br /><br />　　根据一个关键字，搜索到相关的资料，这里的“相关”是指资料中有类似这个关键字的字符串。例如：“山东”这个关键字，只要数据库中资料里包含“山东”这个关键字的都要把它们找出来。模糊搜索的应用其实很简单，只要使用一个sql语句就可以实现，下面咱们看看他的语句的写法。 <br /><br />　　sql语法中你会发现这么几个判定词：LIKE、NOT LIKE和 BETWEEN。 LIKE判定词是一个非常有用的符号。不过，在很多情况下用了它可能会带给你太多的数据，所以在用到它之前最好先开动脑筋多想想自己到底想获得什么数据。NOT LIKE是反其道而行了。BETWEEN假设你想取出一定范围内的数据，而且你事先知道范围的起点和终点，那么你不妨采用BETWEEN 判断词。这几个判定词根据不同的环境使用，一般最常用的就是like和"%"结合了。 <br /><br />　　dim sql,key <br /><br />　　key=request("key") <br /><br />　　sql="select * from www where sitename like '%"&amp;key&amp;"%' or faq like '%"&amp;key&amp;"%' or key like '%"&amp;key&amp;"%' " <br /><br />　　说明：这里的sql语句目的就是检索数据库中sitename字段中是否包含key，faq字段中是否包含key，key字段中是否包含key，这样做的目的是让搜索的范围包含到“站点名称”、“站点简要说明”、“站点关键字”。如果你只想搜索关键字只要使用 sql="select * from www where key like '%"&amp;key&amp;"%' " 就可以了。“like”中都使用了“or”来相连，“or” “或者”的意思，意思是不论哪一个like 符合条件，都要把搜索到的资料输出显示出来。 <br /><br />　　OK，现在大可不必去看看能不能执行或者说执行的结果如何，因为搜索引擎大部分功能的实现都是靠sql语句的书写了。等我把其他的相关sql语句的功能实现介绍完后，大家再看看效果，呵呵！别着急！！ <br /><font color="#002c99">来源：http://edu.chinaz.com</font><img src ="http://www.cppblog.com/Jeff-Chen/aggbug/7153.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Jeff-Chen/" target="_blank">Jeff-Chen</a> 2006-05-15 09:51 <a href="http://www.cppblog.com/Jeff-Chen/archive/2006/05/15/7153.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>