Watch & Subscribe my SQL videos on YouTube | Join me on Facebook

Parse or Query XML column with XMLNAMESPACES (xmlns namespace) – MSDN TSQL forum

January 15, 2016 Leave a comment

–> Question:

We have SQL audit information.
We would like to select XML column in some user friendly way.

CREATE TABLE [dbo].[audit](
	[server_instance_name] [NVARCHAR](128) NULL,
	[statement] [NVARCHAR](4000) NULL,
	[additional_information] XML NULL
)


INSERT INTO [dbo].[audit]([server_instance_name],[statement],[additional_information]) 
VALUES('srv1','sp_addlinkedsrvlogin','')

INSERT INTO [dbo].[audit]([server_instance_name],[statement],[additional_information]) 
VALUES('srv2','','<action_info xmlns="http://schemas.microsoft.com/sqlserver/2008/sqlaudit_data"><session><![CDATA[Audit$A]]></session><action>event enabled</action><startup_type>manual</startup_type><object><![CDATA[audit_event]]></object></action_info>')

SELECT * FROM [dbo].[audit]

Output of the XML column:

XML parse

Required Output:

XML parse2

 

–> Answer:

;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2008/sqlaudit_data' as ns)
SELECT 
	 [server_instance_name]
	,[statement]
	,[additional_information]
	,t.c.value ('ns:session[1]', 'varchar(50)') AS session
	,t.c.value ('ns:action[1]', 'varchar(50)') AS action
	,t.c.value ('ns:startup_type[1]', 'varchar(50)') AS startup_type
	,t.c.value ('ns:object[1]', 'varchar(50)') AS object
FROM [audit] as a
OUTER APPLY a.additional_information.nodes('//ns:action_info') as t(c)
GO

-- OR -- 

;WITH XMLNAMESPACES(DEFAULT 'http://schemas.microsoft.com/sqlserver/2008/sqlaudit_data')
SELECT 
	 [server_instance_name]
	,[statement]
	,t.c.value ('session[1]', 'varchar(50)') AS session
	,t.c.value ('action[1]', 'varchar(50)') AS action
	,t.c.value ('startup_type[1]', 'varchar(50)') AS startup_type
	,t.c.value ('object[1]', 'varchar(50)') AS object
FROM [audit] as a
OUTER APPLY a.additional_information.nodes('//action_info') as t(c)
GO

 

Drop table finally

DROP TABLE [audit]
GO

 

Ref link.


SQL Trivia – Difference between COUNT(*) and COUNT(1)

January 13, 2016 3 comments

Yesterday I was having a discussion with one of the Analyst regarding an item we were going to ship in the release. And we tried to check and validate the data if it was getting populated correctly or not. To just get the count-diff of records in pre & post release I used this Query:

SELECT COUNT(*) FROM tblXYZ

To my surprise he mentioned to use COUNT(1) instead of COUNT(*), and the reason he cited was that it runs faster as it uses one column and COUNT(*) uses all columns. It was like a weird feeling, what to say… and I just mentioned “It’s not, and both are same”. He was adamant and disagreed with me. So I just kept quite and keep on using COUNT(*) 🙂
 

But are they really same or different? Functionally? Performance wise? or by any other means?

Let’s check both of them.
 

The MSDN BoL lists the syntax as COUNT ( { [ [ ALL | DISTINCT ] expression ] | * } )

So, if you specify any numeric value it come under the expression option above.

Let’s try to pass the value as 1/0, if SQL engine uses this value it would definitely throw a “divide by zero” error:

SELECT COUNT(1/0) FROM [Person].[Person]

… but it does not. Because it just ignores the value while taking counts. So, both * and 1 or any other number is same.
 

–> Ok, let’s check the Query plans:

count(star) vs count(1).

and there was no difference between the Query plans created by them, both have same query cost of 50%.
 

–> These are very simple and small queries so the above plan might be trivial and thus may have come out same or similar.

So, let’s check more, like the PROFILE stats:

SET STATISTICS PROFILE ON
SET STATISTICS IO ON

SELECT COUNT(*) FROM [Sales].[SalesOrderDetail]

SELECT COUNT(1) FROM [Sales].[SalesOrderDetail]

SET STATISTICS PROFILE OFF
SET STATISTICS IO OFF

If you check the results below, the PROFILE data of both the queries shows COUNT(*), so the SQL engine converts COUNT(1) to COUNT(*) internally.

SELECT COUNT(*) FROM [Sales].[SalesOrderDetail]
  |--Compute Scalar(DEFINE:([Expr1002]=CONVERT_IMPLICIT(int,[Expr1003],0)))
       |--Stream Aggregate(DEFINE:([Expr1003]=Count(*)))
            |--Index Scan(OBJECT:([AdventureWorks2014].[Sales].[SalesOrderDetail].[IX_SalesOrderDetail_ProductID]))

