Thanks for having a look Wayne and for the nod. Also, congrats on recently becoming an MCM! That's an amazing accomplishment.
I agree that using sys.sql_logins is a cleaner way to differentiate a Loginless User from a User linked to a Login but what I found when researching how to do this in my environment is that using the instance-level views only held up in the case when the User had not yet been orphaned. The scenario I tried to address in the article was to find Users that were presently orphaned meaning I needed a way to omit Loginless Users from the resultset.
Consider this test case to see why I went after the SID:
USE YourDatabaseName;
GO
IF NOT EXISTS (SELECT 1 FROM sys.server_principals WHERE name = 'monkey')
CREATE LOGIN monkey WITH PASSWORD = 'monkey', CHECK_POLICY = OFF;
GO
CREATE USER monkey FROM LOGIN monkey;
CREATE USER donkey WITHOUT LOGIN;
-- run the query you showed as "prior to SQL 2012, you can left join to sys.sql_logins" to see what types of users these are
SELECT dp.name,
dp.type,
dp.type_desc,
CASE WHEN sl.name IS NULL AND dp.type = 'S' THEN 0
WHEN dp.type = 'S' THEN 2
WHEN dp.type LIKE '[UG]' THEN 3
END AS authentication_type, -- ignoring contained database users
CASE WHEN sl.name IS NULL AND dp.type = 'S' THEN 'NONE'
WHEN dp.type = 'S' THEN 'INSTANCE'
WHEN dp.type LIKE '[UG]' THEN 'WINDOWS'
END AS authentication_type_desc
FROM sys.database_principals dp
LEFT JOIN sys.sql_logins sl ON dp.sid = sl.sid
WHERE dp.name IN ('donkey', 'monkey');
-- looks good, we can tell that monkey is a user with a Login and donkey
-- is a Loginless User
-- now let's orphan monkey
DROP LOGIN monkey;
-- run the same query again
SELECT dp.name,
dp.type,
dp.type_desc,
CASE WHEN sl.name IS NULL AND dp.type = 'S' THEN 0
WHEN dp.type = 'S' THEN 2
WHEN dp.type LIKE '[UG]' THEN 3
END AS authentication_type, -- ignoring contained database users
CASE WHEN sl.name IS NULL AND dp.type = 'S' THEN 'NONE'
WHEN dp.type = 'S' THEN 'INSTANCE'
WHEN dp.type LIKE '[UG]' THEN 'WINDOWS'
END AS authentication_type_desc
FROM sys.database_principals dp
LEFT JOIN sys.sql_logins sl ON dp.sid = sl.sid
WHERE dp.name IN ('donkey', 'monkey');
-- according to the query monkey and donkey both look to be orphaned however we know that
-- donkey is a loginless user and monkey used to have a login but was recently orphaned
DROP USER monkey;
DROP USER donkey;
GO
What I found was that as soon as the User was orphaned, i.e. when the Login was dropped, the metadata changed to my disadvantage making it impossible to differentiate a Loginless User from a regular, now-orphaned User:
This behavior is was what pushed me towards deriving the information based on the length of the SID.
I do like that there is a cleaner experience in SQL 2012. Thank you for bringing these new columns to my attention. I must admit that at the current shop I work almost exclusively with 2008 R2 with only some 2005 and a couple 2012 instances coming online soon so the method is targeted at 2005 and 2008 R2. I tested the method in the article on SQL 2012 and it seemed to hold up but if and when I am in a shop with only SQL 2012 I would probably switch to leverage the new columns. Here is what I came up with, adapted from what you shared:
-- SQL 2012
SELECT dp.name,
dp.type,
dp.type_desc,
dp.authentication_type,
dp.authentication_type_desc,
sp.name
FROM sys.database_principals dp
LEFT JOIN sys.server_principals sp ON dp.sid = sp.sid
WHERE dp.authentication_type_desc = N'INSTANCE'
AND sp.name IS NULL;
I tested the above query on regular and partially contained databases and it seemed to work well, although I did not do anything that could be considered exhaustive. The thing that seems to make this method hold up is that after dropping the Login the value of authentication_type_desc remains equal to 'INSTANCE'.
There are no special teachers of virtue, because virtue is taught by the whole community.
--Plato