Web Code Blog

A web repository of coding tips and knowledge base articles

Archive for the ‘SQL Server’ Category

Obtaining XML Node Order when using SQL Server

Posted by Stefan Zvonar on January 11, 2012

Unfortunately, when reading / shredding XML document from SQL Server, there are no guarantees in the order in which SQL Server will read the XML nodes.  This can prove particularly troublesome if your code or import procedure relies on a specific order.  When you run your script, the order it retrieves the XML is random and can be the cause of some intermittent results – which can contribute to hair loss.

Your first thought, may be to try and use the “position()” function in your retrieval script, but alas, this will not work.

The below solution is a bit of a hack, but it seems to work regardless of how many times I run the script and I can guarantee the order in which the node information was presented in the XML.

First, let’s assume you have an XML parameter, or XML column in a table in your database.  Here, we are just going to create a dummy XML data type for demonstration purposes.

DECLARE @Xml XML

SET @Xml =

<Data>

    <Field>

        <Name>Person</Name>

        <Value>Joe Bloggs</Value>

    </Field>

    <Field>

        <Name>Telephone</Name>

        <Value>12345678</Value>

    </Field>

    <Field>

        <Name>Address</Name>

        <Value>1 Blah Street</Value>

    </Field>

</Data>

Now, we are going to create a temporary table which is simply full of sequential numbers which should cater for the number of nodes you are expecting. Note, in my case, I was reading XML describing the fields I was expecting, so 100 numbers here is more than enough. You may require more.

Here I create the table to consist of 100 numbers (as that is more than enough to cover the number of fields I am expecting).

DECLARE @Numbers TABLE(num int identity(1,1))

INSERT @Numbers DEFAULT VALUES;

WHILE SCOPE_IDENTITY() < 100

    INSERT @Numbers DEFAULT VALUES;

Then, we are going to perform the query on the XML, to retrieve the field information, combined with the order in which the node is stored in the XML.

SELECT *

INTO #Fields  

FROM 

(

    SELECT

        –Fields.Field.value(‘position()’, ‘int’) AS Order,  — Unfortunately, this will not work!

        n.num AS [Order],

        Fields.Field.value(‘./Name[1]‘, ‘varchar(50)’) AS FieldName,

        Fields.Field.value(‘./Value[1]‘, ‘varchar(50)’) AS FieldValue

    FROM @Numbers n

        CROSS APPLY @Xml.nodes(‘/Data/Field[sql:column("n.num")]‘) AS Fields(Field)

) AS f

 

SELECT * FROM #Fields

 

DROP TABLE #Fields

Below is an example of what is returned:

Ordered Node Values in Table

As you can see, it provides you now with a temporary table and column that defines the order in which the XML node was presented in the XML. You can then sort this table by the order column if you so wish, join by it, or whatever it is you need to do!

Hope this helps,

Stefan.

For more Web Code, ASP.NET, SQL Server and other development tips, please check back here at webcodeblog.com often.

Posted in SQL Server, XML | Leave a Comment »

Executing xp_cmdshell with SQL Server 2008 R2

Posted by Stefan Zvonar on November 18, 2011

Sometimes there comes a time when you would like to have a user of your database be able to execute a program by making use of SQL Server’s xp_cmdshell procedure.  It can be a little tricky setting up the permissions to enable this call, so here are some steps you can take to make it possible.

Firstly, allow execution of xp_cmdshell:

USE [master]
GO
EXEC sp_configure 'xp_cmdshell', 1 RECONFIGURE 
GO

Secondly, create a SQL Login that can run xp_cmdshell.  Please create a more secure password than the example given:

USE [master] 
GO
CREATE LOGIN [CommandShellLogin] WITH PASSWORD ='abc123!@#' 
GO
CREATE USER [CommandShellLogin] FROM LOGIN [CommandShellLogin] 
GO
GRANT EXECUTEON xp_cmdshell TO [CommandShellLogin] 
GO

Thirdly, because you will be switching to this command shell login temporarily, grant impersonation rights to the user’s group. Note: Am assuming that the user group is mapped to a login in SQL Server:

USE [master] 
GO
GRANT IMPERSONATE ON LOGIN::CommandShellLogin TO [DOMAIN\USER GROUP] 
GO

Now, you could make [CommandShellLogin] a member of sysadmin to make this work, but that would be providing too much priviledges. Instead, you can set up a proxy account that xp_cmdshell uses whenever it is called. This will be an actual windows account that the command will execute as, so make sure that the windows account has the permissions to perform whatever it is you are intending it to execute:

EXEC sp_xp_cmdshell_proxy_account 'DOMAIN\User', 'UserPassword' 
GO

Now in your procedure, trigger, script or whereever you plan on running xp_cmdshell, switch the context to the CommandShellLogin user temporarily to execute your desired command. Here we are just running DIR as an example:

EXECUTE AS LOGIN = 'CommandShellLogin' 
EXEC xp_cmdshell 'DIR' 
REVERT

Note: Setting up the proxy account above results in any xp_cmdshell to run as the user specified. If you no longer want subsequent calls to xp_cmdshell to run as that windows account, then ensure that you empty the proxy account so that it does not run anymore as that account:

EXEC sp_xp_cmdshell_proxy_account NULL 

Hope this helps,

Stefan.

For more Web Code, ASP.NET, SQL Server and other development tips, please check back here at webcodeblog.com often.

Posted in SQL Server | Leave a Comment »

Find out what service pack and edition your SQL Server installation is

Posted by Stefan Zvonar on April 4, 2011

Here is a quick little T-SQL script to run to find out some basic version, service pack and edition your SQL Server Instance is running:

SELECT SERVERPROPERTY('productversion') AS [Version], SERVERPROPERTY ('productlevel') AS [Service Pack], SERVERPROPERTY ('edition') AS [Edition]

The complete knowledge base article can be found here.

Hope this helps,

Stefan.

For more Web Code, ASP.NET, SQL Server and other development tips, please check back here at webcodeblog.com often.

Posted in SQL Server | Leave a Comment »

Replacing all instances of a piece of text inside a SQL Server Database

Posted by Stefan Zvonar on November 12, 2010

So, have you ever had to replace a piece of text wherever it occured inside a SQL Server Database (without knowing where to even look)?  This can be a time consuming task, especially if you have a database with hundreds of tables and columns.

I had to perform this recently when a server moved to a new domain (changing all usernames etc).

Here is how you can make a quick start.  What this article will explain is how to generate the script which you can then use to perform the alteration (although of course you will need to review the resultant script, change it, test it and run at your own risk!)

First, open SQL Server Management Studio and connect to your database, creating a blank query window.  You will need to also click on the “Results to Text” button, like shown below.

Results to text

Results to text

Now copy and paste the below script into your new query window.  This script will perform the script generation, replacing all instances of ‘ABC’ with ‘XYZ’.  Here is where you change what string you are wanting to replace with its new value.

    SET NOCOUNT ON
    DECLARE @SearchStr varchar(100)
    DECLARE @ReplaceStr varchar(100)
    SET @SearchStr = 'ABC'
    SET @ReplaceStr = 'XYZ'

    DECLARE @TableName nvarchar(256), @ColumnName nvarchar(128), @Counter int, @Exists int

    SET @TableName = ''
    SET @Counter = 0

    CREATE TABLE #StringFound
    (
     occurances varchar(1000),
     tableName varchar(1000),
     columnName varchar(1000)
    )

    WHILE @TableName IS NOT NULL
    BEGIN
        SET @ColumnName = ''
        SET @TableName =
        (
            SELECT MIN(QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME))
            FROM     INFORMATION_SCHEMA.TABLES
            WHERE         TABLE_TYPE = 'BASE TABLE'
                AND    QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME) > @TableName
                AND    OBJECTPROPERTY(
                        OBJECT_ID(
                            QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME)
                             ), 'IsMSShipped'
                               ) = 0
        )

        WHILE (@TableName IS NOT NULL) AND (@ColumnName IS NOT NULL)
        BEGIN
            SET @ColumnName =
            (
                SELECT MIN(QUOTENAME(COLUMN_NAME))
                FROM     INFORMATION_SCHEMA.COLUMNS
                WHERE         TABLE_SCHEMA    = PARSENAME(@TableName, 2)
                    AND    TABLE_NAME    = PARSENAME(@TableName, 1)
                    AND    DATA_TYPE IN ('char', 'varchar', 'nchar', 'nvarchar')
                    AND    QUOTENAME(COLUMN_NAME) > @ColumnName
            )

            IF @ColumnName IS NOT NULL
            BEGIN

                INSERT INTO #StringFound EXEC ('SELECT COUNT(*), ''' + @TableName + ''', ''' + @ColumnName + '''  FROM ' + @TableName + ' WHERE ' + @ColumnName + ' LIKE ''%' + @SearchStr + '%'' HAVING COUNT(*) > 0')
                SET @Counter = @Counter + @@ROWCOUNT

            END
        END
    END

    SELECT 'EXEC sp_MSforeachtable ''ALTER TABLE ? DISABLE TRIGGER ALL'''

    SELECT 'UPDATE [' + DB_NAME() + '].' + tableName + CHAR(13) + CHAR(10) +
           'SET ' + columnName + ' = REPLACE(' + columnName + ', ''' + @SearchStr + ''', ''' + @ReplaceStr + ''')' + CHAR(13) + CHAR(10) +
           'GO' + CHAR(13) + CHAR(10) + CHAR(13) + CHAR(10)
    FROM #StringFound
    ORDER BY tableName, columnName

    SELECT 'EXEC sp_MSforeachtable ''ALTER TABLE ? ENABLE TRIGGER ALL'''

    DROP TABLE #StringFound

    --PRINT CAST(@Counter AS varchar) + ' occurence(s)'

    SET NOCOUNT OFF