SELECT COUNT(1) FROM [Sales].[SalesOrderDetail]
  |--Compute Scalar(DEFINE:([Expr1002]=CONVERT_IMPLICIT(int,[Expr1003],0)))
       |--Stream Aggregate(DEFINE:([Expr1003]=Count(*)))
            |--Index Scan(OBJECT:([AdventureWorks2014].[Sales].[SalesOrderDetail].[IX_SalesOrderDetail_ProductID]))

 

–> On checking the I/O stats there is no difference between them:

Table 'SalesOrderDetail'. Scan count 1, logical reads 276, physical reads 1, 
 read-ahead reads 288, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Table 'SalesOrderDetail'. Scan count 1, logical reads 276, physical reads 0, 
 read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Both the queries does reads of 276 pages, no matter they did logical/physical/read-ahead reads here. Check difference b/w logical/physical/read-ahead reads.
 

So, we can clearly and without any doubt say that both COUNT(*) & COUNT(1) are same and equivalent.
 

There are few other things in SQL Server that are functionally equivalent, like DECIMAL & NUMERIC datatypes, check here: Difference b/w DECIMAL & NUMERIC datatypes.


Categories: Differences, Misconception Tags:

SQL Error – The database owner SID recorded in the master database differs from the database owner SID

January 9, 2016 Leave a comment

 
Today we got an email from our support team that they are not able to execute Stored Procedures on a particular Database and getting following error:

The server principal “domain\user” is not able to access the database “dbXYZ” under the current security context.

The database owner SID recorded in the master database differs from the database owner SID recorded in database ‘dbXYZ’. You should correct this situation by resetting the owner of database ‘dbXYZ’ using the ALTER AUTHORIZATION statement.

 

The Database was restored by a backup from Production box to a separate Dev box. I checked the Properties of the Database and the Owner property shows the Prod service account, which cannot be created on Dev machine.

So, we just need to change the Owner Name of the database by using “ALTER AUTHORIZATION” statement of sp_changedbowner system SP, as shown below:

USE [DatabaseName]
GO

-- Option #1
EXEC sp_changedbowner 'sa'
GO

-- OR--

-- Option #2
ALTER AUTHORIZATION ON DATABASE::[DatabaseName] TO [sa]
GO

 

And the issue got resolved !!!


Categories: SQL Errors

Implementing “Row Level Security” (RLS) with “Block Predicates” in SQL Server 2016 – Part 2

January 9, 2016 5 comments

In my [previous post] I discussed about Row Level Security, its introduction, and a detailed demo on how to implement it.

There I discussed about the Filter Predicate option available in the CTP 2.x release. With this option we can restrict read access for a user to avoid unauthorized access of rows.

Filter Predicates filters the read operations like SELECT, UPDATE and DELETE, but do not filter INSERT operations, and thus a user can INSERT any row (associate to another user also).
 

Here in this post I will talk about the new Block Predicate option available in the CTP 3.0 release. With this option we can restrict write access for specific users.

Block Predicates block all write operations like:

– AFTER INSERT and AFTER UPDATE

– BEFORE UPDATE

– and BEFORE DELETE

To know more about these write operations check MS BoL here.
 

I’m using the same script I used in my [previous post] about RLS with Filter Predicates.

–> Step #1. Create some test accounts and test data:

-- Let's create some test accounts:

-- Three users for The CEO, HR &amp; Finance department admin users.
CREATE USER userCEO WITHOUT LOGIN;
GO
CREATE USER userHR WITHOUT LOGIN;
GO
CREATE USER userFin WITHOUT LOGIN;
GO
CREATE USER userAdmin WITHOUT LOGIN;
GO

-- Create a sample table [dbo].[Employee]: with a self-referencing Manager ID column.
CREATE TABLE dbo.Employee (
	[EmpCode] NVARCHAR(50),  -- Employee ID
	[EmpName] NVARCHAR(250), -- Employee/Manager Full Name
	[Salary]  MONEY,		 -- Fictious Salary
	[MgrCode] NVARCHAR(50)   -- Manager ID
);
GO

-- Now insert some test records:

-- Top Boss CEO
INSERT INTO dbo.Employee VALUES ('userCEO' , 'CEO Top Boss'  , 800, NULL)

-- Next 2 levels under CEO
INSERT INTO dbo.Employee VALUES ('userHR'  , 'HR User'		 , 700, 'userCEO');
INSERT INTO dbo.Employee VALUES ('userFin' , 'Finance User'  , 600, 'userCEO');

