Archive for February, 2010

How To Get a Job as a DBA

Sunday, February 28th, 2010

With my upcoming book due to be released this month, I have been getting more and more emails sent my way regarding a common question “how do I get a job as a DBA?” I started to build a new blog post and I was surprised to find that I have already written quite a bit about the topic. So, I decided to build a page that contains all of the stuff I have written previously that pertains to landing a job as a DBA.

How To Get a Job as a DBA

There you will find a handful of resources that I consider to be quite vital in your job search. I reference other blogs, job websites, as well as some helpful books. In time I hope to build it out a bit more, but for now it should suffice for most anyone that is looking to find out more about a job in database administration.

Querying Oracle from Powershell Part 1

Sunday, February 28th, 2010

In this two part blog post we will demonstrate how to query an Oracle database from Powershell. Before we can run queries against Oracle we need to install the Oracle client on our Windows machine. Unlike SQL Server, the drivers for connecting to Oracle are not included with the operating systems. The drivers are however freely available from Oracle. You can find the client software on the Oracle Database Software Downloads Page.

Downloading the Oracle Client

You’ll notice several versions of Oracle software on the download page. The software you choose will varying depending on your operating system. Generally when with dealing Oracle client software it is safe to choose the latest client version even if the Oracle database you will be connecting to is a lower version.

At the time of this blog post the following versions were the latest available:

  • 11.1.0.7.0 Windows 2008 and Windows 2008 R2
  • 11.1.0.6.0 Windows 2003

However, check the download page and choose a later version if listed. I’ve installed both the Windows 2008 and 2003 x64 versions, but for this blog series I’m using the Windows 2003 x64 version. To complete the download

  • Select See All
  • Select Oracle Database 11g Release 1 Client (11.1.0.6.0) for Microsoft Windows (x64). Note: Be sure you select the Client download and not the full Oracle database software!

Note: When you attempt to download Oracle software you will be prompted to login to the Oracle Technology Network (OTN). If you don’t have an account you’ll need to create one—It’s free.

We’re now ready to install and configure the Oracle client software.

Installing the Oracle Client

Many of the components included with the Oracle client are not needed. The following steps are used to perform a minimal Oracle client installation.

Run setup.exe

oracleClient1

Click next on the Install Welcome Screen.

oracleClient2

Select Custom installation type and click next.

oracleClient3

The Oracle base directory should be off of a root drive of your choosing. I’m using C:\Oracle. Change the path and ensure the name field is auto populated correctly and then click next.

oracleClient4

Ensure all the requirement checks succeed and click next (Note: you may receive warnings on Windows 2008 R2 when using the Windows 2008 installation software. The install will still succeed even with these warnings).

oracleClient5

Select SQL Plus and scroll down to select more components.

oracleClient6

Select Oracle Windows Interfaces and ensure the first three components are NOT selected. Ensure all other Windows Interface ARE checked and scroll down to select additional components.

oracleClient7

Select the Oracle Net component and click next.

oracleClient8

Select Install.

oracleClient9

Once the installation is complete the configuration utility will be launched by the installer.

Configuring the Oracle Client

Select next from the Oracle Net Configuration Assistant Welcome screen.

oracleClient10

Select Next.

oracleClient11

Enter the Oracle database service name. Note: I’m using Oracle Express on Ubuntu Linux. The service name is XE, your service name may differ.

oracleClient12

Select Next.

oracleClient13

Enter the Oracle database server host name or IP address.

oracleClient14

Select Next to test connectivity.

oracleClient15

The test will fail, you’ll need to change the login and password by selecting Change Login

oracleClient16

The test should succeed and if not use the error message to troubleshoot.

oracleClient17

Enter an alias name and select next.

oracleClient18

Select Next.

oracleClient19

Select Next.

oracleClient20

Select Next.

oracleClient21

Select Finish.

oracleClient22

Select Exit.

oracleClient23

Select Yes.

oracleClient24

Congratulations you’ve installed the Oracle client! My thanks to an Oracle colleague who wishes to remain anonymous. He was a big help with the installation and putting together this guide. In part two of this blog series we’ll look at querying an Oracle database from Powershell.

Recursively Delete SSIS Folder

Sunday, February 28th, 2010

A while ago I posted a query to create a list of all the Integration Services packages deployed to the MSDB.  I am now using that query to take it a step further.

