Navigation

Search

Categories

On this page

Getting column information using T-SQL
SqlConnection.RetrieveStatistics()
How to Share Data Between Stored Procedures
Connection String has not been properly iniitialized
Finding a column name in SQL Server
Optional Parameters for Stored Procedures
Using SELECT to INSERT records
How to Insert Values into an Identity Column in SQL Server
Comparing Records from Two Tables
Update records from two tables
Using XP_EXECRESULTSET To Obtain Database Size Information
Preventing Duplicate Logins
A SQL Query to Find Duplicate Values
SQL: Preventing Duplicate Records In a Database In a Signup Form
SQL Server CASE-WHEN Statement

Archive

Blogroll

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

RSS 2.0 | Atom 1.0 | CDF

Send mail to the author(s) E-mail

Total Posts: 103
This Year: 41
This Month: 6
This Week: 2
Comments: 0

Sign In

 Wednesday, April 30, 2008
Wednesday, April 30, 2008 1:25:42 PM (Eastern Standard Time, UTC-05:00) (  |  )


SELECT ORDINAL_POSITION , COLUMN_NAME , DATA_TYPE , CHARACTER_MAXIMUM_LENGTH , IS_NULLABLE , COLUMN_DEFAULT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'MyAwesomeTable' ORDER BY ORDINAL_POSITION ASC;
Another method is
sp_help 'tablename' 
 
 Monday, April 21, 2008
Monday, April 21, 2008 1:28:00 PM (Eastern Standard Time, UTC-05:00) (  |  )


The SqlConnection object has a new RetrieveStatistics method in .NET 2.0, which provides some interesting statistics that could come in handy while debugging and performance tuning your .NET applications.  Normally statistics are turned off by default, so you have to set StatisticsEnabled = true in order for the SqlConnection object to begin collecting statistics.  Here is a working demo.

<%@ Page Language="VB" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Web.Configuration" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
    
    Sub Page_Load()
        Dim connectionString As String = WebConfigurationManager.ConnectionStrings("MyDatabase").ConnectionString
        Dim con As New SqlConnection(connectionString)
        Dim cmd As New SqlCommand("WAITFOR DELAY '0:0:03';SELECT Title,Director FROM Movies", con)
        con.StatisticsEnabled = True
        Using con
            con.Open()
            Dim reader As SqlDataReader = cmd.ExecuteReader()
        End Using
        grdStats.DataSource = con.RetrieveStatistics()
        grdStats.DataBind()
    End Sub
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <style type="text/css">
        td,th
        {
            padding:4px 20px;
        }
    </style>
    <title>Show All Statistics</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <h1>Connection Statistics</h1>
    
    <asp:GridView
        id="grdStats"
        AutoGenerateColumns="false"
        Runat="server">
        <Columns>
        <asp:BoundField DataField="Key" HeaderText="Key" />
        <asp:BoundField DataField="Value" HeaderText="Value" />
        </Columns>    
    </asp:GridView>
    
    </div>
    </form>
</body>
</html>
 Tuesday, April 08, 2008
Tuesday, April 08, 2008 12:35:23 PM (Eastern Standard Time, UTC-05:00) ( )


Erland Sommarskog, a SQL Server MVP, has a nice entry on how to share data between stored procedures.  The example I'll use the most often is using OUTPUT parameters

This method can only be used when the result set is one single row. Nevertheless, this is a method that is sometimes overlooked. Say you have this simple stored procedure:

CREATE PROCEDURE insert_customer @name    nvarchar(50),
                                 @address nvarchar(50),
                                 @city    nvarchar(50) AS
DECLARE @cust_id int
BEGIN TRANSACTION
SELECT @cust_id = coalesce(MAX(cust_id), 0) + 1 FROM customers (UPDLOCK)
INSERT customers (cust_id, name, address, city)
   VALUES (@cust_id, @name, @address, @city)
COMMIT TRANSACTION
SELECT @cust_id
That is, the procedure inserts a row into a table, and returns the id for the row.

Rewrite this procedure as:

CREATE PROCEDURE insert_customer @name    nvarchar(50),
                                 @address nvarchar(50),
                                 @city    nvarchar(50),
                                 @cust_id int OUTPUT AS