-- Employees under Kevin
INSERT INTO dbo.Employee VALUES ('manojp'  , 'Manoj Pandey'  , 100, 'userHR');
INSERT INTO dbo.Employee VALUES ('saurabhs', 'Saurabh Sharma', 400, 'userHR');
INSERT INTO dbo.Employee VALUES ('deepakb' , 'Deepak Biswal' , 500, 'userHR');

-- Employees under Amy
INSERT INTO dbo.Employee VALUES ('keshavk' , 'Keshav K'		 , 200, 'userFin');
INSERT INTO dbo.Employee VALUES ('viveks'  , 'Vivek S'		 , 300, 'userFin');
GO

-- Let's check the records before applying "Row Level Security":
SELECT * FROM dbo.Employee; -- 8 rows
GO

Output: As a normal SEELCT and without RLS, it just ignores my Execution Context and execute the Query and return all the 8 rows.
 

–> Step #2. Grant only SELECT to the CEO-User, and Grant SELECT, INSERT,UPDATE, DELETE access on the dbo.Employee table to all 3 users:

GRANT SELECT ON dbo.Employee TO userCEO;
GO
GRANT SELECT, INSERT, UPDATE, DELETE ON dbo.Employee TO userHR;
GO
GRANT SELECT, INSERT, UPDATE, DELETE ON dbo.Employee TO userFin;
GO
GRANT SELECT, INSERT, UPDATE, DELETE ON dbo.Employee TO userAdmin;
GO

 

The new “Row Level Security” feature lets you:

– apply this security at the database level

– create two predicate functions, Filter & Block Predicates.

– and there is no need to apply the WHERE clause to filter out unauthorized users.
 

–> Step #3a. Create an Inline Table-Valued Function to create a Filter Predicate function:

CREATE FUNCTION dbo.fn_SecurityFilterPredicateEmployee (@mgrCode AS sysname)
    RETURNS TABLE
WITH SCHEMABINDING
AS
    RETURN SELECT 1 AS fn_SecurityPredicateEmployee_result 
	-- Predicate logic
	WHERE @mgrCode = USER_NAME() 
	OR USER_NAME() IN ('userCEO', 'userAdmin'); -- the CEO and Admin should see all rows
GO

–> Step #3b. Create an Inline Table-Valued Function to create a Block Predicate function:

CREATE FUNCTION dbo.fn_SecurityBlockPredicateEmployee (@mgrCode AS sysname)
    RETURNS TABLE
WITH SCHEMABINDING
AS
    RETURN SELECT 1 AS fn_SecurityPredicateEmployee_result 
	-- Predicate logic
	WHERE @mgrCode = USER_NAME() 
	OR USER_NAME() = 'userAdmin'; -- the Admin should have SELECT, INSERT, UPDATE, DELETE access to all rows
GO

Both the functions returns value 1 when a row in the MgrCode (Manager ID) column is the same as the user executing the query (@@mgrCode = USER_NAME()) or if the user executing the query is the Top Boss user USER_NAME() IN (‘userCEO’, ‘userAdmin’) and (USER_NAME() = ‘userCEO’) respectively.
 

–> Step #4. Create a security policy adding both the functions for Filter & Block predicates:

CREATE SECURITY POLICY ManagerFilter
ADD FILTER PREDICATE 
	dbo.fn_SecurityFilterPredicateEmployee(MgrCode) ON dbo.Employee, 
ADD BLOCK PREDICATE 
	dbo.fn_SecurityBlockPredicateEmployee(MgrCode) ON dbo.Employee 
WITH (STATE = ON); -- The state must be set to ON to enable the policy.
GO

-- Now let's again check the records after applying "Row Level Security":
SELECT * FROM dbo.Employee; -- 0 rows, because my used does not have any access.
GO

Finally with the above 4 steps we’ve configured RLS with both Filter & Block predicates, to restrict unauthorized access and modification of data.
 


 

Note: to check and validate RLS with Filter Predicate while retrieving data (or SELECT) please refer my [previous post].
 

–> Now, let’s check and validate RLS with Block Predicate while Updating data (INSERT, UPDATE, DELETE):

Usage #1. First we will check our top boss ‘userCEO’ account which has just read/SELECT access:

EXECUTE AS USER = 'userCEO';
	SELECT * FROM dbo.Employee; -- 8 rows

-- 1. Insert:
	INSERT INTO dbo.Employee (EmpCode, EmpName, Salary, MgrCode) 
	VALUES ('akashm' , 'Akash M'  , 550, 'userCEO') -- error
/*
Msg 229, Level 14, State 5, Line 106
The INSERT permission was denied on the object 'Employee', database 'TestManDB', schema 'dbo'.
*/

-- 2. Update:
	UPDATE [dbo].[Employee] SET Salary = 900 WHERE EmpCode = 'akashm' -- error