If you’ve been using SSIS for a while you’ve probably noticed that the Management Studio doesn’t like to delete Integration Services folders that are not empty.  In fact, it will politely ask you if you’re sure that you want to delete the folder on which you’ve just selected the “Delete” option through the right-click menu.

Right-click pop-up menu on SSIS folder

I am sure I want to delete this non-empty SSIS folder

So you click the Yes button.  But then it shows you the following message:

SSIS folder ‘FolderWithSubfolders’ contains packages and/or other folders. You must drop these first. (Microsoft SQL Server Native Client 10.0)

Graphically it looks like this:

Object Explorer pop-up: you can't delete SSIS folders that contain packages or other folders

And this message can be really annoying if you’ve got a main folder with, let’s say, five subfolders, and each subfolder contains about 20-30 packages.  If you want to delete this folder you first need to delete each package separately and then delete the five subfolders, and then you can finally delete the main folder.  And all that through the right-click pop-up menu because you can’t just select the object in the Object Explorer and hit the Delete button on the keyboard – it doesn’t have an action on SSIS objects…

So, I wasn’t planning on doing such a job manually and came up with the following stored procedure.

It’s probably a bit long but don’t run away just yet, I will explain what’s going on down below the code, and there are some comments in the code as well.

/*
DESCRIPTION: Deletes all folders and packages under, and including, specified folder.
WRITTEN BY:  Valentino Vranken
CREATED:     2010-02-28
VERSION:     1.0
USAGE:
  -- mind the forward slash
  EXEC dbo.SSIS_RecursiveDeleteFolder '/FolderWithSubfolders'
  -- to delete a subfolder
  EXEC dbo.SSIS_RecursiveDeleteFolder '/FolderWithSubfolders/ASubfolderWithPackages'

COPIED FROM: http://blog.hoegaerden.be

Note 1: folder names are not case-sensitive
Note 2: uses system tables and (undocumented) stored procedures located in MSDB.
Note 3: this code was written for SQL Server 2008. For 2005:
  o sysssispackagefolders -> sysdtspackagefolders90
  o sysssispackages -> sysdtspackages90
  o sp_ssis_deletefolder -> sp_dts_deletefolder
  o sp_ssis_deletepackage -> sp_dts_deletepackage
*/
CREATE PROCEDURE dbo.SSIS_RecursiveDeleteFolder
    @Folder varchar(2000)
AS
BEGIN
    set nocount on;

    declare @foldersToDelete table
    (
        folderid uniqueidentifier,
        Lvl int
    );

    declare @packagesToDelete table
    (
        PackageName sysname,
        folderid uniqueidentifier,
        Lvl int
    );

    --retrieve list of folders to be deleted
    with ChildFolders
    as
    (
        select PARENT.parentfolderid, PARENT.folderid, PARENT.foldername,
            cast('' as sysname) as RootFolder,
            cast(PARENT.foldername as varchar(max)) as FullPath,
            0 as Lvl
        from msdb.dbo.sysssispackagefolders PARENT
        where PARENT.parentfolderid is null
        UNION ALL
        select CHILD.parentfolderid, CHILD.folderid, CHILD.foldername,
            case ChildFolders.Lvl
                when 0 then CHILD.foldername
                else ChildFolders.RootFolder
            end as RootFolder,
            cast(ChildFolders.FullPath + '/' + CHILD.foldername as varchar(max))
                as FullPath,
            ChildFolders.Lvl + 1 as Lvl
        from msdb.dbo.sysssispackagefolders CHILD
            inner join ChildFolders on ChildFolders.folderid = CHILD.parentfolderid
    )
    insert into @foldersToDelete
    select F.folderid, F.Lvl
    from ChildFolders F
    where F.FullPath like @Folder + '%';

    --retrieve list of packages to be deleted
    with ChildFolders
    as
    (
        select PARENT.parentfolderid, PARENT.folderid, PARENT.foldername,
            cast('' as sysname) as RootFolder,
            cast(PARENT.foldername as varchar(max)) as FullPath,
            0 as Lvl
        from msdb.dbo.sysssispackagefolders PARENT
        where PARENT.parentfolderid is null
        UNION ALL
        select CHILD.parentfolderid, CHILD.folderid, CHILD.foldername,
            case ChildFolders.Lvl
                when 0 then CHILD.foldername
                else ChildFolders.RootFolder
            end as RootFolder,
            cast(ChildFolders.FullPath + '/' + CHILD.foldername as varchar(max))
                as FullPath,
            ChildFolders.Lvl + 1 as Lvl
        from msdb.dbo.sysssispackagefolders CHILD
            inner join ChildFolders on ChildFolders.folderid = CHILD.parentfolderid
    )
    insert into @packagesToDelete
    select P.name, F.folderid, F.Lvl
    from ChildFolders F
        inner join msdb.dbo.sysssispackages P on P.folderid = F.folderid
    where F.FullPath like @Folder + '%';

    --use cursor to loop over objects to be deleted
    declare objectsToDelete_cursor cursor
    for
        select P.folderid, P.Lvl, P.PackageName, 'P' as ObjectType
        from @packagesToDelete P
        UNION ALL
        select F.folderid, F.Lvl, null, 'F'
        from @foldersToDelete F
        order by Lvl desc, ObjectType desc;

    open objectsToDelete_cursor;

    declare @folderid uniqueidentifier;
    declare @lvl int;
    declare @packageName sysname;
    declare @objectType char;

    fetch next from objectsToDelete_cursor
    into @folderid, @lvl, @packageName, @objectType;

    while @@FETCH_STATUS = 0
    begin
        if @objectType = 'F'
        begin
            print 'exec msdb.dbo.sp_ssis_deletefolder '
                + cast(@folderid as varchar(max));
            exec msdb.dbo.sp_ssis_deletefolder @folderid;
        end
        else
        begin
            print 'exec msdb.dbo.sp_ssis_deletepackage '
                + @packageName + ', ' + cast(@folderid as varchar(max));
            exec msdb.dbo.sp_ssis_deletepackage @packageName, @folderid;
        end

        fetch next from objectsToDelete_cursor
        into @folderid, @lvl, @packageName, @objectType;
    end;

    close objectsToDelete_cursor;
    deallocate objectsToDelete_cursor;