Simply execute the query and view the results.  Here is an example of the output script:

----------------------------------------------------------
EXEC sp_MSforeachtable 'ALTER TABLE ? DISABLE TRIGGER ALL'

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
UPDATE [TEMP_DATABASE].[dbo].[SIMPLE_NAME]
SET [FIRST_NAME] = REPLACE([FIRST_NAME], 'ABC', 'XYZ')
GO

UPDATE [TEMP_DATABASE].[dbo].[SIMPLE_NAME]
SET [LAST_NAME] = REPLACE([LAST_NAME], 'ABC', 'XYZ')
GO

UPDATE [TEMP_DATABASE].[dbo].[SIMPLE_NAME]
SET [USERNAME] = REPLACE([USERNAME], 'ABC', 'XYZ')
GO

---------------------------------------------------------
EXEC sp_MSforeachtable 'ALTER TABLE ? ENABLE TRIGGER ALL'

As stated previously, never run this straight onto your database without testing and verifying the results.  You will also probably want to fine tune the script so that its updates are more precise (adding in WHERE clauses to keep the changes more precise).  However, this should give you a good place to start and see what tables and columns you need to look for!

Hope this helps,

Stefan.

For more Web Code, ASP.NET, SQL Server and other development tips, please check back here at webcodeblog.com often.

Posted in SQL Server | Leave a Comment »

Disable all triggers for a database

Posted by Stefan Zvonar on October 4, 2010

Here is a nice little bit of T-SQL that will help with your scripting woes.

Ever wanted to disable all triggers in a database so you can merrily import or merge data and other such tasks?

Well here is what you can do, it simply disables all triggers for all tables in your active database and then enables them again at the end of your changes.


----------------------------------------------------------
EXEC sp_MSforeachtable 'ALTER TABLE ? DISABLE TRIGGER ALL'
----------------------------------------------------------

-- Write some SQL Modifications here

---------------------------------------------------------
EXEC sp_MSforeachtable 'ALTER TABLE ? ENABLE TRIGGER ALL'
---------------------------------------------------------

Hope this helps,

Stefan.

For more Web Code, ASP.NET, SQL Server and other development tips, please check back here at webcodeblog.com often.

Posted in SQL Server | Leave a Comment »

Save and retrieve images / documents into SQL Server using ASP.NET

Posted by Stefan Zvonar on August 22, 2010

In this article I will explain how to save and retrieve VARBINARY(MAX) data into SQL Server using ASP.NET.  You may opt to do this to store images or documents into the database.

So, let us start at the database level.  Below is a simplistic table definition for storing our files into a table:

CREATE TABLE [dbo].[tb_File](
    [FileID] [int] IDENTITY(1,1) NOT NULL,
    [FileName] [varchar](255) NOT NULL,
    [FileData] [varbinary](max) NOT NULL,
    [FileContentType] [varchar](255) NOT NULL,
 CONSTRAINT [PK_TB_FILE] PRIMARY KEY CLUSTERED
(
    [FileID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

And here are two simple stored procedures to save and retrieve the file data:

CREATE PROCEDURE dbo.usp_File_Get
(
     @FileID int
)

AS

BEGIN

    SET NOCOUNT ON

    SELECT FileID
      ,[FileName]
      ,FileData
      ,FileContentType
    FROM tb_File
    WHERE FileID = @FileID
   SET NOCOUNT OFF

END
GO

And

CREATE PROCEDURE dbo.usp_File_Insert
( 
     @FileName varchar(255),
     @FileData varbinary(max),
     @FileContentType varchar(255)
)

AS

BEGIN

    SET NOCOUNT ON
  
    INSERT INTO tb_File ([FileName], FileData, FileContentType)
    VALUES (@FileName, @FileData, @FileContentType) 
    SET NOCOUNT OFF

END
GO

Now that we have the database objects in place, lets create a basic maintenance web page to be able to upload a file and save it to the database:

Markup:

<%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Upload file to database</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                File Name:
            </div>
            <div>
                <asp:TextBox runat="server" ID="txtFileName"></asp:TextBox>
            </div>
            <br />
            <div>
                File:
            </div>
            <div>
                <asp:fileupload runat="server" id="uplFile" />
            </div>
            <br />
            <div>
                <asp:button ID="btnSave" runat="server" text="Save" OnClick="btnSave_Click" />
            </div>
        </form>
    </body>
</html>

Code-Behind:

 Partial Class _Default
    Inherits System.Web.UI.Page

    Private m_strFileName, m_strFileContentType As String
    Private m_intFileLen As Integer
    Private m_byteFileData As Byte()

    Protected Sub btnSave_Click(ByVal sender As Object, ByVal e As EventArgs)

        GetFileData()
        SaveData()

    End Sub

    Private Sub GetFileData()

        Dim myFile As HttpPostedFile = uplFile.PostedFile
        m_strFileName = System.IO.Path.GetFileName(uplFile.FileName)
        m_strFileContentType = myFile.ContentType
        m_intFileLen = myFile.ContentLength
        m_byteFileData = Nothing

        ' make sure the size of the file is > 0
        If m_intFileLen > 0 Then
            ' Allocate a buffer for reading of the file
            m_byteFileData = New Byte(m_intFileLen - 1) {}

            ' Read uploaded file from the Stream
            myFile.InputStream.Read(m_byteFileData, 0, m_intFileLen)

        End If

    End Sub

    Private Sub SaveData()

        Dim conSQL = New SqlClient.SqlConnection
        conSQL.ConnectionString = "Your database connection string"
        conSQL.Open()

        Dim comSQL As New SqlClient.SqlCommand()

        With comSQL

            .CommandText = "dbo.usp_File_Insert"
            .Connection = conSQL
            .CommandType = CommandType.StoredProcedure
            .Parameters.AddWithValue("@FileName", m_strFileName)

            .Parameters.Add("@DocData", SqlDbType.VarBinary, -1)   ' Need to set size to -1 since it is varbinary(max)
            If m_byteFileData Is Nothing Then
                .Parameters("@DocData").Value = System.DBNull.Value
            Else
                .Parameters("@DocData").Value = m_byteFileData
            End If

            .Parameters.AddWithValue("@DocContentType", m_strFileContentType)
            .ExecuteNonQuery()
        End With

        conSQL.Close()
        conSQL.Dispose()

    End Sub

End Class

Now lets look at how we can view the stored binary data through ASP.NET. Below we have a simple handler that is passed a File ID as a Querystring. It will look up the ID in the database and then write the object out as a byte stream.

<%@ WebHandler Language="VB" Class="FileHandler" %>
Imports System
Imports System.Web
Imports System.Web.Services

Public Class FileHandler
    Implements System.Web.IHttpHandler

    Private m_strFileContentType As String = String.Empty
    Private m_objFileData As Object
    Private m_intFileID As Integer

    ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
        Get
            Return False
        End Get
    End Property


    Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest

        GetData(context)
        ShowFile(context)

    End Sub

    Private Sub GetData(ByVal context As HttpContext)

        If context.Request.QueryString("ID") IsNot Nothing AndAlso context.Request.QueryString("ID").Trim() <> String.Empty AndAlso IsNumeric(context.Request.QueryString("ID").Trim()) Then
            m_intFileID = CInt(context.Request.QueryString("ID").Trim())

            Dim conSQL = New SqlClient.SqlConnection
            conSQL.ConnectionString = "Your database connection string"
            conSQL.Open()

            Dim comSQL As New SqlCommand("dbo.usp_File_Get")
            comSQL.Connection = conSQL
            comSQL.Parameters.AddWithValue("@FileID", m_intFileID)
            comSQL.CommandType = CommandType.StoredProcedure

            'read record from database
            Dim drdDrillInstructions As SqlDataReader
            drdDrillInstructions = comSQL.ExecuteReader()
            Try
                If (drdDrillInstructions.HasRows) Then
                    While drdDrillInstructions.Read()
                        m_strFileContentType = drdDrillInstructions.Item("FileContentType").ToString()
                        m_objFileData = drdDrillInstructions.Item("FileData")
                    End While
                End If
            Catch ex As Exception
                ' Publish error here
            Finally
                If Not drdDrillInstructions.IsClosed Then
                    drdDrillInstructions.Close()
                End If
                conSQL.Close()
                conSQL.Dispose()
            End Try
        End If

    End Sub

    Private Sub ShowFile(ByVal context As HttpContext)

        ' Clear out the existing HTTP header information
        context.Response.Expires = 0
        context.Response.Buffer = True
        context.Response.Clear()

        If m_objFileData IsNot Nothing AndAlso (Not DBNull.Value.Equals(m_objFileData)) Then
            context.Response.ContentType = m_strFileContentType
            context.Response.BinaryWrite(DirectCast(m_objFileData, Byte()))
        Else
            context.Response.Write("<html><body bgcolor='#D6EBFF'><p align='center'><br /><br /><br /><br /><br />")
            context.Response.Write("<font class='text'>(No file available)</font>")
            context.Response.Write("</p></body></html>")
        End If

    End Sub

End Class

Hope this helps,

Stefan.

For more Web Code, ASP.NET, SQL Server and other development tips, please check back here at webcodeblog.com often.

Posted in .NET, ASP.NET, SQL Server | 3 Comments »

Finding the size of your SQL Server Databases

Posted by Stefan Zvonar on July 15, 2010

Sometimes you need to know the size of your SQL Server databases (to monitor how large they are growing, managerial reports etc). Here is a quick little script you can run to help you on your way to finding out …

USE master;

-- Retrieve how large a size of a page is
DECLARE @PageSize varchar(10)
SELECT @PageSize=v.low/1024
FROM master..spt_values v
WHERE v.number=1 and v.type='E'

-- Create temporary table listing all application databases
SELECT [name] AS DatabaseName, convert(float, NULL) AS [Size]
INTO #temp
FROM dbo.sysdatabases WHERE dbid > 4

-- Loop through the databases and update the temporary table
DECLARE @SQL varchar (8000)
SET @SQL=''
WHILE EXISTS (SELECT DatabaseName FROM #temp WHERE [Size] IS NULL)
BEGIN
    -- Note: size in sysfiles is in number of pages, so multiply it by size of a page
    SELECT @SQL='UPDATE #temp SET [Size]=(SELECT ROUND(SUM([size])*'+@PageSize+'/1024,0) FROM '+quotename(databasename)+'.dbo.sysfiles)
                                                                                         WHERE DatabaseName=''' + DatabaseName + ''''
    FROM #temp
    WHERE [Size] IS NULL
    EXEC (@SQL)
END

-- Select the report info
SELECT @@ServerName AS Instance,
       DatabaseName,
       LTRIM(RIGHT(LEFT(@@VERSION,37),10)) AS Version,
       convert(varchar, ROUND((Size/1024),4)) AS GB
FROM #temp
ORDER BY DatabaseName

-- Clean up
DROP TABLE #temp

Hope this helps,

Stefan.

For more Web Code, ASP.NET, SQL Server and other development tips, please check back here at webcodeblog.com often.

Posted in SQL Server | Leave a Comment »

SQL Server user-defined function to remove alpha numeric characters

Posted by Stefan Zvonar on May 5, 2010

Here is a SQL Server user-defined function to remove alpha-numeric characters.  Note, you could easily change what is inside the square brackets to remove any other characters as well.

CREATE FUNCTION [dbo].[fn_RemoveNonAlphaNumericCharacters] (@vchrText varchar(8000))
RETURNS varchar(8000)
AS
BEGIN

    -- remove any non alpha numeric character
    While PatIndex('%[^a-zA-Z0-9]%', @vchrText) > 0
        Set @vchrText = Stuff(@vchrText, PatIndex('%[^a-zA-Z0-9&]%', @vchrText), 1, '') 

 RETURN(@vchrText)

END
GO

Hope this helps,

Stefan.

For more Web Code, ASP.NET, SQL Server and other development tips, please check back here at webcodeblog.com often.

Posted in SQL Server | Leave a Comment »

Setting up Google Maps for ASP.NET and SQL Server

Posted by Stefan Zvonar on April 22, 2010

In this article I will describe how you can create a simple location search feature with Google Maps using ASP.NET and SQL Server.  The basis of this article comes from the Google Maps API Documentation (http://code.google.com/apis/maps/articles/phpsqlsearch.html)

The first thing you will want to do is sign up and get a Google Maps API Key.  You will need one for the domain name of where you will be hosting the map.  You can get your API Key from here (http://code.google.com/apis/maps/signup.html).  Hold onto this key, you will need it later.

The end result will look something like this:

Google Map Example

Google Map Example

First, we need to set up the database.  You will want to store all the locations in a database table.  So lets create this table in SQL Server by using code like the below:

CREATE TABLE [dbo].[tblLocation](
    [LocationID] [int] IDENTITY(1,1) NOT NULL,
    [LocationDescription] [nvarchar](100) NOT NULL,
    [Address1] [nvarchar](50) NOT NULL,
    [Address2] [nvarchar](50) NULL,
    [Town] [nvarchar](50) NOT NULL,
    [County] [nvarchar](50) NULL,
    [Postcode] [nvarchar](10) NOT NULL,
    [Country] [nvarchar](50) NOT NULL,
    [Latitude] [decimal](10, 7) NOT NULL CONSTRAINT [DF_tblLocation_Latitude]  DEFAULT ((0)),
    [Longitude] [decimal](10, 7) NOT NULL CONSTRAINT [DF_tblLocation_Longitude]  DEFAULT ((0))
 CONSTRAINT [PK_tblLocation] PRIMARY KEY CLUSTERED
(
    [LocationID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, 
ALLOW_PAGE_LOCKS  = ON, FILLFACTOR = 80) ON [PRIMARY]
) ON [PRIMARY]

GO

You can then pre-populate this table with your store locations. Here is an SQL example:

insert into dbo.tblLocation(LocationDescription, Address1, Address2, Town, County, Postcode, Country,
                            Latitude, Longitude)
values('Earls Court', '50 Earls Court Road', 'Earls Court', 'London', NULL, 'W8 6JE', 'United Kingdom',
       51.49635, -0.19664)

insert into dbo.tblLocation(LocationDescription, Address1, Address2, Town, County, Postcode, Country,
                            Latitude, Longitude)
values('Cromwell Place', '50 Cromwell Road', 'Earls Court', 'London', NULL, 'W8 6JE', 'United Kingdom',
       51.49464, -0.19209)

insert into dbo.tblLocation(LocationDescription, Address1, Address2, Town, County, Postcode, Country,
                            Latitude, Longitude)
values('South Kensington Mall', '10 Old Brompton Road', 'South Kensington', 'London', NULL, 'W8 6JE', 'United Kingdom',
       51.49295, -0.17728)

Note, to get the actual latitudes and longitudes for your locations, you can either use your local postal service data or use Google Maps. You can read my post about obtaining latitude and longitude co-ordinates for an address here (http://webcodeblog.com/2010/04/24/obtain-latitude-and-longitude-co-ordinates-for-an-address-using-asp-net-and-the-google-maps-api/)

Now that you have a locations table and some location data, you will want to create a stored procedure to find all the locations based on some parameters. This will use some maths to find the nearest locations based on a passed in co-ordinate (Haversine formula).  Do not worry so much about the maths of it unless you really want to.  We also want the code to return XML as we will eventually be looking at the resultset with JavaScript.

Here is the basic procedure code:

CREATE PROCEDURE [dbo].[proc_Location_List]
    (
        @dmlLat decimal(10, 7),
        @dmlLng decimal(10, 7),
        @intRadius int
    )
    As

SET NOCOUNT ON

DECLARE @intMilesModifier int
SET @intMilesModifier = 3959  -- If using kilometers, use 6371 instead of 3959

-- Select locations that are near the parameters based on distance formula on a sphere
SELECT LocationID, LocationDescription, Address1, Address2, Town, County, Postcode, Latitude, Longitude,
      (@intMilesModifier*acos(cos(radians(@dmlLat))*cos(radians(Latitude))*cos(radians(Longitude)-
       radians(@dmlLng))+sin(radians(@dmlLat))*sin(radians(Latitude)))) AS distance
FROM dbo.tblLocation
WHERE (@intMilesModifier*acos(cos(radians(@dmlLat))*cos(radians(Latitude))*cos(radians(Longitude)-
       radians(@dmlLng))+sin(radians(@dmlLat))*sin(radians(Latitude)))) < @intRadius

-- Return XML for formatting results
FOR XML RAW('marker'),ROOT('markers')

SET NOCOUNT OFF
GO

Executing the above stored procedure will render XML like the below:

<markers>
    <marker LocationID="1" LocationDescription="Earls Court" Address1="50 Earls Court Road" 
            Address2="Earls Court" Town="London" Postcode="W8 6LE" Latitude="51.4963500"
            Longitude="-0.1966400" distance="1.669166379205019e-001" />
    <marker LocationID="2" LocationDescription="Cromwell Place" Address1="50 Cromwell Road"
            Address2="Earls Court" Town="London" Postcode="W8 6LE" Latitude="51.4946400" 
            Longitude="-0.1920900" distance="2.747850170042376e-001" />
    <marker LocationID="3" LocationDescription="South Kensington Mall" Address1="10 Old Brompton Road"
            Address2="South Kensington" Town="London" Postcode="W8 6LE" Latitude="51.4929500"
            Longitude="-0.1772800" distance="9.144638212456977e-001" />
</markers>

That completes the SQL Server side of it. Now it is time to create some basic ASP.NET pages to show the location results.

You will essentially want two pages 1) A Search Request Page and 2) A Search Response Page.

Here is an snippet of code that demonstrates how the Search Request Page could look like (SearchRequest.aspx):

<div style="margin-left:20px;">


    <div style="margin-left: 5px;">
        <span style="font-size: 10px;">Please enter an address, town or post code and click search</span>
        <br />
        Address: <input type="text" id="txtSearchAddress" value="High Street Kensington" />&nbsp;
        Country: <input type="text" id="txtSearchCountry" value="United Kingdom" />&nbsp;
        Distance:
        <select id="ddlRadius">
          <option value="5" selected>5 miles</option>
          <option value="10">10 miles</option>
          <option value="25">25 miles</option>
          <option value="50">50 miles</option>
          <option value="100">100 miles</option>
        </select>&nbsp;
        <input type="button" onclick="searchLocations()" value="Search"/>
    </div>

    <br />

    <div style="clear:both; margin-left: 5px;">
        <div id="sidebar" style="overflow: auto; height: 400px; width:150px; font-size: 11px; color: #000; 
             float:left; margin-left:5px; padding-left:5px;">Search Results:</div>
        <div style="float:left; margin-left: 5px;">
            <div id="GoogleMap_Div_Container">
                <div id="map" style="width:750px;height:400px;"></div>
            </div>
            <!-- Google Map API Key -->
            <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=YabbaDabbaDoo"  
                    type="text/javascript"></script>

        </div>

    </div>

    <br style="clear:both;" />

    

</div>   

Note: In the above, you might want to change the key value of “YabbaDabbaDoo” to your own API Key that you generated when you registered with Google Maps.

On the same page, we will load all the Google map JavaScript that sets up the map and the calls to the Google Maps API. Here is an example of the JavaScript you will need on the page:

<script language="javascript" type="text/javascript">

    // Google Map API Javascript

    var map;
    var geocoder;
    var http_request = false;
    var lat = 0;
    var lng = 0;
    var startingLat = 54.1509;  // view of england
    var startingLng = -4.4855;  // view of england
    var startingZoom = 5;
    var maximumZoom = 15;

    function mapLoad() {
        if (GBrowserIsCompatible()) {
            geocoder = new GClientGeocoder();
            map = new GMap2(document.getElementById('map'));
            map.addControl(new GSmallMapControl());
            map.addControl(new GMapTypeControl());
            map.enableScrollWheelZoom();
            map.setCenter(new GLatLng(startingLat, startingLng), startingZoom);
        }

    }

    function searchLocations() {

        var address = document.getElementById('txtSearchAddress').value;
        var country = document.getElementById('txtSearchCountry').value;
        var searchString = address + ', ' + country;

        geocoder.getLatLng(searchString, function(latlng) {
            if (!latlng) {
                alert(searchString + ' - not found');
            } else {
                searchLocationsNear(latlng);
            }
        });
    }

    function searchLocationsNear(center) {
        var radius = document.getElementById('ddlRadius').value;
        var searchUrl = 'SearchResponse.aspx?lat=' + center.lat() + '&lng=' + center.lng() + '&radius=' + radius;

        GDownloadUrl(searchUrl, function(data) {
            var xml = GXml.parse(data);
            var sidebar = document.getElementById('sidebar');
            sidebar.innerHTML = '';
            map.clearOverlays();

            if (xml.documentElement == null) {
                sidebar.innerHTML = 'No results found.  Please try widening your search area.';
                map.setCenter(new GLatLng(startingLat, startingLng), startingZoom);
                return;
            }

            var markers = xml.documentElement.getElementsByTagName('marker');

            if (markers.length == 0) {
                sidebar.innerHTML = 'No results found.  Please try widening your search area.';
                map.setCenter(new GLatLng(startingLat, startingLng), startingZoom);
                return;
            }

            var bounds = new GLatLngBounds();
            for (var i = 0; i < markers.length; i++) {
                var address1 = markers[i].getAttribute('Address1');
                var address2 = markers[i].getAttribute('Address2');
                var town = markers[i].getAttribute('Town');
                var postcode = markers[i].getAttribute('Postcode');
                var distance = parseFloat(markers[i].getAttribute('distance'));
                var point = new GLatLng(parseFloat(markers[i].getAttribute('Latitude')),
                             parseFloat(markers[i].getAttribute('Longitude')));

                var marker = createMarker(point, address1, address2, town, postcode);

                map.addOverlay(marker);
                var sidebarEntry = createSidebarEntry(marker, address1, address2, town, distance);
                sidebar.appendChild(sidebarEntry);
                bounds.extend(point);
            }

            var pointCenter = bounds.getCenter();
            var iZoomLevel = map.getBoundsZoomLevel(bounds);
            if (iZoomLevel > maximumZoom) { iZoomLevel = maximumZoom; }
            map.setCenter(pointCenter, iZoomLevel);

        });
    }

    function createMarker(point, address1, address2, town, postcode) {
        var marker = new GMarker(point);
        var html;
        if (address2 == null) {
            html = '<br/>' + address1 + '<br/>' + town + '<br/>' + postcode;
        }
        else {
            html = '<br/>' + address1 + ', ' + address2 + '<br/>' + town + '<br/>' + postcode;
        }
        GEvent.addListener(marker, 'click', function() {
            marker.openInfoWindowHtml(html);
        });
        return marker;
    }

    function createSidebarEntry(marker, address1, address2, town, distance) {
        var div = document.createElement('div');
        var address;
        if (address2 == '' || address2 == null) {
            address = address1 + '<br/>' + town;
        }
        else {
            address = address1 + '<br/>' + address2 + '<br/>' + town;
        }
        var html = '<b>' + distance.toFixed(2) + ' miles: </b><br/>' + address;
        div.innerHTML = html;
        div.style.cursor = 'pointer';
        div.style.marginBottom = '5px';
        GEvent.addDomListener(div, 'click', function() {
            GEvent.trigger(marker, 'click');
        });
        GEvent.addDomListener(div, 'mouseover', function() {
            div.style.backgroundColor = '#eee';
        });
        GEvent.addDomListener(div, 'mouseout', function() {
            div.style.backgroundColor = '#fff';
        });
        return div;
    }

</script>

When the page loads for the first time, we want to make sure we load up the map JavaScript variables. So in the code-behind of this SearchRequest.aspx page, we will add something like the below in the page load event handler:

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        Page.ClientScript.RegisterStartupScript(Me.GetType(), "mapLoad", 
                                           "<script language='javascript'> mapLoad(); </script>")

    End Sub

Okay, so all that is left is the guts for the page that will return the XML. If you create a new page (e.g. SearchResponse.aspx) in the same directory as the Search Request page, then all it needs to do is call the stored procedure that will return the locations as XML and return an XML response. The aspx will be empty except for the page declaration. The code-behind of this Search Response Page will look like the below:

Imports System.Xml

Partial Class SearchResponse
    Inherits System.Web.UI.Page

    ' Page Load
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        Dim dmlSearchLatitude As Double
        Dim dmlSearchLongitude As Double
        Dim intSearchRadius As Int32

        If Request.QueryString("lat") <> "" Then
            dmlSearchLatitude = CDbl(Request.QueryString("lat"))
        End If
        If Request.QueryString("lng") <> "" Then
            dmlSearchLongitude = CDbl(Request.QueryString("lng"))
        End If
        If Request.QueryString("radius") <> "" Then
            intSearchRadius = CInt(Request.QueryString("radius"))
        End If

        ' Here you make the call to your locations stored procedure
        ' This database call is a little messy but is just to show you the point.
        ' You should really use the MS Application Blocks and/or some other seperate data layer
        Dim connDB = New SqlClient.SqlConnection
        Dim cmd As New SqlClient.SqlCommand
        connDB.ConnectionString = "Your database connection string"
        connDB.Open()
        cmd.Connection = connDB
        cmd.CommandType = CommandType.StoredProcedure
        cmd.CommandTimeout = 20
        cmd.CommandText = "dbo.proc_Location_List"
        cmd.Parameters.AddWithValue("@dmlLat", dmlSearchLatitude)
        cmd.Parameters.AddWithValue("@dmlLng", dmlSearchLongitude)
        cmd.Parameters.AddWithValue("@intRadius", intSearchRadius)

        ' Execute the stored procedure and return the result as plain XML
        Dim rdrXMLLocations As XmlReader = Nothing
        rdrXMLLocations = cmd.ExecuteXmlReader()

        Response.Expires = 0
        Response.ContentType = "text/xml"
        Dim oDocument As New XmlDocument()
        Dim sb As New System.Text.StringBuilder()
        Using rdrXMLLocations
            While Not rdrXMLLocations.EOF
                rdrXMLLocations.MoveToContent()
                sb.Append(rdrXMLLocations.ReadOuterXml())
            End While
            rdrXMLLocations.Close()
        End Using
        oDocument.LoadXml(sb.ToString())
        oDocument.Save(Response.Output)
        Response.OutputStream.Flush()
        Response.OutputStream.Close()

    End Sub

End Class

Hopefully that should be enough to get the Google Maps API in action for you.

Click here to download a zip file of the example code used in this article.

Hope this helps,

Stefan.

For more Web Code, ASP.NET, SQL Server and other development tips, please check back here at webcodeblog.com often.

Posted in .NET, ASP.NET, Google Maps API, JavaScript / jQuery, SQL Server | 14 Comments »

Split Function (SQL Server)

Posted by Stefan Zvonar on March 25, 2010

I always seem to need a split function in SQL Server and I can never remember which one I have used previously. This is one that I am using currently and it seems to be working quite well.

CREATE FUNCTION [dbo].[fn_Split]
(
    @List varchar(8000),
    @SplitOn varchar(1)
)
RETURNS @RtnValue table (
    Id int identity(1,1),
    SplitValue nvarchar(100)
)
AS
BEGIN
    While (Charindex(@SplitOn,@List)>0)
    Begin
        Insert Into @RtnValue (SplitValue)
        Select
            SplitValue = ltrim(rtrim(Substring(@List,1,Charindex(@SplitOn,@List)-1)))
        Set @List = Substring(@List,Charindex(@SplitOn,@List)+len(@SplitOn),len(@List))
    End 

    If Len(@List) > 0
        Begin
            Insert Into @RtnValue (SplitValue)
            Select SplitValue = ltrim(rtrim(@List))
        End
    Return
END

Which I pretty much constructed straight from this article

Here is an example on how to call the split function:

SELECT SplitValue
FROM dbo.fn_Split('1,2,3,4,5', ',')

Hope this helps,

Stefan.

For more Web Code, ASP.NET, SQL Server and other development tips, please check back here at webcodeblog.com often.

Posted in SQL Server | 1 Comment »

 
Follow

Get every new post delivered to your Inbox.