Wednesday, January 25, 2012

[Code] Using EXECUTE AS with a trigger to capture changes when a user doesn't have permissions.


Had to build this to keep track of changes on certain tables in our environment.  Due to limited permissions, we wound up with people hitting the table but the trigger not working right, because they hadn't been granted permissions.  So the obvious solution is to grant permissions to both tables to a group, then add people into that group.  Not an option for this particular scenario.  Hence, my code.

Note that THIS WILL NOT WORK across databases, unless it’s marked as TRUSTWORTHY.  Planned functionality, not a bug. Within a database it works. 

New code bolded.  I created a user Change_User, a change-tracking table named change_mytable and gave Change_User select/insert permissions ONLY into change_mytable.

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO


alter trigger [dbo].[mytrigger]
on [dbo].[mytable] with execute as 'change_user'
after update
as
BEGIN
  SET NOCOUNT ON

  declare @current_user nvarchar(128)  --suser_sname() is nvarchar(128)
  execute as caller                    --caller is the original person running code
  set @current_user = SUSER_SNAME()    --get the name of the user
  --select @current_user               --returns my name when I run it
  revert                               --go back to the EXECUTE AS user, ch_user
  --Audit capture
      if exists (select name from sysobjects where name = 'change_mytable')
      begin
      --print suser_sname()                     --returns change_user
      insert into change_mytable(
      ID, nameupdated_date, change_login, dml_action)
      SELECT
            ID, NAME, getdate(), @current_user, 'Update'
            FROM Deleted
      end

END


GO

Friday, January 20, 2012

Adding locations to SSMS "Open File"

Been looking like this for years.  Who knew, the right google and there it is.  I take no credit for this, simply sticking it in my blog so that I can find it next time.

http://www.sqlservercentral.com/Forums/Topic907188-391-1.aspx
Todd Engen


Here's where you'd find that for SSMS with 2005.

\HKCU\Software\Microsoft\Microsoft SQL Server\90\Tools\Shell\Open Find\Places\UserDefinedPlaces

Add subkeys Place0,Place1,Place(n)

Under each subkey add two REG_SZ values

Name = "Shortcut Name"
Path = "Folder location"

Tuesday, January 17, 2012

[Network] Specified network name is no longer available

We've seen this in our environment for a while.  Tried moving backup times around, that helped some but not in every case.  TCP Chimney Offload didn't seem to be our issue either, though it has been for others.

Got this nugget courtesy of kgerde, which seems to be a near-magic-bullet for us.
http://www.sqlservercentral.com/Forums/Topic768429-391-1.aspx


HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\lanmanworkstation\parameters
Create a New DWORD value with the name: SessTimeout
set the value: 360 keep it Hexadecimal
(This value might not work for your backup but it was high enough for mine. If this doesn't work increase the value and try again.)

Monday, November 14, 2011

Use RAISERROR to return results in SSMS immediately

This courtesy of a coworker (JS), who found the idea online somewhere, possibly from Savjani here (http://blogs.msdn.com/b/sqlserverfaq/archive/2009/10/01/behavior-of-with-nowait-option-with-raiserror-in-sql-server.aspx)

If you run a while loop from SSMS, the results won't come back immediately, even if you're using a PRINT.  This is a clever way to return it immediately.


DECLARE @startdate DATETIME,
            @now DATETIME,
            @msg NVARCHAR(50)

SET @now = GETDATE()         
SET @startdate = GETDATE()- 60

WHILE (@startdate <= @now)
BEGIN
SET @msg = (select CONVERT(VARCHAR(10), @startdate, 101))
RAISERROR (@msg, 0, 1) WITH NOWAIT
SET @startdate = @startdate + 1
END

Tuesday, September 27, 2011

Using OUTPUT to set up foreign keys' data


So, I was looking at a process that inserts records into table A (one at a time), then uses SCOPE_IDENTITY() from each insert to get the ID and uses that for the insert into table B, in order to provide a cross-reference tabled (foreign key).  

Obviously, there's a better way - OUTPUT, usable since SQL Server 2005.

An example.  We populate “blah”, then use the values from that to insert into “blah2”, with a foreign key ID of the first table.  

CREATE TABLE blah (id INT IDENTITY, logon_name VARCHAR(50))
CREATE TABLE blah2 (id INT IDENTITY, blahID int, dn VARCHAR(200))

INSERT INTO blah
      SELECT TOP 10 name FROM sysusers
INSERT INTO blah
      SELECT TOP 10 name FROM sysusers
DELETE FROM blah
--incrementing to show different ID values.

DECLARE @insertedlist TABLE (id int, the_logonname VARCHAR(50))

INSERT INTO blah
output inserted.id, inserted.logon_name INTO @insertedlist
SELECT TOP 10 name
FROM sysusers ORDER BY createdate

INSERT INTO blah2 (blahid, dn)
SELECT insertedlist.id, sysusers.uid
FROM @insertedlist insertedlist INNER JOIN sysusers
ON insertedlist.the_logonname = sysusers.name

SELECT * FROM blah
SELECT * FROM blah2

Thursday, September 8, 2011

[Free Space] finding what filegroups your data is saved onto.

Using this to tell me where my indexes are, but more importantly - where my data is. What filegroup/file. It produces multiple rows when there are multiple files for a filegroup; need to code a better way. Note that all the WHERE clauses are optional; we couldn't figure out where our data was until we dropped them. 10 gig in a heap table, and 20gb in a service broker table.
SELECT
o.name AS Table_Name ,
i.NAME AS Index_Name ,
CASE i.type
WHEN 0 THEN 'Heap'
WHEN 1 THEN 'C'
WHEN 2 THEN 'NC'
ELSE '?' END AS [Type],
p.rows AS [#Records] ,
a.total_pages * 8 / 1024 AS [Reserved(mb)] ,
a.used_pages * 8 / 1024 AS [Used(mb)] ,
s.user_seeks ,
s.user_scans ,
s.user_lookups,
fg.name,
f.name,
f.physical_name
FROM sys.indexes AS i
INNER JOIN sys.partitions AS p ON i.object_id = p.object_id
AND i.index_id = p.index_id
INNER JOIN SYS.OBJECTS O ON I.OBJECT_ID = O.OBJECT_ID
INNER JOIN sys.allocation_units AS a ON ( a.type = 2
AND p.partition_id = a.container_id
)
OR ( ( a.type = 1
OR a.type = 3
)
AND p.hobt_id = a.container_id
)
INNER JOIN SYS.DM_DB_INDEX_USAGE_STATS S ON S.OBJECT_ID = I.OBJECT_ID
AND I.INDEX_ID = S.INDEX_ID
AND DATABASE_ID = DB_ID(DB_NAME())
AND o.type_desc NOT IN ( 'SYSTEM_TABLE', 'INTERNAL_TABLE' ) -- No system tables!
LEFT OUTER JOIN sys.database_files f ON f.data_space_id = a.data_space_id
LEFT OUTER JOIN sys.filegroups fg ON fg.data_space_id = a.data_space_id
--AND (ISNULL(s.user_seeks, 0) + ISNULL(s.user_scans, 0) + ISNULL(s.user_lookups, 0)) < 100
WHERE OBJECTPROPERTY(O.OBJECT_ID, 'IsUserTable') = 1
--AND i.TYPE_DESC <> 'HEAP'
AND i.type <> 1 -- clustered index
ORDER BY o.NAME ,
i.name

Thursday, September 1, 2011

[Index] Size, usage, and location of your indexes

Updated my old code, since we were trying to figure out what indexes needed to get moved to the secondary (index) filegroup. You can even filter based off the usage (commented out below)
Standard disclaimer applies. Select the contents of the post, then copy/paste.

Note that there can be dupes - this is by design.  If a table spans multiple files, you'll see multiple rows returned with everything identical except for the filename.

SELECT
        o.name AS Table_Name ,
        i.NAME AS Index_Name ,
--        i.type_desc,
  CASE i.type
   WHEN 0 THEN 'Heap'
   WHEN 1 THEN 'C'
   WHEN 2 THEN 'NC'
   ELSE '?' END AS [Type],
   p.partition_number,
   p.rows AS [#Records] ,
        a.total_pages * 8 / 1024 AS [Reserved(mb)] ,
        a.used_pages * 8 / 1024 AS [Used(mb)] ,
        s.user_seeks ,
        s.user_scans ,
        s.user_lookups,
        fg.name, 
        f.name,
        f.physical_name
FROM    sys.indexes AS i
        INNER JOIN sys.partitions AS p 
   ON i.object_id = p.object_id
            AND i.index_id = p.index_id
        INNER JOIN SYS.OBJECTS O 
   ON I.OBJECT_ID = O.OBJECT_ID
        INNER JOIN sys.allocation_units AS a 
   ON ( a.type = 2
    AND p.partition_id = a.container_id
               )
   OR 
    ( ( a.type = 1 OR a.type = 3)
    AND p.hobt_id = a.container_id)
        INNER JOIN SYS.DM_DB_INDEX_USAGE_STATS S 
   ON S.OBJECT_ID = I.OBJECT_ID
   AND I.INDEX_ID = S.INDEX_ID
   AND DATABASE_ID = DB_ID(DB_NAME())
   AND o.type_desc NOT IN ( 'SYSTEM_TABLE', 'INTERNAL_TABLE' ) -- No system tables!
  LEFT OUTER JOIN sys.database_files f 
   ON f.data_space_id = a.data_space_id
  LEFT OUTER JOIN sys.filegroups fg 
   ON fg.data_space_id = a.data_space_id
WHERE   OBJECTPROPERTY(O.OBJECT_ID, 'IsUserTable') = 1
        AND i.TYPE_DESC <> 'HEAP'
        AND i.type <> 1 -- clustered index
--AND (ISNULL(s.user_seeks, 0) + ISNULL(s.user_scans, 0) + ISNULL(s.user_lookups, 0)) < 100 
ORDER BY o.NAME ,
        i.name

Tuesday, August 16, 2011

[ETL] Importing UNIX files

Coworker had a problem importing a data file - one column per line, but was running into problems and had to invoke Code Page 65001 in his SSIS script. Took forever to fix, and forever to process. Looked at the file - UNIX.

So, the easy T-SQL way to do it:


CREATE TABLE deleteme (resultant VARCHAR(MAX))

BULK INSERT deleteme
FROM '\\server\path\file.rpt'
WITH
(
ROWTERMINATOR = '0x0a'
)

Monday, August 15, 2011

[Tuning] Disk usage over time, checking deltas

Not sure where this came from, if I wrote it or someone else did. Practically, it looks before and after to tell you which files are getting used. The commented out bit was because I was investigating WRITELOG delays.



--as always, drag-copy to get the full text.
DECLARE @compare TABLE (NAME sysname, type_desc varchar(10), physical_name VARCHAR(200), sample_ms BIGINT,
num_of_bytes_written BIGINT, num_of_bytes_read BIGINT, size_in_gb INT)
DECLARE @compare2 TABLE (NAME sysname, type_desc varchar(10), physical_name VARCHAR(200), sample_ms BIGINT,
num_of_bytes_written BIGINT, num_of_bytes_read BIGINT, size_in_gb INT)

INSERT INTO @compare
SELECT
master_files.NAME,
master_files.type_desc,
master_files.physical_name,
vfs.sample_ms,
vfs.num_of_bytes_written,
num_of_bytes_read,
size_on_disk_bytes/1024/1024/1024 AS Size_in_GB
FROM sys.dm_io_virtual_file_stats(null, null) vfs
INNER JOIN MASTER.sys.master_files
ON vfs.[file_id] = master_files.[file_id]
AND vfs.database_id = master_files.database_id
--WHERE type_desc = 'log'
ORDER BY (num_of_bytes_read + num_of_bytes_written) DESC

WAITFOR DELAY '00:00:15'

INSERT INTO @compare2
SELECT
master_files.NAME,
master_files.type_desc,
master_files.physical_name,
vfs.sample_ms,
vfs.num_of_bytes_written,
num_of_bytes_read,
size_on_disk_bytes/1024/1024/1024 AS Size_in_GB
FROM sys.dm_io_virtual_file_stats(null, null) vfs
INNER JOIN MASTER.sys.master_files
ON vfs.[file_id] = master_files.[file_id]
AND vfs.database_id = master_files.database_id
-- WHERE type_desc = 'log'
ORDER BY (num_of_bytes_read + num_of_bytes_written) DESC


SELECT old.NAME, old.physical_name, new.sample_ms - old.sample_ms AS time_elapsed,
new.num_of_bytes_written - old.num_of_bytes_written AS delta_num_bytes_written,
new.num_of_bytes_read - old.num_of_bytes_read AS delta_num_bytes_read, old.size_in_gb
FROM @compare old INNER JOIN @compare2 new ON old.physical_name = new.physical_name
ORDER BY (new.num_of_bytes_written - old.num_of_bytes_written) DESC


Friday, August 5, 2011

[Trick] get midnight fast

I've used this in the past, but technically it's sloppy.

select convert(char(8),getdate(),112)

So these days I'm using this:

select DATEADD(dd, DATEDIFF(dd,0,getdate()), 0)