END

Before trying to dismantle this stored procedure, I recommend you to read my previous article on retrieving the list of packages.  That already explains half of the code, if not 75%.

Our mission is to find a way to recursively delete packages and folders contained in a specified folder.  To be able to loop over those objects in the correct order (from the deepest level up until the level of the folder specified), the SP creates two table variables: one to hold all folders under the specified folder (@foldersToDelete) and one to hold the packages under the specified folder, including all subfolders (@packagesToDelete).

Based on those two lists I create a cursor that joins these two together, taking their level and object type into consideration.  That’s important because we first need to delete the packages in the lowest level folder, followed by their containing folder, then move one level up and do the same.

We then use the cursor to loop over the packages and folders and use two undocumented system stored procedures – one for each object type- to delete the package or folder.  These system SPs are located in the MSDB.  Here’s how they are defined:

ALTER PROCEDURE [dbo].[sp_ssis_deletefolder]
  @folderid uniqueidentifier
AS

ALTER PROCEDURE [dbo].[sp_ssis_deletepackage]
  @name sysname,
  @folderid uniqueidentifier
AS

As you can see, the parameters for these procedures are not that complicated.  Both of them expect a uniqueidentifier as identification for the folder.  That’s okay, these IDs are stored in the msdb.dbo.sysssispackagefolders table and retrieved by our queries to create the list of to-be-deleted objects.

Furthermore, the sp_ssis_deletepackage SP expects the name of the package to be deleted.  Not a problem either, those names are obtained from the msdb.dbo.sysssispackages table.

Note for SQL Server 2005 users: this code was written for SQL Server 2008.  The system stored procedures and system tables exist in 2005 as well, but they have different names.  See the comment header of my SP for more details.

So, let’s give it a little test.  Following screenshot shows the setup.  What I will do is use the stored procedure to delete the FolderWithSubfolders folder.  If you’ve been paying close attention, that is the same folder which I tried to delete manually through the Management Studio’s right-click menu (see first screenshot above).

Overview of my deployed folders and packages

After creating the SP, I ran following command:

EXEC dbo.SSIS_RecursiveDeleteFolder '/FolderWithSubfolders'

And that gave me the following output in the Messages pane:

exec msdb.dbo.sp_ssis_deletepackage AnotherPackage, 7F38288D-4370-40A8-80E3-E92283033E4C

exec msdb.dbo.sp_ssis_deletepackage Package, 7F38288D-4370-40A8-80E3-E92283033E4C

