Oren Eini

CEO of RavenDB

a NoSQL Open Source Document Database

Get in touch with me:

oren@ravendb.net +972 52-548-6969

Posts: 7,567
|
Comments: 51,184
Privacy Policy · Terms
filter by tags archive
time to read 3 min | 513 words

Can't figure out why it is not possible to pass a result set to a stored procedure. I want to bulid something like this:

CREATE PROCEDURE Ensure_FK

      @missing_fk table(fk int)

AS

      INSERT INTO Parent_Table (FK, Desc)

      SELECT fk, 'no description' FROM @missing_fk

      WHERE fk NOT IN (SELECT fk FROM Parent_Table)

Usage:

SELECT

FK INTO #Fks FROM Child_Table;
exec Ensure_FK #Fks

I dug around the documentation, and it looks like table variables can not be used as a procedure parameter :-(

time to read 8 min | 1469 words

I realized that I didn't really explain why anyone would ever need this nastiness, the idea is piping dynamic SQL inside the database. I realize that this doesn't really mean anything, so here is the example. First, we need to expand our nasty trigger to support multiply statements:

CREATE TABLE Nasty( [text] nvarchar(500));

GO

CREATE TRIGGER Very_Nasty_Trigger

ON NASTY

INSTEAD OF INSERT

AS BEGIN

    DECLARE @stmt nvarchar(500);

      DECLARE statements CURSOR

      FOR select [text] from INSERTED;

      OPEN statements

      FETCH NEXT FROM statements

            INTO @stmt

      WHILE @@FETCH_STATUS != -1

      BEGIN

            exec sp_executesql @stmt

            FETCH NEXT FROM statements

                  INTO @stmt

      END

      CLOSE statements

      DEALLOCATE statements

END

Now we can use it to find out stuff about our database using the InformationSchema tables, like so:

INSERT INTO Nasty([text])

SELECT 'SELECT COUNT(*), '''+table_name+''' FROM '+table_name FROM information_schema.tables

This is quick and dirty way to find out how much rows there are in each of your tables. Ten points to the first person who can recognize where similar technqiue is widely used.

time to read 31 min | 6072 words

In the previous post I showed how it is possible to use aggerate functions to avoid procedural code in SQL. The result of this post actually surprised me, since I didn't know that this was possible until I tried it (I intended to show that this is not possible, and then show another way). As an aside, looking into the results of both queries, the cursor based one was wrong by several thousands, because of data type conversions that were happening (losing precision from numeric to money, mostly).

Well, I still want show how to do it in more complex statements, but I'm having trouble of thinking of a problem simple enough to explain that cannot be solved better, as I showed yesterday. So, let's go back to my mythological Payments table, which now looks like this:

Amount To Payments Date
626.329 Gas 23 7/1/06
794.995 Food       10 7/1/06
296.007 Drink       20 7/1/06

The table contains 1,000,000 rows, and has no indexing / primak keys whatsoever.

Here is another contrived example, which now will also calculate the average amount of a single payment, as well as the total number of single payments:

DECLARE

      @current_amount money,

      @current_NumOfPayments int,

      @total money,

      @tmp money,

      @avg money,

      @count NUMERIC

 

DECLARE big_payments CURSOR

            FOR SELECT amount,NumOfPayments FROM Payments

 

SET @total = 0

SET @count = 0

 

OPEN big_payments

 

FETCH NEXT FROM

     big_payments INTO @current_amount, @current_NumOfPayments

 

WHILE @@FETCH_STATUS != -1

BEGIN

 

      IF (@current_NumOfPayments IS NOT NULL AND @current_NumOfPayments <> 0)

            SET @tmp = @current_amount/@current_NumOfPayments   

      ELSE

        SET @tmp = @current_amount

 

      SET @total = @total + @tmp  

        SET @count = @count +1

 

      FETCH NEXT FROM

            big_payments INTO @current_amount, @current_NumOfPayments

 

END

 

CLOSE big_payments

DEALLOCATE big_payments

 

IF (@count <> 0)

      SET @avg = @total / @count

 

SELECT @total, @avg

This query execute in over a minute (01:30, to be exact), this is obviously quite unacceptable, it is possible to use the previous technique here as well, using this statement (runtime over million records is less than a second):

SELECT SUM(

        CASE NumOfPayments

                  WHEN NULL THEN Amount

                  WHEN 0 THEN Amount

                  ELSE Amount/NumOfPayments

        END

) SumOfPayments,

SUM(

        CASE NumOfPayments

                  WHEN NULL THEN Amount

                  WHEN 0 THEN Amount

                  ELSE Amount/NumOfPayments

        END

) / count(*) as AveragePayment
FROM Payments

But this is just ugly, in my opinion, not to mention that it duplicate code. Also, in certain situations it gets ugly (very) fast(think of the case where I need to use both the sum and the average in yet another calculation (the code that started this talk had about seven such calculations, btw).

Here is a better solution, which involve nesting statements, like this:

SELECT

      SumOfPayments,

      AvergatePayment = CASE CountOfPayments

            WHEN NULL THEN

                  SumOfPayments

            WHEN 0 THEN

                  SumOfPayments

            ELSE

                  SumOfPayments / CountOfPayments

            END

FROM (

      SELECT SUM(

              CASE NumOfPayments

                        WHEN NULL THEN Amount

                        WHEN 0 THEN Amount

                        ELSE Amount/NumOfPayments

              END

      ) SumOfPayments,

      count(*) as CountOfPayments

      FROM Payments

) TmpCalc

This method allows for temporary calculations and avoid duplicating code. This statement also run in under a second, and has the same execution plan as the previous one. I have used this method to break up the calculations so I could turn a true masterpiece of cursor based logic into a single (big) SQL statement.

time to read 14 min | 2680 words

The problem with pivoting a table is that you need to pass hard-coded values, so you can’t just pass a runtime selection for it.

Here is the scenario:

Let’s go back to our payments table, which looks like this:

CREATE TABLE [dbo].[Payments](

      [Amount] [money] NOT NULL,

      [Date] [datetime] NOT NULL,

      [To] [nvarchar](50) NOT NULL

)

 

Now, I want to see how much I paid to each of my debtors for the last 12 days, but those dates aren’t fixed, there are days (too few, unfortunately) that I don’t have to pay anyone, so I’m in a bit of a bind there. So, how do I solve it? Write a dynamic query? That is possible, but I really hate it. I used a temporary table as a hash map for the values, and here is the result:

 

CREATE TABLE #IndexPerDistinctDate

(

      id int identity(1,1),

      date datetime

);

 

INSERT INTO #IndexPerDistinctDate

(

      date

)

SELECT DISTINCT

      date

FROM Payments

ORDER BY date ASC;

 

SELECT

      *

FROM

(

      SELECT

            p.[To],

            p.Amount,

            i.id as [index]

      FROM Payments p Left Outer Join #IndexPerDistinctDate i

      ON p.Date = i.Date

)

Source

PIVOT

(

      SUM(amount)

      FOR [index] IN

      (

            [1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12]

      )

) pvt;

 

DROP TABLE #IndexPerDistinctDate;

 

This also demonstrate another feature of pivoting in SQL Server 2005, it just merely ignores values that wasn’t specified. This is exactly what I want for this particular instance.

 

The source table looks like this:

Amount

Date

To

15

12.2.05

gas

27

14.4.05

food

32

17.7.06

drink

 

And the result from the query above looks like this (with 12 columns, but I cut it a bit so it would fit in the page):

To

1

2

3

4

drink

NULL

NULL

32

NULL

food

NULL

27

NULL

NULL

gas

15

NULL

NULL

NULL

 

FUTURE POSTS

No future posts left, oh my!

RECENT SERIES

  1. Production Postmortem (52):
    07 Apr 2025 - The race condition in the interlock
  2. RavenDB (13):
    02 Apr 2025 - .NET Aspire integration
  3. RavenDB 7.1 (6):
    18 Mar 2025 - One IO Ring to rule them all
  4. RavenDB 7.0 Released (4):
    07 Mar 2025 - Moving to NLog
  5. Challenge (77):
    03 Feb 2025 - Giving file system developer ulcer
View all series

RECENT COMMENTS

Syndication

Main feed Feed Stats
Comments feed   Comments Feed Stats
}