Thursday, November 15, 2018

Generate DROP/CREATE statements for all referencing foreign keys

This is a quick post to generate the DROP/CREATE statements for all foreign keys referencing a given primary key... like the subject says. 😉  In this example, we generate the statements for all foreign keys referencing PK_Address_AddressID on Person.Address in AdventureWorks2012, and this is the output:





See this post for a little more detail on when you might need to do this.

     CREATE TABLE #ForeignKeys (
       SchemaName VARCHAR(128),
       TableName VARCHAR(128),
       ForeignKeyName VARCHAR(128),
       DropScript VARCHAR(MAX),
       CreateScript VARCHAR(MAX)
     );

     DECLARE @PKname NVARCHAR(128) = 'PK_Address_AddressID' --<-- change this to your PK name
     DECLARE @create NVARCHAR(MAX) = '';

     -- populate the drop statement
     INSERT #ForeignKeys (SchemaName,TableName,ForeignKeyName,DropScript,CreateScript)
     SELECT schemas.[name],
       tables.[name],
       foreign_keys.[name],
       'ALTER TABLE ' + QUOTENAME(schemas.[name]) + '.' + QUOTENAME(tables.[name]) + ' DROP CONSTRAINT    
             ' + QUOTENAME(foreign_keys.[name]),
       NULL
     FROM
sys.foreign_keys INNER JOIN sys.tables
         ON foreign_keys.parent_object_id = tables.object_id INNER JOIN sys.schemas
           ON tables.schema_id = schemas.schema_id INNER JOIN sys.indexes
                ON sys.indexes.object_id = sys.foreign_keys.referenced_object_id
                AND sys.indexes.index_id = sys.foreign_keys.key_index_id
     WHERE
       indexes.name = @PKname;

     -- populate the create statement
     UPDATE #ForeignKeys
     SET CreateScript = Creation.Script
     FROM #ForeignKeys INNER JOIN
     (
     SELECT
        cs.[name] SchemaName,
        ct.[name] TableName,
        foreign_keys.[name] ForeignKeyName,
        'ALTER TABLE ' + QUOTENAME(cs.[name]) + '.' + QUOTENAME(ct.[name]) + ' ADD CONSTRAINT ' + QUOTENAME(foreign_keys.[name]) + ' FOREIGN KEY (' +
        STUFF((
              SELECT ',' + QUOTENAME(columns.[name])
              FROM sys.columns INNER JOIN sys.foreign_key_columns
                ON foreign_key_columns.parent_column_id = columns.column_id
                AND foreign_key_columns.parent_object_id = columns.object_id
              WHERE
                     foreign_key_columns.constraint_object_id = foreign_keys.object_id
              ORDER BY
                     foreign_key_columns.constraint_column_id
       FOR XML PATH(''), TYPE
                       ).value('.1', 'VARCHAR(MAX)'), 1, 1,'') + ') REFERENCES ' + QUOTENAME(schemas.[name]) + '.' + QUOTENAME(tables.[name]) + '(' +
     STUFF((
     SELECT ',' + QUOTENAME(columns.[name])
     FROM sys.columns INNER JOIN sys.foreign_key_columns fkc
       ON fkc.referenced_column_id = columns.column_id
       AND fkc.referenced_object_id = columns.object_id
     WHERE
         fkc.constraint_object_id = foreign_keys.object_id
     ORDER BY
         fkc.constraint_column_id
      FOR XML PATH(''), TYPE
       ).value('.1', 'NVARCHAR(MAX)'), 1, 1, '') + ')' Script
     FROM sys.foreign_keys INNER JOIN sys.tables
       ON foreign_keys.referenced_object_id = tables.object_id INNER JOIN sys.schemas
         ON tables.schema_id = schemas.schema_id INNER JOIN sys.tables ct
           ON foreign_keys.parent_object_id = ct.object_id INNER JOIN sys.schemas cs
             ON ct.schema_id = cs.schema_id
     WHERE
         tables.is_ms_shipped = 0
         AND ct.is_ms_shipped = 0
     ) Creation
     ON Creation.SchemaName = #ForeignKeys.SchemaName
     AND Creation.TableName = #ForeignKeys.TableName
     AND Creation.ForeignKeyName = #ForeignKeys.ForeignKeyName

     -- output your datta
     SELECT
       SchemaName,
       TableName,
       ForeignKeyName,
       DropScript,
       CreateScript
     FROM
       #ForeignKeys

     -- drop your tmp table
     DROP TABLE #ForeignKeys