BEGIN TRANSACTION
SELECT @cust_id = coalesce(MAX(cust_id), 0) + 1 FROM customers (UPDLOCK)
INSERT customers (cust_id, name, address, city)
   VALUES (@cust_id, @name, @address, @city)
COMMIT TRANSACTION

You can now easily call insert_customer from another stored procedure. Just recall that in T-SQL you need to specify the OUTPUT keyword also in the call:

EXEC insert_customer @name, @address, @city, @cust_id OUTPUT
 Wednesday, November 21, 2007
Wednesday, November 21, 2007 12:45:26 PM (Eastern Standard Time, UTC-05:00) (  |  |  )

I Was recently asked to move a standalone application inside of WSS 3.0. No matter what I tried I was getting an error message "Connection String has not been properly iniitialized" even though it worked fine in a separate app. The problem turned out to be that the first version which worked in version 1.1 does not work ASP.NET 2.0. In the first example, the connection string is pulling it's connection info from the <AppSettings> section. Version 2.0 doesn't use <appsettings> and instead uses <ConnectionStrings> so the SqlConnection and SqlCommand objects have to be structured a little differently.

********************

My typical method which worked fine in a separate app but did not work in WSS:

Dim strSQLText As String = "SELECT UserID, Name FROM tblUsers ORDER BY Name"
Dim con As SqlConnection = New SqlConnection(ConfigurationSettings.AppSettings("QCentralConnectionString"))
Dim cmd As New SqlCommand(strSQLText, con)
con.Open()
ddluserID.DataSource = cmd.ExecuteReader()
ddluserID.DataBind()
con.Close()

web.config:

<appSettings>       
    <add key="QCentralConnectionString" value="server=localhost; database=QCentral; uid=xxx; pwd=xxx" />   
</appSettings>

********************

This version worked in WSS 3.0 (ASP.NET 2.0):

Dim strSQLText As String = "SELECT UserID, Name FROM tblUsers ORDER BY Name"
Dim con As New SqlConnection(ConfigurationManager.ConnectionStrings("QCentralConnectionString").ConnectionString)
Dim cmd As New SqlCommand(strSQLText)
con.Open()
cmd.Connection = con
ddluserID.DataSource = cmd.ExecuteReader()
ddluserID.DataBind()
con.Close()

web.config:

<configuration>
<connectionStrings>
<clear />
<add name="QCentralConnectionString" connectionString="Data Source=localhost;Initial Catalog=QCentral;User ID=xxx;Password=xxx" providerName="System.Data.SqlClient" /> 
</connectionStrings>

********************

 Tuesday, November 13, 2007
Tuesday, November 13, 2007 12:21:03 PM (Eastern Standard Time, UTC-05:00) (  |  )

Use this to find out where a given field name or column reside and is being used in SQL Server.

select so.name
from sysobjects so inner join syscolumns sc
ON so.id = sc.id where sc.name = 'YourColumnNameGoesHere'

or

select so.name, sc.name
from sysobjects so inner join syscolumns sc
ON so.id = sc.id where sc.name like '%YourPartialColumnNameGoesHere%'

or

Select table_name from information_Schema.columns where column_name='yourCol'

 

 Sunday, November 04, 2007
Sunday, November 04, 2007 9:05:42 PM (Eastern Standard Time, UTC-05:00) ( )

I feel as though I've written a thousand search forms where a user is presented with a form and then can pick from a list of fields in a form to perform a search against. In the past I've always just build a dynamic WHERE clause looking for a value in each field of the form and then appending that value(s) to the SQL statement i.e.:

Dim strReturnTrend As String = ddlReturnTrend.SelectedValue
If strReturnTrend <> "" Then
   If blnNotFirst = True Then SqlText += " AND "
   SqlText += " ReturnTrend= '" & strReturnTrend & "'"
   blnNotFirst = True
End If

You can avoid this lengthy process of building a dynamic WHERE clause by using default values and optional parameters in your stored procedures.

create procedure [dbo].[sp_search_directors]
@Company varchar(50) = null,
@DirectorFirstName varchar(50) = '',
@DirectorLastName varchar(50) = '',
@Address varchar(50) = ''
@Title varchar(50) = ''

AS

SELECT
Company,
Director_FirstName,
Director_LastName,
Address,
Title

FROM
TDirectors