exec msdb.dbo.sp_ssis_deletefolder 4102ED59-ED75-4D93-BBAE-0A162447BF02

exec msdb.dbo.sp_ssis_deletefolder 7F38288D-4370-40A8-80E3-E92283033E4C

exec msdb.dbo.sp_ssis_deletefolder C156B436-8C78-4BF9-99F9-5ABFAB10C405

I have deliberately put a couple of print commands in the stored procedure to dump the commands that are actually being executed.  This gives us a good idea of what’s going on.

That’s it for now folks.  Thank you for reading this, and if you found it useful or you’ve got some questions about it: post a comment!

Have fun!

Valentino.

SQL Agent – Giving non-SA users permissions on jobs

Sunday, February 28th, 2010

There are some new database roles in the MSDB database in SQL Server 2005 that allow you to grant more granular permission to  non-sa users in the SQL Server agent.

If you work in an environment similar to the one i support, application and database code will pass through several environments en-route to production. Starting on the Dev box then moving to system testing and then to user acceptance testing, if you have any SQL Agent jobs that relate to the database being tested but you don’t want these jobs to run on to run on schedule, instead you want your testers to run as and when required. You can use these new roles to give your developers and testers permissions to run the jobs when they need too.

There are three roles in MSDB in SQL Server 2005 and onwards called:

SQLAgentUserRole – which is the least privileged of the three roles. Users in this role have permission on jobs and schedules that they own. This was the role i needed in my case.

SQLAgentReaderRole – Has all the permissions of SQLAgentUserRole plus the ability to view a list of multi-server jobs.

SQLAgentOperatorRole - is the most privileged of the SQL Server Agent fixed database roles. It includes all the permissions of SQLAgentUserRole and SQLAgentReaderRole. Members of this role can also view properties for operators and proxies, and enumerate available proxies and alerts on the server.

This information is freely available in Books Online (BOL)

When a non SA user logs into an instance using SSMS, they have to be a member of one of these roles to have the SQL Agent available to them.

The following script grants my login called test access to the MSDB database and then adds it to the SQLAgentUserRole:

USE [msdb]
GO
CREATE USER [Test] FOR LOGIN [Test]
GO
USE [msdb]
GO
EXEC sp_addrolemember N'SQLAgentUserRole', N'Test'
GO



This allowed the testers to run jobs that they owned when it suited them.

Why I blog

Sunday, February 28th, 2010

I enjoy blogging because it allows me to do the following:

  1. Research & Experiment- I find that I am oftentimes testing the limits of SQL Server, or an idea, or something that someone else says or writes about.
  2. Learn and then share ideas – I am always running into something, whether it’s a new way of doing something or finding a solution to an error. Learning and then publishing my findings is enjoyable for me.
  3. Take notes – I can’t remember the number of times where I actually went back to my blog to find the answer to something…I forget just like the other guy and need a reference myself.
  4. Have fun – If you have read any of my posts, you know that I keep it on the light-hearted side of things, and many of my blogs contain humor.

Blogging is for sure a lot of fun. I certainly enjoy learning, and then passing my findings along to others.  My mentor Don Bishop used to tell me to share everything and keep nothing to myself; back in 1998 I never knew why, but I do now. Thanks, Don.

From my adventures in blogging SQL Server topics, I'm finding that I do Research & Experimentation more than anything, whether it's a proof-of-concept or some wacked-out idea or premonition, and I make this conclusion because most of my posts seem to lean towards this category.  I should change my monker from "Sharing tips and tricks so that you might avoid the beatings that I've endured" to "Research & Experimentation, Sharing Ideas, and Fun".  There are many outlets for training these days - SQLServerpedia, MSDN, ASP.net, SQL Share , SQLServerCentral, and others - but few do the Research and Experimentation as I do on my site.

What is "Research and Experimentation" you ask?  For me it's simply doing something with our product that hasn't been done before, or taking an idea and moving it beyond a "Hello World" example. And I am also learning that this category many times leads to dead-ends, e.g. where nothing really comes out of idea.  I have probably a half-dozen topics right now that I am going to blog about that led to little or nothing, other than a conclusion, which might be along the lines of "don't do this", or "this was a bad idea", or simply "nothing came from this". Being creative is a refreshing outlet for me, but my plentiful ideas harvest little more often than not.