Tuesday, November 13, 2018

What is SQL Server's default trace? Is it enabled?

If it's not, it really should be.  This trace is enabled by default w/your SQL Server installation;  it is very light weight, and collects details about SQL Server activity that are useful when assessing server events, or troubleshooting problems.  It is very useful for many things, but in this post I will just provide basic queries to see if it's enabled/running, what events it captures, and a couple examples of reading the default trace output file.

  -- is the default trace configured / enabled  
  SELECT* FROM sys.configurations WHERE configuration_id = 1568

No need for any screenshots.  The above just tells us if the default trace is enabled, and this one will tell us how it is configured:    SELECT * FROM sys.traces


My screenshot is cut off, but these are the important points ---  
status 0 = stopped  /  1 = running
path where the file is at
max_size maximum trace file size in MB
max_files max number of rollover files
when file reaches 20MB, another is created, up to 5 files
then they are cycled through / rolled over / overwritten in sequence

That max_files setting is important.  Say you had a problem a few minutes ago and you need to see what the trace captured.  Well, if your system is busy, those trace files can be overwritten pretty quickly... so you shouldn't waste any time getting in there.  

Ok.  How do you read the file?  This is a simple SELECT of all data from the trace file (be sure to upate your file path/name): 

  SELECT *
  FROM fn_trace_gettable('C:\Program Files\Microsoft SQL Server\...log_175.trc',DEFAULT);


A more useful query might be to go in specifically, and see what was happening when the problem occured.  This query returns an aggregate of all events by database, application and login:

   -- see how much is going on by db/app/login
   SELECT
      te.name [EventName],
      t.DatabaseName,
      t.ApplicationName,
      t.LoginName,        
      COUNT(*) [Count]
   FROM   
  dbo.fn_trace_gettable('C:\Program Files\Microsoft SQL Server\...log_175.trc',0) t
        JOIN sys.trace_events te ON t.EventClass = te.trace_event_id
   GROUP BY
      te.name,
      t.DatabaseName,
      t.ApplicationName,
      t.LoginName;

Sample output... from my very quiet instance:


Note those missing column statistics!!!  That shows you that the default trace can also be used to observe performance conditions such as missing column stats, hash warnings, etc.

On that note, you can use this query to return the events are captured by the trace, so you know what you can actually collect from the file:

       -- what events are captured in the default trace
       DECLARE @id INT
       SELECT @id=id
       FROM sys.traces
       WHERE is_default = 1

       SELECT DISTINCT EventID, [name] [Event]
       FROM fn_trace_geteventinfo(@id) evi JOIN sys.trace_events ev
         ON evi.eventid = ev.trace_event_id 

Here is a snapshot of the results: 


As I said, there are many different uses of the defaut trace data capture, but this should get your started.  I will post more later.

More details:  sys.traces and sys.fn_trace_gettable


Oh yes!  One more thing.  If the SQL Server default trace is not enabled, this is how you enable it.  The change is immediate.  No service restart is necessary

       EXEC dbo.sp_configure 'show advanced options', 1;
       RECONFIGURE WITH OVERRIDE;
       EXEC dbo.sp_configure 'default trace enabled', 1;
       RECONFIGURE WITH OVERRIDE;


Happy tracing.  😏

Monday, November 12, 2018

Where is your SQL Server Error Log ?

Yes, yes, I know this sounds like a dumb question -- but it's not.  You inherit a server built by somebody else.  There's a problem, and you need to get into that error log fast.  Well... where is it?  This post is just a couple quick methods for finding your current SQL Server Error Log.

First, you have sp_readerrorlog which enables you to read the contents of the SQL Server Error Log w/a query.  In this case, I am specifically looking for the log entry beginning with 'Logging SQL Server messages in file'... but, you can actually use it to search for anything written to the log file.  

 EXEC sys.sp_readerrorlog @p1 =0,@p2 =1,@p3 ='Logging SQL Server messages in file'

Your output will be similar to this:


