Row level security + Switch statement - sql

I've a big table with lot of data who needs to have some RLS security.
The RLS is based on an other table (user logins with a profile number).
I've to make different logic of filtering depending on the profile number...
Let's say if the userProfile is 1 he can see all data.
If the user profile is 2 he can only see the data based on colA if he's 3 it's colB who have to be checked.
Example :
Profile | login Data | colA | colB
2 | toto data | toto | tutu
3 | tutu data | tata | tutu
I've tried to create a Switch statement based on profileType but it doesn't work. And i don't know if we can return a filtering in switch.
My try :
CREATE FUNCTION [dbo].[fn_rls_users](#username AS VARCHAR(50))
RETURNS TABLE
with schemabinding
AS
RETURN (
SELECT Department,ProfileType,
CASE
WHEN ProfileType = 1 THEN
RETURN (
SELECT 1 AS [fn_rls_users]
FROM BIG_TABLE
)
WHEN ProfileType = 2 THEN
RETURN (
SELECT 1 AS [fn_rls_users]
FROM BIG_TABLE
WHERE Department = Department
)
ELSE (
SELECT 0 AS [fn_rls_users]
FROM BIG_TABLE
)
END
FROM dbo.UserProfiles WHERE UserLogin = #username
)
GO
Any help appreciated

DECLARE #ProfileType date = (SELECT ProfileType FROM dbo.UserProfiles WHERE UserLogin = #username)
if #ProfileType = 1
select isnull(Data, 'NaN') from BIG_TABLE
else if #ProfileType = 2
select isnull(Data, 'NaN') from BIG_TABLE where colA = #username
else
select isnull(Data, 'NaN') from BIG_TABLE where colB = #username
End
Or, if you like using str exec
DECLARE #ProfileType date = (SELECT ProfileType FROM dbo.UserProfiles WHERE UserLogin = #username)
declare #str_sql varchar(1000)
set #str_sql = 'select isnull(Data, "NaN") from BIG_TABLE'
if #ProfileType = 2
set #str_sql += ' where colA = ' + #username
else if #ProfileType = 3
set #str_sql += ' where colB = ' + #username
exec(#str_sql)

Related

Search Accounts using SQL Server 2008 stored procedure?

I recently started developing/improving the way my search algorithm works. There is few different queries in the system that will use this approach. I'm new in stored procedures world and from what I researched they should improve security, performance and save some code redundancy. Here is example of what I have done in one of my stored procedures for Account search:
ALTER PROCEDURE [dbo].[SearchAccounts]
#Status INT = NULL,
#Type INT = NULL,
#FilterBy INT = NULL,
#Username VARCHAR(50) = NULL,
#Email VARCHAR(80) = NULL,
#LastName VARCHAR(50) = NULL,
#FirstName VARCHAR(50) = NULL,
#FullName VARCHAR(100) = NULL
WITH RECOMPILE
AS
DECLARE #AccountStatus INT = #Status;
DECLARE #AccountType INT = #Type;
DECLARE #AccountFilter INT = #FilterBy;
DECLARE #AccountUsername VARCHAR(50) = #Username;
DECLARE #AccountEmail VARCHAR(80) = #Email;
DECLARE #AccountLast VARCHAR(50) = #LastName;
DECLARE #AccountFirst VARCHAR(50) = #FirstName;
DECLARE #AccountFull VARCHAR(10) = #FullName;
SELECT
A.AccountID, A.FirstName, A.LastName, A.Middle, A.Email,
A.IsUser, A.ActiveUser, A.SystemAdmin, A.AccessType,
A.AccessLevel, A.UserName, A.IsStaff, A.ActiveStaff,
A.Position AS PositionCode, M.Name AS Position
FROM
Accounts AS A
LEFT OUTER JOIN
Master AS M ON M.Tblid = 'STAFF_POS'
AND M.Code = A.Position
WHERE
( -- If Account Type is 1 (User)
(#AccountType = 1 AND A.IsUser = 1)
OR -- If Account Type is 2 (Staff)
(#AccountType = 2 AND A.IsStaff = 1)
OR -- Or if Account type is 0 (All accounts)
(#AccountType = 0 AND 1 = 1)
)
AND
(
(-- If account type is user and Status is 1 (Active) or 0 (Inactive) or 2(pull active and inactive)
#AccountType = 1
AND
(#AccountStatus = 0 OR #AccountStatus = 1) AND ActiveUser = #AccountStatus)
OR
(#AccountType = 1 AND #AccountStatus = 2 AND 1 = 1
)
OR
(-- If account type is staff and Status is 1 (Active) or 0 (Inactive) or 2 (pull active and inactive)
#AccountType = 2
AND
(#AccountStatus = 0 OR #AccountStatus = 1) AND ActiveStaff = #AccountStatus)
OR
(#AccountType = 2 AND #AccountStatus = 2 AND 1 = 1
)
OR
(-- If account type is all pull all accounts active and inactive
(#AccountType != 1 AND #AccountType != 2 AND 1 = 1)
)
)
AND
( -- Filter is 1 then check user name.
(#AccountFilter = 1 AND A.UserName LIKE '%'+#AccountUsername+'%')
OR -- Filter is 2 then check email.
(#AccountFilter = 2 AND A.Email = #AccountEmail)
-- Here if filter is 3 then I should check First or Last or Full Name.
-- Still not sure what is the best approach to filter on the name fields.
)
ORDER BY
A.LastName, A.FirstName
As you can see query above has few different filters. First user can choose if they want to search Users, Staff or pull All account types. Then to choose if they want to pull Active, Inactive or all records. Last thing is to pick the filter. I give them an option to search Username, Email or Name. Each Account has First, Last name. What would be a good option to search for those names? Check just one full name or I have to check first and last separately? First and Last name allow this set of characters in the Accounts form : A-Z, space, dash, apostrophe, period, comma - no other special characters
There are probably dozens of ways to accomplish such a goal. But one way I would prefer (if I was using a query like the one presented) would be like this:
AND A.LNAME LIKE
CASE WHEN Len(#AccountLast) > 0 THEN
'%' + #AccountLast + '%'
ELSE '%'
END
AND A.FNAME LIKE
CASE WHEN Len(#AccountFirst) > 0 THEN
'%' + #AccountFirst + '%'
ELSE '%'
END

Fill multiple empty rows

I'm tryting to fill empty rows from my table . Here is the example from my table.
ID | Code |
1 NULL
2 NULL
3 NULL
what i want is
ID | Code |
1 KL0000001
2 KL0000002
3 KL0000003
I'm using sql server 2008 and here is my script so far :
declare #jml as int;
declare #no as int = 1;
declare #kode as varchar(50);
set #jml = (SELECT COUNT(IdArealeader) FROM arealeader);
set #kode = (select case
when right(max(KodeareaLeader),7) is null then 'KL0000001'
else ('KL' + RIGHT('0000000' + cast(right(max(KodeareaLeader),7) + 1 as nvarchar),7))
end KodeareaLeader from arealeader)
while #no < #jml begin
update arealeader set KodeareaLeader = #kode;
END
Try this simple method,
UPDATE T
SET T.Code = 'KL'+REPLICATE('0',7 - LEN(ID))+CAST(ID AS NVARCHAR(10))
FROM test_table T
Try to avoid loops, only use if it necessary.
Result
ID Code
1 KL0000001
2 KL0000002
3 KL0000003
....
10 KL0000010

SQL IF to Set Parameter Value Within SELECT Statement

EDIT: This problem has been solved using a better method. Rather than using a variable I am concatenating results to get my desired output.
I am trying to get values from several fields from a couple of different tables and that works fine. But I would also like to have a "#Description" which is a concatenation of hard coded text based on existing values for each row.
DECLARE #Description VARCHAR(1000) = ''
SELECT t1.ID
t1.myType
t1.myName
,CASE WHEN
(
t1.aValue1 = 1
AND t1.aValue2 = 1
AND t2.aValue1 = 1
AND t2.aValue2 = 1
)
THEN 'ELIGIBLE'
ELSE 'NOT ELIGIBLE'
END AS 'isEligible'
,#Description
-- Then I'm trying to set the description
/*
IF
(
t1.aValue1 = 2
)
SET #Description = #Description + ' t1.aValue1 is 2'
IF
(
t1.aValue2 = 2
)
SET #Description = #Description + ' t1.aValue2 is 2'
IF
(
t2.aValue1 = 2
)
SET #Description = #Description + ' t2.aValue1 is 2'
IF
(
t2.aValue2 = 2
)
SET #Description = #Description + ' t2.aValue2 is 2'
*/
END AS 'Description'
FROM [dbo].[Table1] t1
JOIN [dbo].[Table2] t2 ON t1.ID = t2.ID
So for example, if a row had a t1.aValue1 = 2 and t2.aValue1 = 2 then the output might look something like this.
ID | myType | myName | isEligible | Description
-----------------------------------------------
...
24 | Red | John | Not Eligible | t1.aValue1 is 2 t2.aValue1 is 2
25 | Blue | Eric | Eligible |
etc...
I am using SSMS 2008 R2.
How would I accomplish this?
You have the variable, but the expected results looks like you actually want it to the row? You can get it to the row just by using case, something like this:
END AS isEligible,
case when t1.aValue1 = 2 then 't1.aValue1 is 2 ' else '' end +
case when t1.aValue2 = 2 then 't1.aValue2 is 2 ' else '' end +
case when t2.aValue1 = 2 then 't2.aValue1 is 2 ' else '' end +
case when t2.aValue2 = 2 then 't2.aValue2 is 2 ' else '' end as Description

T-SQL Procedure split and join on sub columns

Not really sure how to explain this so I just start with the example.
Say I have a table like this which can include several rows:
id Type Text
1 Test Failure A=123 B=444 C=43343 Error=4 ErroDes=1
I also have a static Error and ErrorDes table which look like this
Id Code Description
1 1 Error1
2 4 Error4
How can I split up the information from the column into seperate fields and also join in the info from the subtables.
Expected result would be something like this:
Type Field1 FieldA FieldB FieldC Error ErrorDes
Test Failure 123 444 43343 Error4 Error1
I used the same table for joining in the example but this is 2 tables in the db.
So to help with this I have a split function in the database.
And if I first split the Text field on "space" and then on "=" I get everything I need (or atleast all the columns in seperate rows)
cross apply dbo.Split(a.Text, ' ') s
cross apply dbo.Split(s.Value, '=') s2
I get "TokenID" and "Value" field back from the split function.
The output from that looks like this:
TokenID Value TokenID Value
1 Failure 1 Failure
2 A=123 1 A
2 A=123 2 123
3 B=444 1 B
3 B=444 2 444
4 C=43343 1 C
4 C=43343 2 43343
5 Error=4 1 Error
5 Error=4 2 4
6 ErrorDes=1 1 ErrorDes
6 ErrorDes=1 2 1
I hope you understand what I ment and can help me how this can be solved.
you can use something like the folowing UDF function to cross apply
create function udf_ReturnTextSplit(#vText varchar(100))
returns #rt table (
Field1 varchar(100),
FieldA varchar(100),
FieldB varchar(100)
) as begin
declare #st varchar(100) = #vText + ' '
declare #sti varchar(100)
declare #stj varchar(100)
insert into #rt (Field1, FieldA, FieldB) values (null, null, null)
declare #i int = charindex(' ', #st)
while #i > 0 begin
set #sti = SUBSTRING(#st, 1, #i)
set #st = substring(#st, #i + 1, 100)
set #i = CHARINDEX('=', #sti)
if #i > 0 begin
set #stj = substring(#sti, #i + 1, 100)
set #sti = substring(#sti, 1, #i - 1)
if #sti = 'A' update #rt set FieldA = #stj
if #sti = 'B' update #rt set FieldB = #stj
end else begin
update #rt set Field1 = #sti
end
set #i = charindex(' ', #st)
end
return
end
go
select * from dbo.udf_ReturnTextSplit('Failure A=123 B=444 C=43343 Error=4 ErroDes=1')

merge two rows in return query as one

I have a query that returns the people in a certain household, however the individuals show up in to separate rows, what i want to do is merge the two rows into one.
SELECT dbo.households.id, dbo.individuals.firstname, dbo.individuals.lastname
FROM dbo.households INNER JOIN
dbo.individuals ON dbo.households.id = dbo.individuals.householdID
WHERE (dbo.households.id = 10017)
Current results:
ID | First Name | Last Name |
1 | Test | Test1 |
1 | ABC | ABC1 |
Desired results:
ID | First Name | Last Name |ID1| First Name1| Last Name1|
1 | Test | Test1 |1 | ABC | ABC1 |
However if theres 3 people then it would need to merge all 3 and so on
Depending on the response to the question I asked above, below is a simple script that would compile the names into a string and then output the string (I don't have access to the syntax validator now so forgive any errors):
DECLARE
#CNT INT,
#R_MAX INT,
#H_ID INT,
#R_FIRST VARCHAR(250),
#R_LAST VARCHAR(250),
#R_NAMES VARCHAR(MAX)
SET #CNT = 0; --Counter
SET #R_NAMES = 'Names: ';
SELECT #R_MAX = COUNT(*) FROM dbo.individuals a WHERE a.householdID = #H_ID; --Get total number of individuals
PRINT(#R_MAX); --Output # of matching rows
--Loop through table to get individuals
WHILE #CNT < #R_MAX
BEGIN
--Select statement
SELECT * FROM (SELECT
#R_FIRST = b.firstname,
#R_LAST = b.lastname,
ROW_NUMBER() OVER (ORDER BY b.lastname, b.firstname) AS Row
FROM
dbo.households a INNER JOIN
dbo.individuals b ON a.id = b.householdID
WHERE
(a.id = #H_ID)) AS RN WHERE (Row = #CNT);
SET #R_NAMES = #R_NAMES + #R_FIRST + #R_LAST + '; '; --Add individual's name to name string
PRINT(CAST(#CNT AS VARCHAR) + ':' + #R_NAMES);
SET #CNT = #CNT +1; --Increase counter
END
PRINT(#R_NAMES); --Output the individuals
Provided you're using SQL Server 2005 or up, you might be able to use FOR XML PATH('') to concatenate the strings.
This should do what you want without having to do manual loops:
edit: fixed up SQL to actually work (now I have access to SQL)
SELECT households.id,
STUFF(
(
SELECT '; ' + [firstname] + '|' + lastname AS [text()]
FROM individuals
WHERE individuals.householdID = households.id
FOR XML PATH('')
)
, 1, 2, '' ) -- remove the first '; ' from the string
AS [name]
FROM dbo.households
WHERE (households.id = 10017)
This is pretty close to the format of data that you wanted.
it converts the data to XML (without any actual XML markup due to the PATH('')) and then joins it back to the header row.