WHERE
Company = IsNull(@Company, Company),
and Director_FirstName LIKE
case @DirectorFirstName when '' then Director_FirstName
else @DirectorFirstName +'%' end
and Director_LastName LIKE
case @DirectorLastName when '' then Director_LastName
else @DirectorLastName +'%' end
and Address like
case @Address when '' then Address
else @Address + '%' end
and Title like
case @Title when '' then Title
else '%' + @Title + '%' end

This single query can search for directors by any combination of the parameters.  The conditions in the where clause are such that when the default value is used, the condition will always be true. So to search for directors by a title starting with 'Big Money' the call would look like the following:

sp_search_directors null,'','','','Big Money'

The conditional logic contains a couple of different ways to do this and they are both equivalent. 

 First, if the default value of a parameter is null then the IsNull construct of the first condition is a simple way to implement this. 

In the second example, the case statement is used to test for the default value and simply subsitute the field value in place of the parameter value, if the parameter value is something other than the default,  then the parameter value is used instead.

For the directors name and address, the condition is a "starts with" match.  For the title the condition is a "contains" match.

One more thing to remember, in SQL, a null = null is not a match.  So if the data columns are allowed to have null values, we need to use IsNull to supply a dummy value for the match and in that case the syntax for the condition will be as follows:

and IsNull(Director_FirstName,'') like
case @DirectorFirstName when '' then IsNull(Director_FirstName,'')
else @DirectorFirstName +'%' end

 Not everyone has a first name so we allow that field to be null.  If the value of the director field is null, it will be converted to an empty string so instead of null = null which does not match, we now have ''='' ( 'empty string' = 'empty string'  ) which does match.

 Wednesday, September 26, 2007
Wednesday, September 26, 2007 2:51:56 PM (Eastern Standard Time, UTC-05:00) (  |  )

I found this article on one of my favorite sites on how to insert new records using an embedded SELECT statement.  Here is an example:

INSERT california_authors (au_id, au_lname, au_fname)
SELECT au_id, au_lname, au_fname
FROM authors
WHERE State = 'CA'

http://www.sqlteam.com/article/using-select-to-insert-records

 Wednesday, August 08, 2007
Wednesday, August 08, 2007 9:09:12 AM (Eastern Standard Time, UTC-05:00) (  |  )

Inserting a value into an identity column (primary key) is something I need to do often but usually forget how to do.  It's actually pretty easy so I made myself some notes on this topic.

If you try and run this in QA in SQL Server, you'll receive the error below because ID is the PK for tableA 

INSERT tableA(ID, TheValue)
VALUES (1, 'First Row')
GO

Msg 544, Level 16, State 1, Line 3
Cannot insert explicit value for identity column in table 'tableA' when IDENTITY_INSERT is set to OFF.

The trick is to enable IDENTITY_INSERT for the table like this

SET IDENTITY_INSERT tableA ON

INSERT tableA(ID, TheValue)
VALUES (3, 'First Row')

SET IDENTITY_INSERT tableA OFF

 Monday, July 23, 2007
Monday, July 23, 2007 10:09:52 AM (Eastern Standard Time, UTC-05:00) ( )

I picked this up from SQLTeam.com and it's come in handy a few times.

CREATE PROCEDURE CompareTables(

@table1 varchar(100), 
@table2 Varchar(100),
@T1ColumnList varchar(1000),
@T2ColumnList varchar(1000) = '')
AS

-- Table1, Table2 are the tables or views to compare.
-- T1ColumnList is the list of columns to compare, from table1.
-- Just list them comma-separated, like in a GROUP BY clause.
-- If T2ColumnList is not specified, it is assumed to be the same
-- as T1ColumnList. Otherwise, list the columns of Table2 in
-- the same order as the columns in table1 that you wish to compare.
--
-- The result is all records from either table that do NOT match
-- the other table, along with which table the record is from.

declare @SQL varchar(8000);

IF @t2ColumnList = '' SET @T2ColumnList = @T1ColumnList

