Moved over to foxtricks.posterous.com
I move over the blog to http://foxtricks.posterous.com Most posts from this blog can be found over there. You will be redirected in a few seconds.
Mostly Microsoft SQL Server related tips and tricks.
I move over the blog to http://foxtricks.posterous.com Most posts from this blog can be found over there. You will be redirected in a few seconds.
Posted by Filip De Vos at 4:01 PM 0 comments
Every SQL Server developer and DBA has to use the sp_helpindex from time to time to get some information on the indexes on a table. I have 2 major problems with this stored procedure:
exec sp_helpindex 'Customer'
index_name | index_description | index_keys |
---|---|---|
pk_entity | clustered, unique, primary key located on PRIMARY | entity |
CREATE FUNCTION dbo.fn_helpindex(@table_name sysname) RETURNS @result table(table_name sysname, index_name sysname, is_unique bit, ignore_dup_key bit, is_primary_key bit, is_unique_constraint bit, fill_factor tinyint, is_padded bit, is_disabled bit, is_hypothetical bit, allow_row_locks bit, allow_page_locks bit, data_space_type nvarchar(2), data_space nvarchar(1024)) AS BEGIN WITH partitionfields ([object_id], [index_id], [name]) AS (SELECT ic.[object_id] ,ic.index_id, c.name FROM sys.index_columns ic JOIN sys.columns c ON ic.[object_id] = c.[object_id] AND ic.column_id = c.column_id WHERE ic.partition_ordinal = 1) INSERT INTO @result SELECT table_name = object_name(i.[object_id]) , i.name , i.is_unique , i.ignore_dup_key , i.is_primary_key , i.is_unique_constraint , i.fill_factor , i.is_padded , i.is_disabled , i.is_hypothetical , i.allow_row_locks , i.allow_page_locks , data_space_type = ds.type , data_space = coalesce(ds.name + N'(' + pf.name + N')', ds.name) FROM sys.indexes i INNER JOIN sys.data_spaces ds ON i.data_space_id = ds.data_space_id LEFT JOIN partitionfields pf ON i.[object_id] = pf.[object_id] AND i.index_id = pf.index_id WHERE i.name IS NOT NULL AND object_name(i.[object_id]) = @table_name -- Return the result of the function RETURN END
select * from dbo.fn_helpindex('Customer')
table_name | index_name | is_clustered | is_unique | ignore_dup_key | is_primary_key | is_unique_constraint | fill_factor | is_padded | is_disabled | is_hypothetical | allow_row_locks | allow_page_locks | data_space_type | data_space |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Customer | pk_entity | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | FG | PRIMARY |
CREATE PARTITION FUNCTION pfn_maturity_bucket (int) AS RANGE RIGHT FOR VALUES (3, 6, 12); CREATE PARTITION SCHEME psc_maturity_bucket AS PARTITION pfn_maturity_bucket ALL TO ([PRIMARY]) CREATE TABLE LoanDetails(code nvarchar(50), amount numeric(23,3), maturity_bucket int) CREATE INDEX ixLoanDetails ON LoanDetails(maturity_bucket) ON psc_maturity_bucket(maturity_bucket)
select * from dbo.fn_helpindex('LoanDetails')
table_name | index_name | is_clustered | is_unique | ignore_dup_key | is_primary_key | is_unique_constraint | fill_factor | is_padded | is_disabled | is_hypothetical | allow_row_locks | allow_page_locks | data_space_type | data_space |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
LoanDetails | ixLoanDetails | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | PS | psc_maturity_bucket(maturity_bucket) |
Posted by Filip De Vos at 8:43 PM 0 comments
Tags: SQL Server 2005, SQL Server 2008, TSQL
When you create a database in SQL Server using the "CREATE DATABASE" statement without any options, the database files will be created in some "default" locations. Many people think this default location is the same as the location of the master database, but this is incorrect, it is possible to alter the location in server properties after installation of the server. (in Management Studio right click on the server and select "Server Properties", then go to the "Database Settings" tab and near the bottom you will see a section with "Database Default locations") For example, suppose I execute the following statement:
CREATE DATABASE DefaultLocationDB
RESTORE DATABASE DefaultLocationDB FROM DISK = N'c:\backups\DemoDB.bak' WITH FILE = 1, MOVE N'demo_data_device' TO N'C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Data\DemoDb.ldf', MOVE N'demo_log_device' TO N'C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Data\DemoDb.ldf', NOUNLOAD, REPLACE
Edit: Rudi Larno posted a very nice refactored version of the code I posted. (especially note the nice recursion in it.)
View my old codeIF object_id('dbo.fn_get_default_data_path') is not null DROP FUNCTION dbo.fn_get_default_data_path GO CREATE FUNCTION dbo.fn_get_default_data_path() returns nvarchar(260) AS BEGIN DECLARE @instance_name nvarchar(200), @system_instance_name nvarchar(200), @registry_key nvarchar(512), @data_path nvarchar(260), @log_path nvarchar(260); SET @instance_name = COALESCE(convert(nvarchar(20), serverproperty('InstanceName')), 'MSSQLSERVER') -- sql 2005/2008 with instance EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', N'Software\Microsoft\Microsoft SQL Server\Instance Names\SQL', @instance_name, @system_instance_name output; SET @registry_key = N'Software\Microsoft\Microsoft SQL Server\' + @system_instance_name + '\MSSQLServer'; EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', @registry_key, N'DefaultData', @data_path output; IF @data_path is null -- sql 2005/2008 default instance BEGIN SET @registry_key = N'Software\Microsoft\Microsoft SQL Server\' + @system_instance_name + '\Setup'; EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', @registry_key, N'SQLDataRoot', @data_path output; SET @data_path = @data_path + '\Data'; END if @data_path is null -- sql 2000 with instance begin SET @registry_key = N'Software\Microsoft\Microsoft SQL Server\' + @instance_name + '\MSSQLServer'; EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', @registry_key, N'DefaultData', @data_path output; end if @data_path is null -- sql 2000 default instance begin SET @registry_key = N'Software\Microsoft\MSSQLServer\MSSQLServer'; EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', @registry_key, N'DefaultData', @data_path output; end return @data_path END GO
IF object_id('dbo.fn_get_default_log_path') is not null DROP FUNCTION dbo.fn_get_default_log_path GO CREATE FUNCTION dbo.fn_get_default_log_path() RETURNS nvarchar(260) AS BEGIN DECLARE @instance_name nvarchar(200), @system_instance_name nvarchar(200), @registry_key nvarchar(512), @log_path nvarchar(260); SET @instance_name = COALESCE(convert(nvarchar(20), serverproperty('InstanceName')), 'MSSQLSERVER'); -- sql 2005/2008 instance EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', N'Software\Microsoft\Microsoft SQL Server\Instance Names\SQL', @instance_name, @system_instance_name output; SET @registry_key = N'Software\Microsoft\Microsoft SQL Server\' + @system_instance_name + '\MSSQLServer'; EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', @registry_key, N'DefaultData', @data_path output; EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', @registry_key, N'DefaultLog', @log_path output; if @log_path is null -- sql 2000 with instance begin SET @registry_key = N'Software\Microsoft\Microsoft SQL Server\' + @instance_name + '\MSSQLServer'; EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', @registry_key, N'DefaultLog', @log_path output; end if @log_path is null -- sql 2000 default instance begin SET @registry_key = N'Software\Microsoft\MSSQLServer\MSSQLServer'; EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', @registry_key, N'DefaultLog', @log_path output; end IF @log_path is null BEGIN SELECT @log_path = dbo.fn_get_default_data_path() END return @log_path END GO
IF object_id('dbo.fn_get_default_path') is not null DROP FUNCTION dbo.fn_get_default_path GO CREATE FUNCTION dbo.fn_get_default_path(@log bit) RETURNS nvarchar(260) AS BEGIN DECLARE @instance_name nvarchar(200), @system_instance_name nvarchar(200), @registry_key nvarchar(512), @path nvarchar(260), @value_name nvarchar(20); SET @instance_name = COALESCE(convert(nvarchar(20), serverproperty('InstanceName')), 'MSSQLSERVER'); -- sql 2005/2008 with instance EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', N'Software\Microsoft\Microsoft SQL Server\Instance Names\SQL', @instance_name, @system_instance_name output; SET @registry_key = N'Software\Microsoft\Microsoft SQL Server\' + @system_instance_name + '\MSSQLServer'; SET @value_name = N'DefaultData' IF @log = 1 BEGIN SET @value_name = N'DefaultLog'; END EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', @registry_key, @value_name, @path output; IF @log = 0 AND @path is null -- sql 2005/2008 default instance BEGIN SET @registry_key = N'Software\Microsoft\Microsoft SQL Server\' + @system_instance_name + '\Setup'; EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', @registry_key, N'SQLDataRoot', @path output; SET @path = @path + '\Data'; END IF @path IS NULL -- sql 2000 with instance BEGIN SET @registry_key = N'Software\Microsoft\Microsoft SQL Server\' + @instance_name + '\MSSQLServer'; EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', @registry_key, @value_name, @path output; END IF @path IS NULL -- sql 2000 default instance BEGIN SET @registry_key = N'Software\Microsoft\MSSQLServer\MSSQLServer'; EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', @registry_key, @value_name, @path output; END IF @log = 1 AND @path is null -- fetch the default data path instead. BEGIN SELECT @path = dbo.fn_get_default_path(0) END RETURN @path; END GO
declare @sql nvarchar(2000), @data varchar(260), @log varchar(260); select @data = dbo.fn_get_default_data_path(), @log = dbo.fn_get_default_log_path() select @sql= 'RESTORE DATABASE DefaultLocationDB FROM DISK = N''c:\backups\DemoDB.bak'' WITH FILE = 1, MOVE N''demo_data_device'' TO N''' + @data + '\DemoDb.ldf'', MOVE N''demo_log_device'' TO N''' + @log + '\DemoDb.ldf'', NOUNLOAD, REPLACE' exec (@sql)
Posted by Filip De Vos at 5:04 PM 4 comments
Tags: SQL Server, SQL Server 2000, SQL Server 2005, SQL Server 2008, TSQL
This message has moved to http://foxtricks.posterous.com
Posted by Filip De Vos at 8:35 AM 3 comments
Tags: SQL Server, TSQL
A client came to me with another string formatting problem. I would never recommend formatting strings in T-SQL, but rather to do formatting on the client side. The question was to strip trailing zeros from a numeric value stored in a string but retain the decimals when they are not zero.
1.0501 | needs to show | 1.0501 |
1.0500 | needs to show | 1.05 |
1.0000 | needs to show | 1 |
CREATE FUNCTION dbo.fn_strip_zeros(@number numeric(38,10)) RETURNS varchar(38) WITH ENCRYPTION AS BEGIN DECLARE @result varchar(38) DECLARE @decimal varchar(3) SET @decimal = substring(convert(varchar(38), convert(numeric(38,10),1)/5 ), 2,1) set @result = rtrim(replace(replace(rtrim(replace(@number,'0',' ')),' ','0') + ' ', @decimal + ' ', '')) RETURN @result END GO
Posted by Filip De Vos at 1:28 PM 4 comments
Tags: SQL Server, TSQL
When developing an application that does a lot of inserts and updates, it is sometimes easier to wrap both statements in one stored procedure. This results in a simple procedure that will check if a certain record exists. When no record is found a new one is inserted, when a record is found the values are updated.
We will use the following table script as example to explain the problem:
create table updatetest ( id int primary key, name varchar(20), amount numeric ) insert into updatetest values (1, 'first', 100) insert into updatetest values (2, 'second', 500) insert into updatetest values (3, 'third', 30) insert into updatetest values (4, 'fourth', 200)
When making an insert/update procedure most people write something like this:
CREATE PROCEDURE updatevalues(@id int, @name varchar(20), @amount numeric) AS IF EXISTS(SELECT * FROM updatetest WHERE id = @id) BEGIN UPDATE updatetest SET name = @name, amount= @amount WHERE id = @id END ELSE BEGIN INSERT INTO updatetest VALUES (@id, @name, @amount) END
Let's alter our procedure script a bit to make it easier to reproduce the problem.
CREATE PROCEDURE updatevalues(@id int, @name varchar(20), @amount numeric) AS IF EXISTS(SELECT * FROM updatetest WHERE id = @id) BEGIN waitfor delay '00:00:10' UPDATE updatetest SET name = @name, amount= @amount WHERE id = @id END ELSE BEGIN waitfor delay '00:00:10' INSERT INTO updatetest VALUES (@id, @name, @amount) END
Compile this script and then run the following statements (at the same time) in 2 query windows.
exec updatevalues 7, 'Seven', 777
Surprise surprise, look at the error we get in the second query window...
Why does this happen? Simple, the first execution checks if the record exists and decides to insert a new record because the if exists returns false. The second thread is executing the same query at the same time and of course the existance check returns false as well. They both try to insert and a primary key error happens.
Some people think that this will never happen, but they are wrong, from the moment you are working with large amounts of data and users, this conflict will happen very often.
Luckily there is an easy way to solve this (very annoying) problem by using Lock hints in our advantage.
We will update our test procedure to include a transaction and an Exclusive Lock:
CREATE PROCEDURE updatevalues(@id int, @name varchar(20), @amount numeric) AS BEGIN TRANSACTION IF EXISTS(SELECT * FROM updatetest (XLOCK, SERIALIZABLE) WHERE id = @id) BEGIN waitfor delay '00:00:10' UPDATE updatetest SET name = @name, amount= @amount WHERE id = @id END ELSE BEGIN waitfor delay '00:00:10' INSERT INTO updatetest VALUES (@id, @name, @amount) END COMMIT TRANSACTION RETURN(0)
The updated code has as effect that the second thread will wait until the first thread has completed it's operation. It is no longer possible that another operation on the same record is "sneaking" in between the check and the insert or update.
Since I simplified the procedure script for the example you can download a complete version of the code.
Posted by Filip De Vos at 10:17 AM 10 comments
Tags: Performance, SQL Server, TSQL
I love Virtual PC 2007, but I hate how every VPC image I used so far is at least 5GB and easily grows to 10GB. It is hard to keep multiple images active on your machine. So I started looking for a way to reduce the size of an image.
About the only usefull resource I found was the site of http://www.bold-fortune.com. This website contains the log of a guy that deletes windows files one by one and tests his system for stability.
My goal was to end up with the smallest possible Windows XP installation with the SQL Server 2005 service installed (and running).
Here is how I got there:
When a in my view important service or component was deleted I restarted the vpc to see if everything was still in working order. (I check in event viewer for errors.)
Another small fix I did was running the following in a cmd.exe window:
When all this was done I switched on compression on the complete c-drive of the vpc (make sure sql server is running because it does not when the system databases are compressed). Then used the Whitney Defrag utility to defragment the drive. This defragmenter is extremely nice on virtual pc's because it has a tiny installation footprint and it moves all the clusters to the start of the disk.
When all is ready it is time to start compacting the Virtual PC. Start this process by capturing the ISO file "C:\Program Files\Microsoft Virtual PC\Virtual Machine Additions\Virtual Disk Precompactor.iso". This will automatically start zeroing out all the free space of the virtual hard disk.
Once the virtual hard disk is pre-compacted, shut down your Virtual Machine and compact the virtual hard disk by using the "Virtual Disk Wizard".
This (lenghty) process delivered me a Virtual PC of 826 MB and with compression on a footprint of 550 MB on disk which enables me to keep several virtual machines around. I do realise that a vpc with so little memory is slow and not usable for any serious testing, but it enables me to comfortably experiment with clustering and replication.
You can download the batch files i created to shrink my vpc images but be carefull. If you run these files on your normal pc, they WILL delete a LOT of files and certain software will NOT WORK anymore. So please be carefull. And I do not guarantee that these batch files will work perfectly for you. For me they do exactly what I need and leave me with a good usable VPC image for testing.
So If you want to try the same thing I did, you can download the batch files and start deleting windows.
Posted by Filip De Vos at 5:24 PM 0 comments
Tags: Installation, SQL Server 2005, Virtual PC