how to change effectively the In clause in SQL - sql-server

we have a employee_master table and a Report_Filter Table, the Report_Filter contains different typs of filter the user enters for eg. status, employee code, nationaality etc. and this report_filter contains the user Information also. The table structure is like this Report_Filter( User_Id, Report_TYpe, Report_Code ) all are character fields. based on the users input the table will have values like
( 'User_1', 'STATUS', '01' )
( 'user_1', 'STATUS', '02' )
( 'User_1', 'EMP_CODE', 'ABC' )
( 'User_1', 'NATIONALITY', 'ALL' ) -- All means he want to see all the nationalities
now currently we are writing the query like this
Select Emp_code, Emp_Name, status, nationlity
From Empolyee_Master m
Where
('All' in ( select report_code from Report_Filter where user_id = #user_id and report_type='STATUS') or m.STATUS in ( select report_code from Report_Filter where user_id = #user_id and report_type='STATUS') ) and
or m.CATEGORY in ( select report_code from Report_Filter where user_id = #user_id and report_type='NATIONALITY') ) and
or m.emp_code in ( select report_code from Report_Filter where user_id = #user_id and report_type='EMP_CODE') )
there are other conditions also we are using, the problem is its take too much time because of the In clauses. how can we effectively change this code for improving performance
we are using MSSQL 2014

Instead of retrieving all values and then seeing if your value is part of it, you should filter by your value. Try if this helps (just one example):
replace
WHERE 'ALL' in (
select report_code from Report_Filter
where user_id = #user_id and report_type='STATUS'
)
with
WHERE EXISTS (
select 1 from Report_Filter
where user_id = #user_id and report_type='STATUS'
and report_code = 'ALL'
)
Now it would also help to include column report_code in an INDEX (you are using INDEXes, I hope?)
Update - to skip filtering in case of 'ALL', that can be done like this:
DECLARE #statusFilter VARCHAR(20) = ... -- 'ALL' or other value
SELECT ...
WHERE #statusFilter = 'ALL'
OR EXISTS (
select 1 from Report_Filter
where user_id = #user_id and report_type='STATUS'
and report_code = #statusFilter
)

You can try to do the filtering in advance:
(For your next question: Such a mockup should be done by you)
DECLARE #ReportFilter TABLE([user_id] VARCHAR(100),report_type VARCHAR(100),report_code VARCHAR(100));
INSERT INTO #ReportFilter VALUES
( 'User_1', 'STATUS', '01' )
,( 'user_1', 'STATUS', '02' )
,( 'User_1', 'EMP_CODE', 'ABC' )
,( 'User_1', 'NATIONALITY', 'ALL' );
DECLARE #Employee_Master TABLE(Emp_Code VARCHAR(100),Emp_Name VARCHAR(100), [status] VARCHAR(100), nationality VARCHAR(100));
INSERT INTO #Employee_Master VALUES
('ABC','test1','01','USA')
,('DEF','test2','99','GB')
,('ABC','test3','02','DE')
,('XYZ','test4','01','FRA');
--This is part of your query
DECLARE #user_id VARCHAR(100)='User_1';
WITH filterSTATUS AS
(
SELECT * FROM #ReportFilter WHERE [user_id]=#user_id AND report_type='STATUS'
AND NOT EXISTS(SELECT 1 FROM #ReportFilter WHERE [user_id]=#user_id AND report_type='STATUS' AND report_code='ALL')
)
,
filterEMPCODE AS
(
SELECT * FROM #ReportFilter WHERE [user_id]=#user_id AND report_type='EMP_CODE'
AND NOT EXISTS(SELECT 1 FROM #ReportFilter WHERE [user_id]=#user_id AND report_type='EMP_CODE' AND report_code='ALL')
)
,filterNATIONALITY AS
(
SELECT * FROM #ReportFilter WHERE [user_id]=#user_id AND report_type='NATIONALITY'
AND NOT EXISTS(SELECT 1 FROM #ReportFilter WHERE [user_id]=#user_id AND report_type='NATIONALITY' AND report_code='ALL')
)
SELECT *
FROM #Employee_Master AS em
WHERE (SELECT COUNT(*) FROM filterSTATUS)=0 OR em.[status] IN (SELECT x.report_code FROM filterSTATUS AS x)
AND (SELECT COUNT(*) FROM filterEMPCODE)=0 OR em.Emp_Code IN (SELECT x.report_code FROM filterEMPCODE AS x)
AND (SELECT COUNT(*) FROM filterNATIONALITY)=0 OR em.nationality IN (SELECT x.report_code FROM filterNATIONALITY AS x);
For the give set this returns
Emp_Code Emp_Name status nationality
ABC test1 01 USA
ABC test3 02 DE

Related

SQL: If exists, limit user. If not exists show everything

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.

How to SET Default value in SQL Server case statement?

I have a scenario like to display Status and Count. Look at the below model
Status Counts
--------------- --------
Completed 10
In Progress 6
Others 0
I have this code:
SELECT
CASE
WHEN Status = 'I' THEN 'IN PROGRESS'
WHEN Status = 'O' THEN 'Others'
WHEN Status = 'C' THEN 'COMPLETED'
END AS ' Status',
COUNT(Status) AS ' Counts'
FROM
table1
I have values for only Completed and InProgress. Currently I am getting only two rows. But I need three rows including Others and it's count should be 0 - how to do it in SQL?
Add an "else", like this:
SELECT CASE
WHEN Status= 'I' THEN 'IN PROGRESS'
WHEN Status= 'C' THEN 'COMPLETED'
ELSE 'Others'
END AS ' Status'
FROM table1
From my understanding you have a main table with status values in it, which I'll simplify to this:
CREATE TABLE #MainTable
(
id INT,
[status] NVARCHAR(1)
)
Your problem is that if there are no rows with the status O for Others, you're not getting a 0 when you group the rows to get counts.
I suggest you create a Status table to link to with a RIGHT JOIN. This will also get rid of the need for your CASE statements.
Full solution:
CREATE TABLE #MainTable
(
id INT ,
[status] NVARCHAR(1)
);
INSERT INTO #MainTable
( id, [status] )
VALUES ( 1, 'I' ),
( 2, 'I' ),
( 3, 'I' ),
( 4, 'I' ),
( 5, 'C' ),
( 6, 'C' );
CREATE TABLE #status
(
[status] NVARCHAR(1) ,
[statusText] NVARCHAR(15)
);
INSERT INTO #status
( status, statusText )
VALUES ( 'I', 'In Progress' ),
( 'C', 'Completed' ),
( 'O', 'Others' );
SELECT s.statusText ,
COUNT(t.[status]) StatusCount
FROM #MainTable t
RIGHT JOIN #status s ON s.status = t.status
GROUP BY s.statusText;
DROP TABLE #MainTable;
DROP TABLE #status;
Produces:
statusText StatusCount
Completed 2
In Progress 4
Others 0
In the above example it joins on the I,O,C string values, which I would suggest that you replace with ID values. Then you could do this:
CREATE TABLE #MainTable
(
id INT ,
statusId INT
);
CREATE TABLE #status
(
statusId INT ,
statusShort NVARCHAR(1) ,
statusText NVARCHAR(15)
);
SELECT t.id ,
t.statusId ,
s.statusId ,
s.statusShort ,
s.statusText
FROM #MainTable t
RIGHT JOIN #status s ON s.statusId = t.statusId
DROP TABLE #MainTable;
DROP TABLE #status;

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

Insert into table if record does not already exist

For some reason, this is giving me the "cannot insert duplicate record into table" error.
INSERT INTO [DMS].[dbo].[Deductions]
(
CustomerID,
DeductionCode,
DeductionDescription
)
SELECT b.CustomerID,
b.AdjustmentReason,
b.AdjustmentReason
FROM #CreditDebitAdjustmentDetail b
WHERE NOT EXISTS ( SELECT 1
FROM [DMS].[dbo].[Deductions]
WHERE CustomerID = b.CustomerID
AND DeductionCode = b.AdjustmentReason )
The weird thing is, I tested it as such:
DECLARE #CreditDebitAdjustmentDetail TABLE
(
CustomerID INT,
AdjustmentReason VARCHAR(50)
)
INSERT INTO #CreditDebitAdjustmentDetail
(CustomerID, AdjustmentReason)
VALUES (143, -- CustomerID - int
'024' -- AdjustmentReason - varchar(50)
)
INSERT INTO [DMS].[dbo].[Deductions]
(
CustomerID,
DeductionCode,
DeductionDescription
)
SELECT b.CustomerID,
b.AdjustmentReason,
b.AdjustmentReason
FROM #CreditDebitAdjustmentDetail b
WHERE NOT EXISTS ( SELECT 1
FROM [DMS].[dbo].[Deductions]
WHERE CustomerID = b.CustomerID
AND DeductionCode = b.AdjustmentReason )
And it DOES NOT insert into the table because the record already exists.
Am I missing something here?
EDIT - I thought I had fixed it by doing this but I'm still getting the same error:
INSERT INTO [DMS].[dbo].[Deductions]
(
CustomerID,
DeductionCode,
DeductionDescription
)
SELECT a.CustomerID,
a.AdjustmentReason,
a.AdjustmentReason
FROM #CreditDebitAdjustmentDetail a
WHERE NOT EXISTS ( SELECT 1
FROM [DMS].[dbo].[Deductions] b
WHERE a.CustomerID = b.CustomerID
AND a.AdjustmentReason = b.DeductionCode )
I figured it out, DOH!
KEYWORD ... DISTINCT -_-
INSERT INTO [DMS].[dbo].[Deductions]
(
CustomerID,
DeductionCode,
DeductionDescription
)
SELECT DISTINCT
a.CustomerID,
ISNULL(a.AdjustmentReason, 'UNKNOWN'),
ISNULL(a.AdjustmentReason, 'UNKNOWN')
FROM #CreditDebitAdjustmentDetail a
WHERE NOT EXISTS ( SELECT 1
FROM [DMS].[dbo].[Deductions] b
WHERE a.CustomerID = b.CustomerID
AND CASE a.AdjustmentReason
WHEN NULL THEN 'UNKNOWN'
WHEN '' THEN 'UNKNOWN'
END = b.DeductionCode )

t-sql: dynamically filter XML on multiple conditions?

I'm trying to find a way to do a accept/reject on an XML string, by joining it to a table of conditions. I have one "filter" working now, but want to write it so that it can filter 2 or more.
Here's code that matches one of the two. If either matches, it will filter the string.
What I want to do is make it so it has to match BOTH, while still leaving the option for single-condition
CREATE TABLE #filter (exclusion_type CHAR(1), excluded_value varchar(10))
INSERT INTO #filter VALUES ('B','boy')
INSERT INTO #filter VALUES ('C','cat')
DECLARE #data XML
SELECT #data = '<A><B>boy</B><C>cat</C></A>'
SELECT * FROM (SELECT CONVERT(VARCHAR(128),node.query('fn:local-name(.)')) AS NodeName, CONVERT(VARCHAR(MAX),node.query('./text()')) AS NodeValue
FROM #data.nodes(N'//*') T(node))xml_shred
IF NOT EXISTS
(SELECT * FROM (SELECT CONVERT(VARCHAR(128),node.query('fn:local-name(.)')) AS NodeName, CONVERT(VARCHAR(MAX),node.query('./text()')) AS NodeValue
FROM #data.nodes(N'//*') T(node)) xml_shred
INNER JOIN #filter
ON (nodename = exclusion_type AND nodevalue LIKE excluded_value)
)
select 'record would be inserted '
ELSE select 'record was filtered'
Here's how I currently have it to filter both. Ugly and non-expandable.
IF NOT EXISTS
(SELECT * FROM (SELECT CONVERT(VARCHAR(128),node.query('fn:local-name(.)')) AS NodeName, CONVERT(VARCHAR(MAX),node.query('./text()')) AS NodeValue
FROM #data.nodes(N'//*') T(node)) xml_shred
INNER JOIN #filter
ON (nodename = exclusion_type AND nodevalue LIKE excluded_value)
)
--combination filters don't easily work within that xml_shred
and not(
#data.value('(/A/B)[1]', 'varchar(128)') = 'boy'
AND
#data.value('(/A/C)[1]', 'varchar(128)')='cat'
)
select 'record would be inserted '
ELSE select 'record was filtered'
My only other ideas:
some sort of GUID that would link records in the #filter table together, and then inner join on a GROUP BY of #filtertable, grouping by the GUID and using the SUM to match the number of records.
use semicolons to split the #filter rows, then use a CTE or something to fake a hierarchy and work from there.
Code changes made by Mikael's suggestion
CREATE TABLE #filter
(
exclusion_set SMALLINT,
exclusion_type CHAR(1) ,
excluded_value VARCHAR(10)
)
INSERT INTO #filter
VALUES (1, 'B', 'boy')
INSERT INTO #filter
VALUES (1, 'C', 'cat')
INSERT INTO #filter
VALUES (2, 'D', 'dog' )
DECLARE #data XML
SELECT #data = '<A><B>boy</B><C>cat</C></A>'
IF NOT EXISTS(
SELECT * FROM
(
select COUNT(*) AS match_count, exclusion_set
from #filter as F
where exists (
select *
from (
select X.N.value('local-name(.)', 'varchar(128)') as NodeName,
X.N.value('./text()[1]', 'varchar(max)') as NodeValue
from #data.nodes('//*') as X(N)
) T
where T.NodeName = F.exclusion_type and
T.NodeValue like F.excluded_value
)
GROUP BY exclusion_set
) matches_per_set
INNER JOIN
(SELECT COUNT(*) AS total_count, exclusion_set FROM #filter GROUP BY exclusion_set) grouped_set
ON match_count = total_count
AND grouped_set.exclusion_set = matches_per_set.exclusion_set
)
if not exists (
select *
from #filter as F
where exists (
select *
from (
select X.N.value('local-name(.)', 'varchar(128)') as NodeName,
X.N.value('./text()[1]', 'varchar(max)') as NodeValue
from #data.nodes('//*') as X(N)
) T
where T.NodeName = F.exclusion_type and
T.NodeValue like F.excluded_value
)
having count(*) = (select count(*) from #filter)
)
select 'record would be inserted '
else
select 'record was filtered'
Since I apparently get dinged if I don't mark something as the answer, I'm including mine from above. Many thanks for the help to Mikael Eriksson. His XML shred is faster than mine, and by adding the "exclusion_set" field (char(2) to make it obvious that it wasn't an IDENTITY or primary key), I can do multiple checks. If all conditions in a set match, then the record is filtered.
CREATE TABLE #filter
(
exclusion_set CHAR(2),
exclusion_type CHAR(1) ,
excluded_value VARCHAR(10)
)
INSERT INTO #filter
VALUES ('aa', 'B', 'boy')
INSERT INTO #filter
VALUES ('aa', 'C', 'cat')
INSERT INTO #filter
VALUES ('ab', 'D', 'dog' )
DECLARE #data XML
SELECT #data = '<A><B>boy</B><C>cat</C></A>'
IF NOT EXISTS(
SELECT * FROM
(
select COUNT(*) AS match_count, exclusion_set
from #filter as F
where exists (
select *
from (
select X.N.value('local-name(.)', 'varchar(128)') as NodeName,
X.N.value('./text()[1]', 'varchar(max)') as NodeValue
from #data.nodes('//*') as X(N)
) T
where T.NodeName = F.exclusion_type and
T.NodeValue like F.excluded_value
)
GROUP BY exclusion_set
) matches_per_set
INNER JOIN
(SELECT COUNT(*) AS total_count, exclusion_set FROM #filter GROUP BY exclusion_set) grouped_set
ON match_count = total_count
AND grouped_set.exclusion_set = matches_per_set.exclusion_set
)
select 'record would be inserted '
else
select 'record was filtered'

Resources