set @SQL = 'SELECT ''' + @table1 + ''' AS TableName, ' + @t1ColumnList +
' FROM ' + @Table1 + ' UNION ALL SELECT ''' + @table2 + ''' As TableName, ' +
@t2ColumnList + ' FROM ' + @Table2

set @SQL = 'SELECT Max(TableName) as TableName, ' + @t1ColumnList +
' FROM (' + @SQL + ') A GROUP BY ' + @t1ColumnList +
' HAVING COUNT(*) = 1'

exec ( @SQL)

 Wednesday, July 11, 2007
Wednesday, July 11, 2007 3:17:55 PM (Eastern Standard Time, UTC-05:00) ( )

UPDATE table1
SET col1 = t2.col1
FROM table1 AS t1
JOIN table2 AS t2
ON t1.col2 = t2.col2

 Tuesday, June 26, 2007
Tuesday, June 26, 2007 10:01:39 PM (Eastern Standard Time, UTC-05:00) (  |  )

Came across this article and code which provides a script for obtaining the size of every database on your SQL Server 2000 box. Very nice...

http://www.sqlservercentral.com/columnists/jtshyman/usingxp_execresultsettoobtaindatabasesizeinformati.asp

 Monday, June 18, 2007
Monday, June 18, 2007 11:21:48 AM (Eastern Standard Time, UTC-05:00) (  |  )

I recently had to put into place a method that prevented more than one user from logging into our company web site under the same login ID. With the help of a friend, this is what we came up with. 

Basically, each time a user logs in, the user's UserID and sessionID are stored in a database table at login.  Each time the user visits a page in the site, the user's session ID is matched against what is stored in the database.  If the sessionIDs do not match, then the user is logged out and redirected to a page that says something like "duplicate login detected.  you're a cheating bum. etc."

A typical scenario would work like this:

User A logs into the site and the sessionID is recorded into the dbase. She browses from page and page within the site and on each page her sessionID is verified against what's stored in the database.

User B then logs into the site using User A's ID and password. The sessionID information stored in the database for User A is overwritten by User B. When user A tries to browse to another page, she receives a message stating that someone has logged into the site using her ID.  User A can log back in, but then User B will receive the duplicate login message and round and round they go.

Here's the code for it.

This stored procedure is called on the login page. If the user's userID is not already in the datbase, then it is inserted as a new record. Otherwise, the existing record is updated with the new session ID.

CREATE Procedure sp_LoginStatusCheck
@UserID as Int,
@SessionID as nvarchar(100)


AS
    IF (SELECT COUNT(UserID)
FROM TLoginStatus
WHERE UserID = @UserID) = 0
        
BEGIN
INSERT INTO TLoginStatus(UserID, SessionID) Values (@UserID, @SessionID)
    
END
ELSE
         UPDATE TLoginStatus
         SET SessionID = @SessionID
         WHERE UserID = @UserID
GO

Call sp_LoginStatusCheck in the login page.

Sub AddSessionCheck(ByVal UserID As Integer)
'Due to some bizarre requirement that there must be some value assigned to a Session variable not to lose
'the value for SessionID, I created a random session variable and value            
Session("BA") = "BA"
            
            
Dim myConnection As SqlConnection
Dim cmd As SqlCommand
            
myConnection = New SqlConnection(ConfigurationSettings.AppSettings("ConnectionString"))
cmd = New SqlCommand("sp_LoginStatusCheck", myConnection)
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.Add("@UserID", SqlDbType.Int, 4).Value = UserID
cmd.Parameters.Add("@SessionID", SqlDbType.NVarChar, 100).Value = Session.SessionID
                        
Try
myConnection.Open()
cmd.ExecuteNonQuery()
Finally
myConnection.Close()
End Try

End Sub


On each page that I Want to check for a duplicate login, I would first get the userID value which is stored in a cookie value in the Page_Load function. 

<%@ Page Language="VB" Inherits="MyBaseClass" src="~/classes/MyBaseClass.vb" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SQLClient" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
Sub Page_Load()
If Not IsPostBack Then
Dim strUserID As String
If Not IsNothing(Request.Cookies("BA")) Then
strUserID = Request.Cookies("BA")("UserID").ToString()
lblUserID.Value = strUserID
End If


'Call CheckLoginStatus
    CheckLogin(strUserID)
    
End If
End Sub
</script>

I created a new class called MyBaseClass with a sub called CheckLogin that accepts UserID as a parameter. This gets called on every page that I check.

Imports Microsoft.VisualBasic
Imports System
Imports System.Data
Imports System.Data.SqlClient
Imports System.Web.Configuration
Imports System.Web.HttpContext

Public Class MyBaseClass
Inherits System.Web.UI.Page

Public Shared Sub CheckLogin(ByVal UserID As Integer)

'This entire section is the duplicate login prevention section
System.Web.HttpContext.Current.Session("Spice") = "Ginger"
Dim strSessionID = System.Web.HttpContext.Current.Session.SessionID
Dim strLoginStatus As String

Dim MyConnection As SqlConnection
Dim Cmd As SqlCommand

MyConnection = New SqlConnection(ConfigurationSettings.AppSettings("ConnectionString"))
Cmd = New SqlCommand("sp_LoginStatusCheck2", MyConnection)
Cmd.CommandType = CommandType.StoredProcedure
Cmd.Parameters.Add("@UserID", SqlDbType.Int, 4).Value = UserID
Cmd.Parameters.Add("@SessionID", SqlDbType.NVarChar, 100).Value = strSessionID
Cmd.Parameters.Add("@LoginStatus", SqlDbType.Bit).Direction = ParameterDirection.Output

Try
MyConnection.Open()
Cmd.ExecuteNonQuery()
strLoginStatus = Cmd.Parameters(2).Value
'System.Web.HttpContext.Current.Response.Write(strLoginStatus)
Finally
MyConnection.Close()
End Try

If strLoginStatus = "False" Then
System.Web.HttpContext.Current.Response.Redirect("/duplicate.login.aspx")
End If

End Sub

End Class

Here's the code for the stored procedure sp_LoginStatusCheck2

CREATE Procedure sp_LoginStatusCheck2
@UserID as Int,
@SessionID as nvarchar(100),
@LoginStatus as Bit OUTPUT

--We don't want to check the LoginStatus of enterprise level accounts UserID 884, UserID 7996
AS
IF @UserID=884
    SET @LoginStatus = 1
ELSE IF @UserID=7996 
    SET @LoginStatus = 1
ELSE    

    IF (SELECT COUNT(*) FROM TLoginStatus WHERE UserID = @UserID AND SessionID = @SessionID) > 0
        SET @LoginStatus = 1
    ELSE
        SET @LoginStatus = 0
GO

 Thursday, June 07, 2007
Thursday, June 07, 2007 10:11:39 AM (Eastern Standard Time, UTC-05:00) ( )

SELECT <KEY_COLUMN>, COUNT(*)
FROM <TABLE>
GROUP BY <KEY_COLUMN>
HAVING COUNT(*) > 1

 Wednesday, June 06, 2007
Wednesday, June 06, 2007 1:26:16 PM (Eastern Standard Time, UTC-05:00) ( )

I use this on signup forms to prevent a user from registering a second account with the same email address.

CREATE Procedure sp_AddSubscriber
@Name as nvarchar(50),
@Email as nvarchar(50),
@AddSubscriber varchar(255) OUTPUT

AS
 IF (SELECT COUNT(Email)
                  FROM TSubscribers
                  WHERE Email = @Email) = 0
  
  BEGIN INSERT TSubscribers
  (Name, Email)  Values  (@Name, @Email)
SET @AddSubscriber = "True"                           
END              
ELSE
     SET @AddSubscriber = "The email address you have entered has already been registered in our database."
GO

 Tuesday, June 05, 2007
Tuesday, June 05, 2007 12:32:07 PM (Eastern Standard Time, UTC-05:00) ( )

I was recently working on a project where I had to return the larger of two values in my T-SQL code.  COALESCE would not work because often one of the two values was not NULL which COALESCE requires.  The solution ended up being relatively simple by using a CASE-WHEN statement like below.

CASE
  WHEN CEOAnnualBonus > CEOBonus THEN CEOAnnualBonus
  WHEN CEOBonus IS NULL THEN CEOAnnualBonus
  ELSE CEOBonus
END 
AS CEOBonusCombined,
CASE
  WHEN CEOOtherAnnualComp > CEOAllOtherCompensation THEN CEOOtherAnnualComp
  WHEN CEOAllOtherCompensation IS NULL THEN CEOOtherAnnualComp
  ELSE
  CEOAllOtherCompensation
  END 
AS CEOAnnualCompCombined 

Also, I learned about using ISNULL to return a different value when you encounter a NULL value in your data.  This helps clean it up at run time.

ISNULL(dbo.TBenchmarkTemp.ChairIndependent, '-') AS ChairIndependent