SQL: If exists, limit user. If not exists show everything - sql-server

I'm trying to found the best way to this requirements:
#fkStaffID INT = Current user.
If #fkStaffID got resource BLABLA only show rows of table X where is StaffID is here. If he DON'T have resource BLABLA, show everything.
SORRY I cannot paste full SQL, for employer's security policy. (I wish I show enough for help, not too much for security...)
What I do:
SELECT * FROM X
WHERE ((EXISTS
(SELECT 1 FROM STAFF WHERE pkStaff=#fkStaffID
AND STAFF.PkStaff IN (SELECT fkStaff FROM SECURITYSUBQUERY WHERE ResourceName='BLABLA')) AND X.fkStaff=#fkStaffID)
OR ((NOT EXISTS (SELECT 1 FROM STAFF WHERE pkStaff=#fkStaffID
AND STAFF.PkStaff IN (SELECT fkStaff FROM SECURITYSUBQUERY WHERE ResourceName='BLABLA')) )
PROBLEM: It's really slow. Can I do a more efficient way? Can I do another way? Thank you for your help!

I think you should be able to qrite the query thus:
SELECT * FROM x
WHERE #fkStaffID NOT IN (SELECT fkStaff FROM SecuritySubquery WHERE ResourceName= 'BLABLA')
OR #fkStaffID = fkStaff;
So either the #fkStaffID isn't 'BLABLA' or it matches the record's staff ID.
This NOT IN / OR still won't be very fast. Anyway, you should have the following indexes:
CREATE INDEX idx1 ON SecuritySubquery (ResourceName, fkStaff);
CREATE INDEX idx2 ON x (fkStaff);

I would try this:
if exists(select 1 from staff where pkstaff=#fkstaffid)
begin
select * from X where ResourceName = 'Blabla' and fkStaff = #fkStaffId
end
else
begin
select * from X where ResourceName = 'Blabla'
end
If we have a matching record, then we filter by that #fkStaffId, otherwise we select everything.

The below query will give you only the data for people in X who are in the STAFF table with a corresponding record in SECURITYSUBQUERY table ('BlaBla' records).
First, build test data.
IF OBJECT_ID(N'tempdb..#x') IS NOT NULL
DROP TABLE #x
CREATE TABLE #X ( fkStaff int, myStuff varchar(20) )
INSERT INTO #X ( fkStaff, myStuff )
VALUES
(1,'not me')
, (2,'not me')
, (3,'show me')
, (4,'not me')
, (5,'show me too')
IF OBJECT_ID(N'tempdb..#STAFF') IS NOT NULL
DROP TABLE #STAFF
CREATE TABLE #STAFF ( pkStaff int, name varchar(20) )
INSERT INTO #STAFF ( pkStaff, name )
VALUES
(1, 'Joe')
, (2, 'Jim')
, (3, 'Bill')
, (4, 'Ted')
, (5, 'Rufus')
IF OBJECT_ID(N'tempdb..#SECURITYSUBQUERY') IS NOT NULL
DROP TABLE #SECURITYSUBQUERY
CREATE TABLE #SECURITYSUBQUERY ( fkStaff int, ResourceName varchar(20) )
INSERT INTO #SECURITYSUBQUERY ( fkStaff, ResourceName )
VALUES
( 1, 'NotAuth' )
, ( 2, 'NotAuth' )
, ( 3, 'BlaBla' )
, ( 3, 'Extra Perm' )
, ( 4, 'NotAuth' )
, ( 5, 'BlaBla' )
Now for the query.
DECLARE #fkStaffID int ; /* Only 3 or 5 will return records. */
SELECT x.*
FROM #x x
LEFT OUTER JOIN (
SELECT s.pkStaff
FROM #STAFF s
INNER JOIN #SECURITYSUBQUERY ss ON s.pkStaff = ss.fkStaff
AND ss.ResourceName = 'BlaBla'
WHERE s.pkStaff = #fkStaffID
) t ON t.pkStaff = x.fkStaff
WHERE t.pkStaff IS NOT NULL
AND x.fkStaff = #fkStaffID
This will only give results if users Bill or Rufus are logged in (and passed as #fkStaffID).
I don't know how well this will scale, but the optimizer should work faster than EXISTS or NOT IN subqueries. Try it with your data.

Related

How to get records which has more than one entries on another table

An example scenario for my question would be:
How to get all persons who has multiple address types?
Now here's my sample data:
CREATE TABLE #tmp_1 (
ID uniqueidentifier PRIMARY KEY
, FirstName nvarchar(max)
, LastName nvarchar(max)
)
CREATE TABLE #tmp_2 (
SeedID uniqueidentifier PRIMARY KEY
, SomeIrrelevantCol nvarchar(max)
)
CREATE TABLE #tmp_3 (
KeyID uniqueidentifier PRIMARY KEY
, ID uniqueidentifier REFERENCES #tmp_1(ID)
, SeedID uniqueidentifier REFERENCES #tmp_2(SeedID)
, SomeIrrelevantCol nvarchar(max)
)
INSERT INTO #tmp_1
VALUES
('08781F73-A06B-4316-B6A5-802ED58E54BE', 'AAAAAAA', 'aaaaaaa'),
('4EC71FCE-997C-46AA-B119-6C5A2545DDC2', 'BBBBBBB', 'bbbbbbb'),
('B0726ABF-738E-48BC-95CB-091C9D731A0E', 'CCCCCCC', 'ccccccc'),
('6C6CE284-A63C-49D2-B2CC-F25C9CBC8FB8', 'DDDDDDD', 'ddddddd')
INSERT INTO #tmp_2
VALUES
('4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'Value1'),
('4C891FE9-60B6-41BE-A64B-11A9A8B58AB2', 'Value2'),
('6F6EFED6-8EA0-4F70-A63F-6A103D0A71BD', 'Value3')
INSERT INTO #tmp_3
VALUES
(NEWID(), '08781F73-A06B-4316-B6A5-802ED58E54BE', '4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'sdfsdgdfbgcv'),
(NEWID(), '08781F73-A06B-4316-B6A5-802ED58E54BE', '4C891FE9-60B6-41BE-A64B-11A9A8B58AB2', 'asdfadsas'),
(NEWID(), '08781F73-A06B-4316-B6A5-802ED58E54BE', '4C891FE9-60B6-41BE-A64B-11A9A8B58AB2', 'xxxxxeeeeee'),
(NEWID(), '4EC71FCE-997C-46AA-B119-6C5A2545DDC2', '4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'sdfsdfsd'),
(NEWID(), 'B0726ABF-738E-48BC-95CB-091C9D731A0E', '4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'zxczxcz'),
(NEWID(), 'B0726ABF-738E-48BC-95CB-091C9D731A0E', '6F6EFED6-8EA0-4F70-A63F-6A103D0A71BD', 'eerwerwe'),
(NEWID(), '6C6CE284-A63C-49D2-B2CC-F25C9CBC8FB8', '4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'vbcvbcvbcv')
Which gives you:
This is my attempt:
SELECT
t1.*
, Cnt -- not really needed. Just added for visual purposes
FROM #tmp_1 t1
LEFT JOIN (
SELECT
xt.ID
, COUNT(1) Cnt
FROM (
SELECT
#tmp_3.ID
, COUNT(1) as Cnt
FROM #tmp_3
GROUP BY ID, SeedID
) xt
GROUP BY ID
) t2
ON t1.ID = t2.ID
WHERE t2.Cnt > 1
Which gives:
ID FirstName LastName Cnt
B0726ABF-738E-48BC-95CB-091C9D731A0E CCCCCCC ccccccc 2
08781F73-A06B-4316-B6A5-802ED58E54BE AAAAAAA aaaaaaa 2
Although this gives me the correct results, I'm afraid that this query is not the right way to do this performance-wise because of the inner queries. Any input is very much appreciated.
NOTE:
A person can have multiple address of the same address types.
"Person-Address" is not the exact use-case. This is just an example.
The Cnt column is not really needed in the result set.
The way you have named your sample tables and data help little in understanding the problem.
I think you want all IDs which have 2 or more SomeIrrelevantCol values in the last table?
This can be done by:
select * from #tmp_1
where ID in
(
select ID
from #tmp_3
group by ID
having count(distinct SomeIrrelevantCol)>=2
)

Fill in Missing rows using T-SQL

I am trying to fill in missing values. A simple example is table A has 10 rows with Id's of 1 through 10.
Table B has 5 rows of 1,3,5,7,9
I need to use table A to fill in the missing even numbers from Table B.
What is the best way to go about this?
CREATE TABLE #A (
Id INT
,Val VARCHAR(10)
)
CREATE TABLE #B (
Id INT
,Val VARCHAR(10)
)
INSERT INTO #A (
Id
,Val
)
VALUES (
1
,'1'
)
INSERT INTO #A (
Id
,Val
)
VALUES (
2
,'2'
)
INSERT INTO #A (
Id
,Val
)
VALUES (
3
,'3'
)
INSERT INTO #B (
Id
,Val
)
VALUES (
1
,'1'
)
INSERT INTO #B (
Id
,Val
)
SELECT Id
,Val
FROM #A a
WHERE NOT EXISTS (
SELECT NULL
FROM #B b
WHERE b.Id = a.Id
)
AND a.Id % 2 = 0
Select * from #B
Run a select query towards table A with a for loop that compares I’d for each loop. If I’d don’t match execute an insert. If you want to run it inside DB make an SP for it otherwise create a simple console app or such.
You can use INSERT INTO ... SELECT...
INSERT INTO A
SELECT B.ID FROM B
WHERE B.ID % 2 = 0
AND B.ID NOT IN (SELECT ID FROM A)
Just make sure to SELECT the same columns that you are inserting.
You can do using not exists like below :
insert into B
select ID from A
where ID % 2 = 0 and not exists(select 1 from B where ID = A.ID)
SQL HERE