/*
Msg 229, Level 14, State 5, Line 111
The UPDATE permission was denied on the object 'Employee', database 'TestManDB', schema 'dbo'.
*/

-- 3. Delete:
	DELETE FROM [dbo].[Employee] WHERE EmpCode = 'akashm' -- error
/*
Msg 229, Level 14, State 5, Line 118
The DELETE permission was denied on the object 'Employee', database 'TestManDB', schema 'dbo'.
*/
REVERT;
GO

As you can see above for ‘userCEO’ account all INSERT/UPDATE/DELETE operations failed on the Employee table, as it only has read/SELECT access.
nbsp;

Usage #2. Now let’s check the ‘userHR’ account which as all permissions on his data:

EXECUTE AS USER = 'userHR';
	SELECT * FROM dbo.Employee; -- 3 rows

-- 1. Insert:
	INSERT INTO dbo.Employee (EmpCode, EmpName, Salary, MgrCode) 
	VALUES ('akashm' , 'Akash M'  , 550, 'userHR')
	SELECT * FROM dbo.Employee; -- 4 rows

-- 2: Update:
	UPDATE [dbo].[Employee] SET Salary = 900 WHERE EmpCode = 'akashm'
	SELECT * FROM dbo.Employee; -- 4 rows, with Salary = 900

-- 3: Delete:	
	DELETE FROM [dbo].[Employee] WHERE EmpCode = 'akashm'
	SELECT * FROM dbo.Employee; -- 3 rows

-- 4. Update another user's row:
	UPDATE [dbo].[Employee] SET Salary = 1000 WHERE EmpCode = 'viveks'
	-- (0 row(s) affected)
REVERT;
GO

So, the ‘userHR’ account can modify his records in all 3 INSERT/UPDATE/DELETE operations. But the 4th UPDATE operation fails where he tries to update another user (‘userFin’) record. However this does not displays any error message, but the query output message clearly shows no records were updated.
 

Usage #3. Execute as our Admin who can insert record for any User:

EXECUTE AS USER = 'userAdmin';
	SELECT * FROM dbo.Employee; -- 8 rows

-- 1. Insert row for another user HR:
	INSERT INTO dbo.Employee (EmpCode, EmpName, Salary, MgrCode) 
	VALUES ('akashm' , 'Akash M'  , 550, 'userHR')
	SELECT * FROM dbo.Employee; -- 9 rows

-- 2. Insert row for another user Fin:
	INSERT INTO dbo.Employee (EmpCode, EmpName, Salary, MgrCode) 
	VALUES ('shantis' , 'Shanti S'  , 200, 'userFin')
	SELECT * FROM dbo.Employee; -- 10 rows

-- 3. Update other user's rows (HR & Fin):
	UPDATE [dbo].[Employee] SET Salary = 900 WHERE EmpCode IN ('akashm', 'shantis')
	SELECT * FROM dbo.Employee; -- 10 rows total, last 2 rows with Salary = 900

-- 4. Delete other user's rows (HR & Fin)::
	DELETE FROM [dbo].[Employee] WHERE EmpCode IN ('akashm', 'shantis')
	SELECT * FROM dbo.Employee; -- 8 rows
REVERT;
GO

And here you can see the ‘userAdmin’ account is able to update all rows belonging to other users as well, as it is configured to behave like that, and this logic is built into the Security function that we build above in Step 3.b, where the Predicate logic looks like this:

    -- Predicate logic
    WHERE @mgrCode = USER_NAME() 
    OR USER_NAME() = 'userAdmin';

 

–> Thus, by using Row Level Security feature in SQL Server 2016 you can build your own customized Security feature for Read & Write operations for Tables by creating Inline Table-Valued Functions with Filter & Block predicates which are linked to the Security Policy for respective tables.
 

–> Final Cleanup

USE [TestManDB]
GO
DROP SECURITY POLICY [dbo].[ManagerFilter]
GO
DROP FUNCTION [dbo].[fn_SecurityFilterPredicateEmployee]
GO
DROP FUNCTION [dbo].[fn_SecurityBlockPredicateEmployee]
GO
DROP TABLE [dbo].[Employee]
GO

 

Check the same demo on YouTube:

Check & Like my FB Page


2015 in review

December 30, 2015 Leave a comment

The WordPress.com stats helper monkeys prepared a 2015 annual report for this blog.

Here’s an excerpt:

The Louvre Museum has 8.5 million visitors per year. This blog was viewed about 520,000 times in 2015. If it were an exhibit at the Louvre Museum, it would take about 22 days for that many people to see it.

In 2015, there were 68 new posts, growing the total archive of this blog to 272 posts.

Click here to see the complete report.