Or, maybe the problem is BIG and you cannot even get into SSMS to run a query.  You can also find the log location from within the SQL Server Configuration Manager, SQL Server (MSSQLSERVER) Properties, Startup Parameters -- that one prefaced with the -e is your error log location.



And there you have it!  Another post soon w/some other clever uses for sp_readerrorlog. 

Monday, November 5, 2018

Enable SSIS package logging with dated output filename

Had to outline this for an associate today, so I thought I'd share it here as well.  Just a quick method for adding logging to your SSIS packages, and including the date in the output filename.

Within VS, open your package and right click the Control Flow back panel, choose Logging:


This brings up 'Configure SSIS Logs: PackageName' dialog box, where ‘PackageName’ is the name of the package you are managing.  Check the box next to your package name in Containers.  Note at the bottom of the dialog it tells us we need to enable the logging in the tree view in order to configure logging. 


Now hit the ‘Details’ tab to choose your log details.  


My suggestions for detail selection:

     ALWAYS:                                OFTEN HELPFUL:
OnError                                 OnInformation
OnTaskFailed                         Diagnostic
OnWarning

After choosing the log details, go back to Providers and Logs tab and choose ‘SSIS log provider for Text files’ provider type.  Check the Name box, choose ‘Configuration’ and then <New connection…>.  


This opens a File Connection Manager Editor, where you choose 'Create file' and browse to your output folder directory, using a descriptive name for your package.  I always use packagename.txt.


Hit ‘Ok’ here, and again at the bottom of the 'Configure SSIS Logs: packagename' dialog box. Now you will see a packagename.txt entry down below in your Connection Managers.  That is your new log file connection manager.

Now, a ‘packagename.txt’ log file is created with every execution of your SSIS package ...which will be overwritten every time. To avoid that, we need to use a variable and an expression to dynamically include the date in the logfile name.  

For the variable, right click the Control Flow back panel and choose 'Variables'.  Scope will be filled in with your package name, but you must enter Name, Type and value, which should correspond to the path you want the log file written to, like this:


For the expression, right click your new log file connection manager and choose properties.  



Choose 'Expressions' in the returned properties and hit the box to open the 'Property Expressions Editor' dialog box.  Under Property, choose 'Connection String' and hit the box to open the 'Expression Builder' dialog box. Here you will paste either statement below into the Expression box and choose 'Evaluate Expression' to see the final format on your log filename.  If it's good, choose Ok and then you must Save your package.



packagename_log_YYYYMMDD.txt -
@[User::VarLogFolderPath]+ @[System::PackageName]+"_log_"+ (DT_WSTR,4)DATEPART("yyyy",GetDate()) +
RIGHT("0" + (DT_WSTR,2)DATEPART("mm",GetDate()) ,2) +
RIGHT("0" + (DT_WSTR,2)DATEPART("dd",GetDate()),2)+ ".txt"

packagename_log_YYYYMMDD_hhmm.txt -
@[User::VarLogFolderPath]+ @[System::PackageName]+"_log_"+ (DT_WSTR,4)DATEPART("yyyy",GetDate()) +
RIGHT("0" + (DT_WSTR,2)DATEPART("mm",GetDate()) ,2) +
RIGHT("0" + (DT_WSTR,2)DATEPART("dd",GetDate()),2) + "_" +
RIGHT("0" + (DT_WSTR,2)DATEPART("hh",GetDate()),2)+
RIGHT("0" + (DT_WSTR,2)DATEPART("mi",GetDate()),2) + ".txt"


Of course, there are many other formats for outputting the datestamp into the log filenames, but those are the two I use most often.  

Hope to have helped!




Sunday, November 4, 2018

How to schedule Agent job to run every 2 weeks

SQL Server Agent job scheduling is generally pretty easy, but I still have received this question many times.  I've even Googled it myself this morning, and am very surprised about all the inquiries on how to do this, and all of the different suggested solutions.  No, you don't need Powershell.  No, you don't have to create two jobs with two different schedules.  You don't even need two different schedules for one job...  It really is quite simple.  

For example, say we need to run a job every two weeks on Saturday.  You will create a new schedule, or edit your existing one to be like the one I've shown here.  The trick is with the Frequency.  Choose 'Weekly' and set it to recur every 2 weeks.  And, of course, be sure to check the Saturday box.  

That's it!  Now your Agent job will run every two weeks on Saturday, at 7AM.