Case statement with two cases together - sql-server

I have a scenario where I have multiple occurrences of Emp in a table. I want to return a string Context depending on these conditions:
If for one Emp
Dep_no in (10,11,12) only => 'FTB'
Dep_no in (22,23) only => 'SD'
Dep_no in (10,11,12) and in (22,23) => 'IT'
The 3rd condition can match, because Dep_no can be in these two sets in different records for the same Emp.
How can I implement the third case using a case statement or any other solution in SQL server 2016?
For example an
Employee has Dep_No in 10 and 22 (yellow example), he should be IT
Employee has 10 and/or 11 he should be FTB
Employee has 22 and/or 23 then he should be SD
Data in table look like below

CREATE TABLE Thing (
Emp int NOT NULL,
Dep_No int NOT NULL
)
INSERT INTO Thing (Emp, Dep_No)
VALUES
(123, 10),
(123, 11),
(455, 22),
(455, 23),
(566, 10),
(566, 22)
SELECT e.Emp,
CASE
WHEN ftb.Emp IS NOT NULL AND sd.Emp IS NOT NULL THEN 'IT'
WHEN ftp.Emp IS NOT NULL AND sd.Emp IS NULL THEN 'FTB'
WHEN ftb.Emp IS NULL AND sd.Emp IS NOT NULL THEN 'SD'
ELSE '?'
END AS NewThing
FROM (
-- PK of the result table.
SELECT DISTINCT Emp FROM Thing
) AS e
LEFT JOIN (
-- Lookup case FTB.
SELECT DISTINCT Emp FROM Thing WHERE Dep_No IN (10, 11, 12)
) AS ftb
ON e.Emp = ftb.Emp
LEFT JOIN (
-- Lookup case SD.
SELECT DISTINCT Emp FROM Thing WHERE Dep_No IN (22, 23)
) AS sd
ON e.Emp = sd.Emp

You can solve it with nested SELECTs. The inner SELECT counts the number of times a Dep_no is in one the two sets of numbers. The outer SELECT then returns the desired string based on these counts.
SELECT
Emp,
CASE WHEN g1>0 AND g2>0 THEN 'IT'
WHEN g1>0 THEN 'FTB'
WHEN g2>0 THEN 'SD'
END AS Context
FROM (
SELECT
Emp,
SUM(CASE WHEN Dep_No IN (10, 11, 12) THEN 1 ELSE 0 END) AS g1,
SUM(CASE WHEN Dep_No IN (22, 23) THEN 1 ELSE 0 END) AS g2
FROM myTable
GROUP BY Emp
) g
With this example table
create table myTable (
Emp int,
Dep_No int
);
insert into MyTable (Emp, Dep_No) VALUES (123,10), (123,11), (455, 22), (455, 23), (566, 10), (566, 22);
We get this result
Emp
Context
123
FTB
455
SD
566
IT
See: http://sqlfiddle.com/#!18/339814/3/0

Related

Sql Server - Join and group with table which has more records