Going forward I'll probably throw in a little caveat that "hey this is an experiment" in some of my research blog postings, just to make sure that you know from what perspective the idea should be viewed.  I think of this now because I am going to put out a really cool post that perfectly fits this category, one that I spent quite a bit of time and effort on, that really went nowhere.  More importantly, though, I mention this now because a colleague of mine and good friend who assisted me on it - myself frustrated on the outcome - reminded me of the good that comes from these 'dead end' ideas.

As always, thanks for visiting.

Lee Everest

 

blogbanner     face

SSRS NOT LIKE expression

Saturday, February 27th, 2010
I had the recent request to provide the ability to filter data in a report that was not like specific pattern.  This requirement is not uncommon or difficult when placed on the dataset using a parameter:

SELECT FirstName
FROM Person.Person
WHERE FirstName NOT LIKE @filter
The difficulty was that the report was being presented from a snapshot so the filter needed to be placed in the form of an expression.  Again, this does not seem overly difficult except that there is no NOT LIKE comparison operator in SSRS, but fortunately we can embed VB.NET in reports. 

In this example I have a single report using my local instance of SQL 2008 and the AdventureWorks2008 as the data source and a dataset populated using the below query:

SELECT FirstName
FROM Person.Person

From the Report menu select Report Properties and enter the below VB.NET code in the Custom Code box:
   
Public Function NotLike(ByVal val As String, ByVal filter As String)As Boolean
If val.Contains(filter) Then
   Return False
Else
   Return True
End If
End Function

So you should end up with something like this

Now let's get an idea of how this works by displaying the results in a table side by side with the FirstName column from our dataset.

While in the Design tab of BIDS drag and drop a table from the toolbox on the design work surface.  In the first details field of the table drag and drop your FirstName column from your dataset from the Report Data tab.  Right click the second details field and choose Expression and enter the following expression:

=code.NotLike(Fields!FirstName.Value, "ld")


Now preview the report and take a look at what our embedded code is providing:


We can see that all first names that do not contain "ld" returns True, so they are NOT LIKE the filter text we entered, while names that do contain the filter, like Donald, returns False. 

We need to create a parameter that will hold the user provided provided  filter value.  Go back to the Design tab and from the Report Data tab right click the Parameters folder and choose Add New Parameter.  The parameter will be called Comparison and the prompt will be Comparison string and will be a text data type:


From the Report Data tab right click the dataset and select Dataset Properties:

Go to the Filters tab and add a filter.  In the Expression open the expression builder and enter the below:


=code.NotLike(Fields!FirstName.Value, Parameters!Comparison.Value)
In the Value text box enter =True:


Now click the preview tab again and in the Comparison string parameter prompt enter ld and click View Report.  The data set is now filtered on the expression utilizing our embedded code only where a value of True is returned representing values that are not like the provided string comparison.

This is a rather simplistic example of utilizing embedded code to filter a report, but should provide a good starting point.  If you are interested in reviewing more in depth information on embedded code within SSRS or creating and using custom code references in expressions then please take a look at the documentation on MSDN:



SQL SERVER – INSERT TOP (N) INTO Table – Using Top with INSERT

Friday, February 26th, 2010

During my recent training at one of the clients, I was asked regarding the enhancement in TOP clause. When I demonstrated my script regarding how TOP works along with INSERT, one of the attendees suggested that I should also write about this script on my blog. Let me share this with all of you and do let me know what you think about this.

Note that there are two different techniques to limit the insertion of rows into the table.

Method 1:

INSERT INTO TABLE …
SELECT TOP (N) Cols…
FROM Table1

Method 2:

INSERT TOP(N) INTO TABLE …
SELECT Cols…
FROM Table1

Today, we will go over the second method, which in fact is the enhancement in TOP clause along with INSERT. It is very interesting to also observe the difference between both the methods. Let us take one real example and understand what exactly happens in either case.

Method 1:

INSERT INTO TABLE …
SELECT TOP (N) Cols…
FROM Table1

Method 2:

INSERT TOP(N) INTO TABLE …
SELECT Cols…
FROM Table1

Today we will go over the second method which in fact is the enhancement in TOP along with INSERT. It is very interesting to also observe difference between both the methods. Let us play with one real example and we understand what exactly is happening in either of the case.

USE tempdb

GO

-- Create Table

