My interest in writing this article was started by an MSDN article titled SQL Server 2005: The CLR Enters the Relational Stage . The article shows how to write a function that returns the top three countries per category. That's always been something that was difficult to do in SQL so I was curious about the approach. The article started out well but I was very unhappy by the end. It's just soooo much easier to do this in SQL Server 2005 using the new CROSS APPLY clause in Transact-SQL. So I'm going to write a query to return the top 3 orders for each customer and I'm going to do it in about 10 lines of SQL. (
UPDATE: An alert reader found an even better approach!) First please take a second to read the MSDN article . Pay special attention to how much C# code is required for the solution and how flexible it is.
Microprocessor Design Note: I also want to mention here that I started out trying to replicate their solution. I tried to install the MSI but that failed with an invalid characters message. I tried to recreate the CLR code but since he mostly posted snippets and had three versions I finally gave up trying to get that to work. One thing I really wanted to do was compare performance on identical data. Unfortunately I wasn't able to do that. My example will use the AdventureWorks database Let's start at the TOP
Are you hesitant to migrate to Visual Studio(r) 2005 Maybe you're ready to upgrade to Visual Basic 2005 but feel a bit overwhelmed by ADO.NET 2.0 and the Visual Data Tools of this new release. on guide, I'll share with you the best practices, the latest features, and advanced data management techniques with Visual Basic 2005 and SQL Server or SQL Server Express 2005. Plus, I' world, end applications with Windows and Web forms.
Intel Motherboard Along the way to making this solution work we'll discuss a few new features of SQL Server 2005. The first is that the TOP clause can now take a variable:
DECLARE @Rows INT SET @Rows = 10 SELECT TOP ( @Rows ) * FROM Sales.SalesOrderHeader
The name attribute provides an identifier to the SQL Server database. The attribute specifies the name of the connection. Because the connection string in the preceding example is called, you use this value for the attribute as well. pollTime The pollTime attribute specifies the time interval from one SQL Server poll to the next. The default is .5 seconds or 500 milliseconds (as shown in the example). This is not needed for SQL Server 2005 notification.
Gigabyte Mainboard This will return the top 10 rows from SalesOrderHeader. You can also replace @Rows with anything that evaluates to a number. The following query looks odd but runs just fine:
SELECT TOP ( SELECT COUNT(*) FROM Sales.Customer ) * FROM Sales.SalesOrderHeader
- Unmatched scalability keeps your website up and running, even with an increase in the amount of users.
- MS SQL Server 2005 allows you to store a large amount of data and retrieve it in record time.
- MS SQL Server 2005 interacts perfectly with other Microsoft technologies, like FrontPage® and Active Server Pages (ASP). in, Text Indexing engine to find what you're looking for at the blink of an eye.
History Of Microprocessor There are 19,185 rows in the Customer table and this query returns the top 19,185 rows from SalesOrderHeader. You can also use the TOP clause for INSERT, UPDATE and DELETE statements. If you wanted to DELETE in batches of 500 you can now do that using the TOP clause. My Function
hand experiences to offer you best practices, unique tips and tricks, and useful workarounds, the authors help you handle even the most difficult SQL Server 2005 administration issues, including blocking and locking. You' tune queries you've already written, automate redundant monitoring and maintenance tasks, and use hidden tools so that you can quickly get over the learning curve of how to configure and administer SQL Server 2005.
Ecs Motherboard Next we need a function to return the TOP X rows from SalesOrderHeader based on the total sales amount. That function looks like this:
CREATE FUNCTION dbo.fn_GetTopOrders(@custid AS int, @n AS INT) RETURNS TABLE AS RETURN SELECT TOP(@n) * FROM Sales.SalesOrderHeader WHERE CustomerID = @custid ORDER BY TotalDue DESC GO
In the Report Recipes section of the book Professional SQL Server 2005 Reporting Services, I've compiled a description of reporting challenges and solutions we've encountered, developing reports for our clients. For each "solution recipe, " I provide a brief list of skills, techniques, and resources needed to apply the report feature. This should give you a good idea about how prepared you may be to use the techniques based on your skill set and the level of complexity. Some of these are easy to duplicate and others require more advanced skills, SQL and Visual Basic programming. step instructions. I have made a point to provide enough information to demonstrate the concepts and techniques. However, to implement these solutions you will need to apply the skills you learned in the previous chapters of Professional SQL Server 2005 Reporting Services.
Mainboard Driver Notice that it accepts the number of orders to return as a parameter. Also notice that I'm using SELECT * all over the place. I really encourage you to explicitly list out the columns when you write this for real. If you call this with a CustomerID and a number of rows it will return that many rows ordered by the total amount of the order in descending order. Also notice that there is an ORDER BY clause in this function. We'll talk about that in a minute. Apply the APPLY Clause
Computer Motherboard The real magic happens when you use SQL Server 2005's new APPLY clause. The APPLY clause let's you join a table to a table-valued-function. That let's you write a query like this:
SELECT C.CustomerID, O.SalesOrderID, O.TotalDue FROM AdventureWorks.Sales.Customer AS C CROSS APPLY AdventureWorks.dbo.fn_GetTopOrders(C.CustomerID, 3) AS O ORDER BY CustomerID ASC, TotalDue DESC
Asus Mainboard which results in this...
CustomerID SalesOrderID TotalDue ----------- ------------ --------------------- 1 45283 37643.1378 1 46042 34722.9906 1 44501 26128.8674 2 46976 10184.0774 2 47997 5469.5941 2 57044 4537.8484 3 53616 92196.9738 3 47439 78578.9054 3 48378 56574.3871 4 47658 132199.8023 . . .
Microprocessor Programming The APPLY clause acts like a JOIN without the ON clause comes in two flavors: CROSS and OUTER. The OUTER APPLY clause returns all the rows on the left side (Customers) whether they return any rows in the table-valued-function or not. The columns that the table-valued-function returns are null if no rows are returned. The CROSS APPLY only returns rows from the left side (Customers) if the table-valued-function returns rows.
Socket Motherboard Notice that I'm just passing in the CustomerID to the function. It returns the TOP 3 rows based on the amount of the order. Since I'm using CROSS APPLY a customer without orders won't appear in the list. I can also pass in a number other than 3 to easily return a different number of orders per customer. Even better I can pass in a different number of orders for each customer. So I could list the top 5 orders for one type of customer but the top 10 for another type of customer. How cool is that?!?
Ecs Mainboard And it gets even better. Remember the function has an ORDER BY in it. It will always return the top orders based on the amount. However you can change the ORDER BY clause in the query that calls the function to display those rows in whatever order you want. You could easily display the top 3 orders in ascending order instead. Performance and Conclusion
Motherboard Manual Unfortunately I wasn't able to compare the performance of my solution to MSDN's. The query plan from my solution doesn't look that bad. (And did I mention that it showed me a full query plan for the query with the UDF rather than just a UDF step? Sweet!) If anyone gets their solution running send me the code and I'll update the article.
Shuttle Mainboard I also think their solution returns the result as a comma separated value. Now that would be a great use for the CLR. You could easily wrap that around the results of this function for a fast, effecient procedure. And it happens to be the next article I'm working on. :) If you want to see a great article on using the CLR inside SQL Server I'd start with Using CLR Integration in SQL Server 2005 . The code is from an earlier beta but it gives you a great idea of when to use CLR code.
Microprocessor Architecture In summary I think that 5-10 lines of SQL is much simpler than 50+ lines of C# (or VB) for this type of solution. The article provides a lot of good information on the CLR inside SQL Server. I just don't think this is the best place to use it. One of my biggest fears is that people will use the CLR incorrectly and cause performance problems. This is the first real case I've seen of this. Is this really how Microsoft wants us to use the CLR? UPDATE
Biostar Mainboard I was curious when I posted this how long it would take to find an even better solution. It didn't take long. Arnold Fribble posted it in the forums. The syntax needed a little tweaking but it appears you can do this in a single query. And how did I forget the PARTITION BY clause of the ROW_NUMBER function. Doh! Here's the cleaned up query:
SELECT CustomerID, SalesOrderID, TotalDue FROM ( SELECT O.CustomerID, O.SalesOrderID, O.TotalDue, ROW_NUMBER = ROW_NUMBER() OVER ( PARTITION BY O.CustomerID ORDER BY O.TotalDue DESC) FROM Sales.SalesOrderHeader AS O ) AS d WHERE d.ROW_NUMBER
Motherboard Review You have to use a derived table (or a Common Table Expression) since the row number function can't be used in the WHERE clause of the query where it's defined. Now I'll have to write an article on the new ranking functions. :) Thanks Arnold!
[ Comment, Edit or Article Submission ]