I have below 4 tables :
User
Student
Department
Marks
I am trying to fetch Total number of students and sum of total marks by DepartmentId. For ex my result set should say Department Id 1 has 50 students and 1200 total Marks scored by all students in all exams across department.
Below query gives me correct result when i need Total Marks by Department
SELECT DepartmentId, SUM([Value]) AS TotalMarks FROM [dbo].[Marks] M
WHERE CollegeId = 3
GROUP BY DepartmentId
Below query gives correct result when i need only Total number of Students by Department.
SELECT S.[DepartmentId], COUNT(U.[StudentId]) AS TotalStudents
FROM [dbo].User U
INNER JOIN dbo.[Student] S
ON U.[UserId] = S.[UserId]
INNER JOIN [dbo].Department D
ON D.[DepartmentId] = S.[DepartmentID]
WHERE D.[CollegeId] = 3 AND U.[IsFullTimeStudent] = 1
GROUP BY S.[DepartmentId]
Now when i want to get Total Number of Students and Total Marks by Department in a single result using below query i am facing issues. My Marks table can have multiple number of record for single user and for that reason it giving redundant result.
SELECT S.[DepartmentId], COUNT(U.[StudentId]) AS TotalStudents, SUM(M.[Value]) AS TotalMarks
FROM [dbo].User U
INNER JOIN dbo.[Student] S
ON U.[UserId] = S.[UserId]
INNER JOIN [dbo].Department D
ON D.[DepartmentId] = S.[DepartmentID]
INNER JOIN [dbo].[Marks] M
ON D.[DepartmentId] = M.[DeprtmentId]
WHERE D.[CollegeId] = 3 AND U.[IsFullTimeStudent] = 1
GROUP BY S.[DepartmentId]
My marks table has UserId, DepartmentId ,CollegeId, Value fields.
For Ex :- If there are 110 entries for DepartmentId 1 in marks table and 1 Student who is an FTE student then in this case TotalUsers i am getting 110 Total Students though that Department has only 1 student because there are 110 entries in Marks i am getting as 110 Total Students
Is there any simpler way to get this resolved ?
Some sample data and table definitions would be useful. I invented my own sample data, it should almost fit your table definitions.
Using cross apply or outer apply (documentation and examples) allows to combine the count and sum results.
Sample data
create table departments
(
departmentid int,
departmentname nvarchar(20)
);
insert into departments (departmentid, departmentname) values
(1000, 'Business Faculty'),
(2000, 'Science Faculty' ),
(3000, 'Maintenance' );
create table users
(
departmentid int,
userid int,
username nvarchar(10),
isfulltimestudent bit
);
insert into users (departmentid, userid, username, isfulltimestudent) values
(1000, 1, 'Alice', 1),
(1000, 2, 'Bob', 0),
(2000, 3, 'Clarence', 1),
(2000, 4, 'Britt', 0);
create table students
(
userid int,
studentid int
);
insert into students (userid, studentid) values
(1, 100),
(2, 200),
(3, 300);
create table marks
(
departmentid int,
userid int,
mark int
);
insert into marks (departmentid, userid, mark) values
(1000, 1, 15),
(1000, 1, 8),
(1000, 2, 13),
(1000, 2, 12),
(2000, 3, 10),
(2000, 3, 7),
(2000, 3, 15),
(2000, 4, 10);
Solution
select d.departmentname,
ts.TotalStudents,
tm.TotalMarks
from departments d
outer apply ( select count(1) as TotalStudents
from users u
where u.departmentid = d.departmentid
and u.isfulltimestudent = 1 ) ts
outer apply ( select sum(m.mark) as TotalMarks
from marks m
where m.departmentid = d.departmentid ) tm;
Fiddle to see it in action.
Applied solution
Untested query that merges the queries from your question:
SELECT d.DepartmentId,
tm.TotalMarks,
ts.TotalStudents
FROM dbo.Department d
OUTER APPLY ( SELECT SUM(m.[Value]) AS TotalMarks
FROM dbo.Marks m
WHERE m.DepartmentId = d.DepartmentId ) tm
OUTER APPLY ( SELECT COUNT(u.StudentId) AS TotalStudents
FROM dbo.User u
JOIN dbo.Student s
ON u.UserId = s.UserId
WHERE u.IsFullTimeStudent = 1
AND s.DepartmentId = d.DepartmentId ) ts
WHERE d.CollegeId = 3;

Can self referencing table pivot to show family lineage for every child with generation count giving rise to the names of each column?