Creating duplicates with a different ID for test in SQL

I have a table with 1000 unique records with one of the field as ID. For testing purpose, my requirement is that To update the last 200 records ID value to the first 200 records ID in the same table. Sequence isn't mandatory.
Appreciate help on this.
Typically I charge for doing other ppls homework, don't forget to cite your source ;)
declare #example as table (
exampleid int identity(1,1) not null
, color nvarchar(255) not null
);
insert into #example (color)
select 'black' union all
select 'green' union all
select 'purple' union all
select 'indigo' union all
select 'yellow' union all
select 'pink';
select *
from #example;
declare #max int = (select max(exampleId) from #example);
declare #min int = #max - 2
;with cte as (
select top 2 color
from #example
)
update #example
set color = a.color
from cte a
where exampleid <= #max and exampleid > #min;
select *
from #example
This script should solve the issue and will cover scenarios even if the id column is not sequential.I have included the comments to help you understand the joins and the flow of the script.
declare #test table
(
ID int not null,
Txt char(1)
)
declare #counter int = 1
/*******This variable is the top n or bottom n records in question it is 200 ,
for test purpose setting it to 20
************/
declare #delta int = 20
while(#counter <= 50)
begin
Insert into #test values(#counter * 5,CHAR(#counter+65))
set #counter = #counter + 1
end
/************Tag the records with a row id as we do not know if ID's are sequential or random ************/
Select ROW_NUMBER() over (order by ID) rownum,* into #tmp from #test
/************Logic to update the data ************/
/*Here we first do a self join on the tmp table with first 20 to last 20
then create another join to the test table based on the ID of the first 20
*/
update t
set t.ID = tid.lastID
--Select t.ID , tid.lastID
from #test t inner join
(
Select f20.rownum as first20 ,l20.rownum as last20,f20.ID as firstID, l20.ID lastID
from #tmp f20
inner join #tmp l20 on (f20.rownum + #delta) = l20.rownum
)tid on tid.firstID = t.ID and tid.first20 < = #delta
Select * from #test
drop table #tmp

Avoid referring table two times in the WHERE clause

Following is a simplified version of my database in SQL Server 2005. I need to select employees based on business units. Each employee has home department, parent department and visiting department. Based on the department, business unit can be found out.
For an employee, if the HomeDeptID = ParentDeptID, then
#SearchBusinessUnitCD should be present for the VisitingDeptID.
If HomeDeptID <> ParentDeptID, then #SearchBusinessUnitCD should be
present for the ParentDeptID.
Following query works fine. But it has scan on the #DepartmentBusinesses table two times. Is there a way to use the table #DepartmentBusinesses only once by making it as a CASE statement or similar?
DECLARE #SearchBusinessUnitCD CHAR(3)
SET #SearchBusinessUnitCD = 'B'
--IF HomeDeptID = ParentDeptID, then #SearchBusinessUnitCD should be present for the VisitingDeptID
--IF HomeDeptID <> ParentDeptID, then #SearchBusinessUnitCD should be present for the ParentDeptID
CREATE TABLE #DepartmentBusinesses (DeptID INT, BusinessUnitCD CHAR(3))
INSERT INTO #DepartmentBusinesses
SELECT 1, 'A' UNION ALL
SELECT 2, 'B'
CREATE NONCLUSTERED INDEX IX_DepartmentBusinesses_DeptIDBusinessUnitCD ON #DepartmentBusinesses (DeptID,BusinessUnitCD)
DECLARE #Employees TABLE (EmpID INT, HomeDeptID INT, ParentDeptID INT, VisitingDeptID INT)
INSERT INTO #Employees
SELECT 1, 1, 1, 2 UNION ALL
SELECT 2, 2, 1, 3
SELECT *
FROM #Employees
WHERE
(
HomeDeptID = ParentDeptID
AND
EXISTS (
SELECT 1
FROM #DepartmentBusinesses
WHERE DeptID = VisitingDeptID
AND BusinessUnitCD = #SearchBusinessUnitCD)
)
OR
(
HomeDeptID <> ParentDeptID
AND
EXISTS (
SELECT 1
FROM #DepartmentBusinesses
WHERE DeptID = ParentDeptID
AND BusinessUnitCD = #SearchBusinessUnitCD
)
)
DROP TABLE #DepartmentBusinesses
Plan
SELECT *
FROM #Employees e
WHERE EXISTS (
SELECT 1
FROM #DepartmentBusinesses t
WHERE t.BusinessUnitCD = #SearchBusinessUnitCD
AND (
(e.HomeDeptID = e.ParentDeptID AND t.DeptID = e.VisitingDeptID)
OR
(e.HomeDeptID != e.ParentDeptID AND t.DeptID = e.ParentDeptID)
)
)
You can give this a try:
SELECT e.*
FROM #Employees AS e
INNER JOIN #DepartmentBusinesses AS d
ON (d.DeptID = e.VisitingDeptID AND e.HomeDeptID = e.ParentDeptID) OR
(d.DeptID = e.ParentDeptID AND e.HomeDeptID <> e.ParentDeptID)
WHERE d.BusinessUnitCD = #SearchBusinessUnitCD

How can I put a Transac SQL block into a function that I can reuse in Stored Procedures?

I have code to take the [Correct] column of a table with a particular QuestionId and make it it to a character string:
Here's the code that was suggested to me. It really just has one input which is the [QuestionUId] and one output which is a string looking like "001110" or "00111" or "111" etc.
-- I need to actually search by QuestionUId so I have this to get the QuestionId
DECLARE #QuestionId int;
SELECT #QuestionId = QuestionID
FROM dbo.question
Where QuestionUId = '87e6bbac-651f-4fdb-862b-412979f71847';
;WITH Partitioned AS (
SELECT
ROW_NUMBER() OVER (PARTITION BY QuestionId ORDER BY AnswerId ASC) AS RowNumber
, COUNT(1) OVER (PARTITION BY QuestionId) AS ColumnCount
, CONVERT(VARCHAR(MAX), Correct) AS Correct
FROM
#Answers
WHERE
[QuestionId] = #QuestionId
),
Concatenated AS (
SELECT RowNumber, ColumnCount, Correct FROM Partitioned WHERE RowNumber = 1
UNION ALL
SELECT
P.RowNumber
, P.ColumnCount
, C.Correct + P.Correct AS Correct
FROM
Partitioned P
INNER JOIN Concatenated C
ON P.RowNumber = C.RowNumber + 1
)
SELECT
CONVERT(VARCHAR(20), Correct) AS Correct
FROM
Concatenated
WHERE
RowNumber = ColumnCount
The code works but I would like to make this code available so it can be used by more than one stored procedure.
Can someone tell me is there a way I can put this code into a function or should I just put it into another stored procedure and if so then how could I do that and how could I call it ?
FYI here's the tables that are used and some sample input and output data:
CREATE TABLE [dbo].[Question] (
[QuestionId] INT,
[QuestionUId] UNIQUEIDENTIFIER
)
CREATE TABLE [dbo].[Answer] (
[AnswerId] INT IDENTITY (1, 1) NOT NULL,
[QuestionId] INT NOT NULL,
[Correct] BIT NULL
);
AnswerId QuestionId Correct >>>>> needed a string "001"
19 8 0
20 8 0
21 8 1
AnswerId QuestionId Correct >>>>> needed a string "10"
22 9 1
23 9 0
As far as I understand your code returns a single varchar value. So you can create scalar function from it. It will look something like that:
CREATE FUNCTION dbo.MyFunc
(
#QuestionId int
)
RETURNS varchar(20)
AS
BEGIN
DECLARE #RetVal varchar(20)
-- I need to actually search by QuestionUId so I have this to get the QuestionId
;WITH Partitioned AS (
SELECT
ROW_NUMBER() OVER (PARTITION BY QuestionId ORDER BY AnswerId ASC) AS RowNumber
, COUNT(1) OVER (PARTITION BY QuestionId) AS ColumnCount
, CONVERT(VARCHAR(MAX), Correct) AS Correct
FROM
#Answers
WHERE
[QuestionId] = #QuestionId
),
Concatenated AS (
SELECT RowNumber, ColumnCount, Correct FROM Partitioned WHERE RowNumber = 1
UNION ALL
SELECT
P.RowNumber
, P.ColumnCount
, C.Correct + P.Correct AS Correct
FROM
Partitioned P
INNER JOIN Concatenated C
ON P.RowNumber = C.RowNumber + 1
)
SELECT
#RetVal = CONVERT(VARCHAR(20), Correct) AS Correct
FROM
Concatenated
WHERE
RowNumber = ColumnCount
RETURN #RetVal
END
Then you call call it from any other T-SQL code (queries, procedures, other functions):
DECLARE #QuestionId int;
SELECT #QuestionId = QuestionID
FROM dbo.question
Where QuestionUId = '87e6bbac-651f-4fdb-862b-412979f71847';
SELECT dbo.MyFunc(#QuestionId)

Resources