IF EXISTS (SELECT * FROM sys.objects WHERE OBJECT_ID = OBJECT_ID(N'TestValue') AND type IN (N'U'))

DROP TABLE TestValue

GO

CREATE TABLE TestValue(ID INT)

INSERT INTO TestValue (ID)

SELECT 1

UNION ALL

SELECT 2

UNION ALL

SELECT 3

UNION ALL

SELECT 4

UNION ALL

SELECT 5

GO

-- Select Data from Table

SELECT *

FROM TestValue

GO

-- Create Two Table where Data will be Inserted

IF EXISTS (SELECT * FROM sys.objects WHERE OBJECT_ID = OBJECT_ID(N'InsertTestValue') AND type IN (N'U'))

DROP TABLE InsertTestValue

GO

CREATE TABLE InsertTestValue (ID INT)

IF EXISTS (SELECT * FROM sys.objects WHERE OBJECT_ID = OBJECT_ID(N'InsertTestValue1') AND type IN (N'U'))

DROP TABLE InsertTestValue1

GO

CREATE TABLE InsertTestValue1 (ID INT)

GO

-- Option 1: Top with Select

INSERT INTO InsertTestValue (ID)

SELECT TOP (2) ID

FROM TestValue

ORDER BY ID DESC

GO

-- Option 2: Top with Insert

INSERT TOP (2) INTO InsertTestValue1 (ID)

SELECT ID

FROM TestValue

ORDER BY ID DESC

GO

-- Check the Data

SELECT *

FROM InsertTestValue

GO

SELECT *

FROM InsertTestValue1

GO

-- Clean up

DROP TABLE InsertTestValue

DROP TABLE InsertTestValue1

DROP TABLE TestValue

GO

Now let us check the result of above SELECT statements.

Insert alogn with Top (N)
Insert alogn with Top (N)

It is very interesting to see when Option 2 is used, ORDER BY is absolutely ignored and data is inserted in any order.

In future articles, we will talk about performance for these queries. What are your thoughts on this feature? Have you used INSERT TOP(N) in your application?

Reference: Pinal Dave (http://blog.SQLAuthority.com)


Filed under: Database, Pinal Dave, SQL, SQL Authority, SQL Query, SQL Scripts, SQL Server, SQL Tips and Tricks, SQLServer, T SQL, Technology

A Rhode Island Yankee In Queen Elizabeth’s Rink

Friday, February 26th, 2010

I just found out via Facebook (thanks Jason Strate and Tim Mitchell) that Aaron Bertrand (web|twitter) took a very expensive route to make it onto TV and into the 2010 Winter Olympics. 

Here are the details:  http://lebertrand.wordpress.com/2010/02/25/312/

Congratulations Nicole and Aaron!

Help with Learning Powershell

Friday, February 26th, 2010

If you’re not reading Buck Woody’s blog, why not? Today he posted a helpful hint for getting performance counters directly out of PowerShell v2. I’ll add a little bit to the hint, don’t try running this on your XP boxes. It doesn’t hurt anything, but you get a helpful little message “Get-Counter : This cmdlet can only run on Vista and above.”


MacGyver Moments

Thursday, February 25th, 2010

Aaron Bertrand and Denny Cherry double-teamed me in another round of Web 2.0 Chainposting, and you know I never dare break the chain. I spent a few days thinking about what MacGyver moments I have had that I could share. And to be honest, very few came to mind. Apparently I am not very imaginative, or I have been fortunate enough to always have all the resources I need at my disposal.

Technically the rules for this chainpost do not call for me to recall a MacGyver moment only from my IT history. As such, many of my moments come from coaching. I would gladly recount for you the time we came from seven points down with two minutes to play, quite possibly my best coaching moment ever, but I doubt you want to read about that. I could tell you about the fantasy football website I built in ASP.NET on top of MS Access ‘97 and the late nights I put in trying to figure out a way to enforce the league rules by building various arrays, but that doesn’t seem too exciting either. And there are the handful of times I have coded a solution that helped streamline one thing or another, but it really doesn’t strike me as “MacGyver-esque”.

So, I decided to think about someone who is like MacGyver in real life. Someone who constantly makes something out of nothing and is able to get himself out of seemingly any situation. Watch this video and you’ll agree how nothing we do in IT can compare to what a real MacGyver would do in real life while thinking of England.

I’m tagging Kevin Kline, and only Kevin Kline, because I don’t want to hear him whine anymore.