I have a self-referencing table of parents and children and I have written a recursive CTE so I now have a list of parent-child relationships with their depths against them i.e. which generation they are in.
Is it now possible to pivot this to show great-grandparents' Ids in the left column, then grandparents in the next column, then parents, then children etc. with their respective generations as the column headings please?
e.g. with the data you see I'm inserting into my temp table, can I get this please?
Gen0 Gen1 Gen2 Gen3 Gen4
1 2 3 4 5
10 20 100 1000
10 20 200
10 30
10 40
create table [#Data] ([ParentId] int, [ChildId] int)
insert [#Data] values
(1, 2)
, (2, 3)
, (3, 4)
, (4, 5 )
, (10, 20)
, (10, 30)
, (10, 40)
, (20, 100)
, (20, 200)
, (200, 1000)
;with [CTE] AS
(
select [A].[ParentId], [A].[ChildId], 1 as [Generation]
from [#Data] [A]
left join [#Data] [B]
on [A].[ParentId] = [B].[ChildId]
where [B].[ChildId] is null
union all
select [D].[ParentId], [D].[ChildId], [Generation] + 1
from [CTE] [C]
join [#Data] [D]
on [C].[ChildId] = [D].[ParentId]
)
select * from [CTE] order by [ParentId], [ChildId]
I am using SQL 2017.
Many thanks for looking.
You can use something like this to query your CTE:
;with [CTE] AS
(/*your code here*/)
select
g1.[ParentId] as Gen0
, coalesce(g1.[ChildId], g2.[ParentId]) as Gen1
, coalesce(g2.[ChildId], g3.[ParentId]) as Gen2
, coalesce(g3.[ChildId], g4.[ParentId]) as Gen3
, g4.[ChildId] as Gen4
from
[CTE] as g1
left join [CTE] as g2 on g1.[ChildId] = g2.[ParentId]
left join [CTE] as g3 on g2.[ChildId] = g3.[ParentId]
left join [CTE] as g4 on g3.[ChildId] = g4.[ParentId]
where g1.[Generation] = 1
Result:
See the complete code here. This is a static solution, if you want something that works with an arbitrary number of generations, you'll have to transform this code in dynamic TSQL.
P.S. I think there is probably a typo in your expected results: in Gen3 column 1000 should be on the third row, not on the second since 1000 is child of 200, not of 100

How can I filter records in nested queries and then select other columns in the same table from the filtered results?

First, I want to filter and only select OBJECTIDS in two nested queries (based off of user entered parameters, #Borough and #WOEntity) and then join columns (Type and Priority) from the same table back to the most refined set of records. The purpose of this is to improve the performance of stored procedures that sit behind an SSRS report.
I'm getting these error messages:
Msg 156, Level 15, State 1, Line 13
Incorrect syntax near the keyword 'SELECT'
Msg 102, Level 15, State 1, Line 36
Incorrect syntax near ')'
My query looks like this so far:
declare #Borough int
declare #WOEntityINT int = NULL --0, 1, 3, 4, 11, 10, NULL
set #Borough = 1
set #WOEntityINT = 1
SELECT
B.OBJECTID, WO.Type, WO.Priority
FROM
CFAdmin.WorkOrder_EVW as WO
WHERE
B.OBJECTID = WO.OBJECTID
(
SELECT A.ObjectID
FROM
(SELECT
ObjectID,
CASE
WHEN WOEntity = 0 THEN 0 -- In-House
WHEN WOEntity IN (1, 2) THEN 1 -- Contract
WHEN WOEntity IN (3, 4) THEN 3 -- Utility
WHEN WOEntity IN (5, 6) THEN 4 -- Permitted
WHEN WOEntity IS NULL THEN 10 -- No Entity
ELSE 11 --11 = Other
END AS WOEntityINT
FROM
CFAdmin.WorkOrder_EVW
WHERE
(Status NOT IN (1, 2)) AND
(Borough IN (#Borough))) AS A
WHERE
(#WOEntityINT IS NULL OR (WOEntityINT = #WOEntityINT)) --field WOEntityINT is a derived field
) AS B
Your query must match with pattern: SELECT FROM WHERE. I mean, you can reorder clauses:
SELECT B.OBJECTID, WO.Type, WO.Priority
FROM CFAdmin.WorkOrder_EVW as WO
INNER JOIN (
SELECT A.ObjectID
FROM
(
SELECT
ObjectID,
CASE
WHEN WOEntity = 0 THEN 0 -- In-House
WHEN WOEntity IN (1,2) THEN 1 -- Contract
WHEN WOEntity IN (3,4) THEN 3 -- Utility
WHEN WOEntity IN (5,6) THEN 4 -- Permitted
WHEN WOEntity IS NULL THEN 10 -- No Entity
ELSE 11 --11 = Other
END AS WOEntityINT
FROM CFAdmin.WorkOrder_EVW
WHERE
(Status not in (1,2)) AND
(Borough IN (#Borough))
) AS A
WHERE (#WOEntityINT IS NULL OR (WOEntityINT = #WOEntityINT)) --field
WOEntityINT is a derived field
) AS B
on B.OBJECTID = WO.OBJECTID
Remember you can write CTE to improve readability:
WITH a AS
(
SELECT objectid,
CASE
WHEN woentity = 0 THEN 0 -- In-House
WHEN woentity IN (1,2) THEN 1 -- Contract
WHEN woentity IN (3,4) THEN 3 -- Utility
WHEN woentity IN (5,6) THEN 4 -- Permitted
WHEN woentity IS NULL THEN 10 -- No Entity
ELSE 11 --11 = Other
END AS woentityint
FROM cfadmin.workorder_evw
WHERE (
status NOT IN (1,2))
AND (
borough IN (#Borough)) )
, b AS
(
SELECT a.objectid
FROM a
WHERE (
#WOEntityINT IS NULL
OR (
woentityint = #WOEntityINT)) --field
woentityint IS a derived field
)
SELECT b.objectid,
wo.type,
wo.priority
FROM cfadmin.workorder_evw AS wo
INNER JOIN b
ON b.objectid = wo.objectid
Disclaimer: This is just a suggestion to start to debug and isolate your problem. I guess, after this changes, you will found new issues. Please, don't ask for new issues in this post.

SQL Server CTE left outer join

I have 2 tables in SQL Server 2008, customertest with columns customer id (cid) and it's boss id (upid), and conftest with cid, confname, confvalue
customertest schema and data:
conftest schema and data:
I want to know how to design a CTE that if cid in conftest doesn't have that confname's confvalue, it will keep searching upid and till find a upper line which have confname and confvalue.
For example , I want to get value of 100 if I search for cid=4 (this is normal case). And I want to get value of 200 if I search for cid=7 or 8.
And if cid7 and cid8 have child node , it will all return 200 (of cid5) if I search using this CTE.
I don't have a clue how to do this , I think maybe can use CTE and some left outer join, please give me some example ?? Thanks a lot.
If it's unknown how many levels there are in the hierarchy?
Then such challenge is often done via a Recursive CTE.
Example Snippet:
--
-- Using table variables for testing reasons
--
declare #customertest table (cid int primary key, upid int);
declare #conftest table (cid int, confname varchar(6) default 'budget', confvalue int);
--
-- Sample data
--
insert into #customertest (cid, upid) values
(1,0), (2,1), (3,1), (4,2), (5,2), (6,3),
(7,5), (8,5), (9,8), (10,9);
insert into #conftest (cid, confvalue) values
(1,1000), (2,700), (3,300), (4,100), (5,200), (6,300);
-- The customer that has his own budget, or not.
declare #customerID int = 10;
;with RCTE AS
(
--
-- the recursive CTE starts from here. The seed records, as one could call it.
--
select cup.cid as orig_cid, 0 as lvl, cup.cid, cup.upid, budget.confvalue
from #customertest as cup
left join #conftest budget on (budget.cid = cup.cid and budget.confname = 'budget')
where cup.cid = #customerID -- This is where we limit on the customer
union all
--
-- This is where the Recursive CTE loops till it finds nothing new
--
select RCTE.orig_cid, RCTE.lvl+1, cup.cid, cup.upid, budget.confvalue
from RCTE
join #customertest as cup on (cup.cid = RCTE.upid)
outer apply (select b.confvalue from #conftest b where b.cid = cup.cid and b.confname = 'budget') as budget
where RCTE.confvalue is null -- Loop till a budget is found
)
select
orig_cid as cid,
confvalue
from RCTE
where confvalue is not null;
Result :
cid confvalue
--- ---------
10 200
Btw, the Recursive CTE uses the OUTER APPLY because MS SQL Server doesn't allow a LEFT OUTER JOIN to be used there.
And if it's certain that there's maximum 1 level depth for the upid with a budget?
Then just simple left joins and a coalesce would do.
For example:
select cup.cid, coalesce(cBudget.confvalue, upBudget.confvalue) as confvalue
from #customertest as cup
left join #conftest cBudget on (cBudget.cid = cup.cid and cBudget.confname = 'budget')
left join #conftest upBudget on (upBudget.cid = cup.upid and upBudget.confname = 'budget')
where cup.cid = 8;
I don't think you are looking for a CTE to do that, from what I understand:
CREATE TABLE CustomerTest(
CID INT,
UPID INT
);
CREATE TABLE ConfTest(
CID INT,
ConfName VARCHAR(45),
ConfValue INT
);
INSERT INTO CustomerTest VALUES
(1, 0),
(2, 1),
(3, 1),
(4, 2),
(5, 2),
(6, 3),
(7, 5),
(8, 5);
INSERT INTO ConfTest VALUES
(1, 'Budget', 1000),
(2, 'Budget', 700),
(3, 'Budget', 300),
(4, 'Budget', 100),
(5, 'Budget', 200),
(6, 'Budget', 300);
SELECT MAX(CNT.CID) AS CID,
CNT.ConfName,
MIN(CNT.ConfValue) AS ConfValue
FROM ConfTest CNT INNER JOIN CustomerTest CMT ON CMT.CID = CNT.CID
OR CMT.UPID = CNT.CID
WHERE CMT.CID = 7 -- You can test for values (8, 4) or any value you want :)
GROUP BY
CNT.ConfName;

CTE Recursion to get tree hierarchy

I need to get an ordered hierarchy of a tree, in a specific way. The table in question looks a bit like this (all ID fields are uniqueidentifiers, I've simplified the data for sake of example):
EstimateItemID EstimateID ParentEstimateItemID ItemType
-------------- ---------- -------------------- --------
1 A NULL product
2 A 1 product
3 A 2 service
4 A NULL product
5 A 4 product
6 A 5 service
7 A 1 service
8 A 4 product
Graphical view of the tree structure (* denotes 'service'):
A
___/ \___
/ \
1 4
/ \ / \
2 7* 5 8
/ /
3* 6*
Using this query, I can get the hierarchy (just pretend 'A' is a uniqueidentifier, I know it isn't in real life):
DECLARE #EstimateID uniqueidentifier
SELECT #EstimateID = 'A'
;WITH temp as(
SELECT * FROM EstimateItem
WHERE EstimateID = #EstimateID
UNION ALL
SELECT ei.* FROM EstimateItem ei
INNER JOIN temp x ON ei.ParentEstimateItemID = x.EstimateItemID
)
SELECT * FROM temp
This gives me the children of EstimateID 'A', but in the order that it appears in the table. ie:
EstimateItemID
--------------
1
2
3
4
5
6
7
8
Unfortunately, what I need is an ordered hierarchy with a result set that follows the following constraints:
1. each branch must be grouped
2. records with ItemType 'product' and parent are the top node
3. records with ItemType 'product' and non-NULL parent grouped after top node
4. records with ItemType 'service' are bottom node of a branch
So, the order that I need the results, in this example, is:
EstimateItemID
--------------
1
2
3
7
4
5
8
6
What do I need to add to my query to accomplish this?
Try this:
;WITH items AS (
SELECT EstimateItemID, ItemType
, 0 AS Level
, CAST(EstimateItemID AS VARCHAR(255)) AS Path
FROM EstimateItem
WHERE ParentEstimateItemID IS NULL AND EstimateID = #EstimateID
UNION ALL
SELECT i.EstimateItemID, i.ItemType
, Level + 1
, CAST(Path + '.' + CAST(i.EstimateItemID AS VARCHAR(255)) AS VARCHAR(255))
FROM EstimateItem i
INNER JOIN items itms ON itms.EstimateItemID = i.ParentEstimateItemID
)
SELECT * FROM items ORDER BY Path
With Path - rows a sorted by parents nodes
If you want sort childnodes by ItemType for each level, than you can play with Level and SUBSTRING of Pathcolumn....
Here SQLFiddle with sample of data
This is an add-on to Fabio's great idea from above. Like I said in my reply to his original post. I have re-posted his idea using more common data, table name, and fields to make it easier for others to follow.
Thank you Fabio! Great name by the way.
First some data to work with:
CREATE TABLE tblLocations (ID INT IDENTITY(1,1), Code VARCHAR(1), ParentID INT, Name VARCHAR(20));
INSERT INTO tblLocations (Code, ParentID, Name) VALUES
('A', NULL, 'West'),
('A', 1, 'WA'),
('A', 2, 'Seattle'),
('A', NULL, 'East'),
('A', 4, 'NY'),
('A', 5, 'New York'),
('A', 1, 'NV'),
('A', 7, 'Las Vegas'),
('A', 2, 'Vancouver'),
('A', 4, 'FL'),
('A', 5, 'Buffalo'),
('A', 1, 'CA'),
('A', 10, 'Miami'),
('A', 12, 'Los Angeles'),
('A', 7, 'Reno'),
('A', 12, 'San Francisco'),
('A', 10, 'Orlando'),
('A', 12, 'Sacramento');
Now the recursive query:
-- Note: The 'Code' field isn't used, but you could add it to display more info.
;WITH MyCTE AS (
SELECT ID, Name, 0 AS TreeLevel, CAST(ID AS VARCHAR(255)) AS TreePath
FROM tblLocations T1
WHERE ParentID IS NULL
UNION ALL
SELECT T2.ID, T2.Name, TreeLevel + 1, CAST(TreePath + '.' + CAST(T2.ID AS VARCHAR(255)) AS VARCHAR(255)) AS TreePath
FROM tblLocations T2
INNER JOIN MyCTE itms ON itms.ID = T2.ParentID
)
-- Note: The 'replicate' function is not needed. Added it to give a visual of the results.
SELECT ID, Replicate('.', TreeLevel * 4)+Name 'Name', TreeLevel, TreePath
FROM MyCTE
ORDER BY TreePath;
I believe that you need to add the following to the results of your CTE...
BranchID = some kind of identifier that uniquely identifies the branch. Forgive me for not being more specific, but I'm not sure what identifies a branch for your needs. Your example shows a binary tree in which all branches flow back to the root.
ItemTypeID where (for example) 0 = Product and 1 = service.
Parent = identifies the parent.
If those exist in the output, I think you should be able to use the output from your query as either another CTE or as the FROM clause in a query. Order by BranchID, ItemTypeID, Parent.

Resources