SQL View with count from multiple tables - sql

I have a nodejs app with SQL Server. I want to be able to have a View where I can get a tally of number of users, projects, tasks for multiple organization. Let's say I have 4 tables as follows:
TABLES:
Organization: orgId(PK), orgName
Project: projId(PK), orgId(FK), projName
Tasks: taskId(PK), projId(FK), taskName
Users: userId(PK), orgId(FK), userName
VIEW:
OrganizationStats: numberOfProjects, numberOfUsers, numberOfTasks orgId(FK)
It was suggested to use something like this:
CREATE VIEW dbo.vw_OrganisationStats AS
SELECT {Columns from OrganizationStats},
P.Projects AS NumProjects
FROM dbo.OrganizationStats OS
CROSS APPLY (SELECT COUNT(*) AS Projects
FROM dbo.Projects P
WHERE P.OrgID = OS.OrgID) P;
My problem is I am having problem adding the count for Tasks and Users in addition to the Project within the same View. Any help would be appreciate it.
**Sample Data:**
* **Organization**: orgId(PK), orgName
1, ACME Inc.
2, Walmart Inc.
3, YoYo Inc.
* **Project**: projId(PK), orgId(FK), projName
1, 1, ACME Project 1
2, 1, ACME Project 2
3, 2, Walmart Project 1
4, 2, Walmart Project 2
5, 2, Walmart Project 3
* **Tasks**: taskId(PK), projId(FK), taskName
1, 1, Task 1 for Acme Project 1
2, 1, Task 2 for Acme Project 1
3, 4, Task 1 for Walmart Project 2
* **Users**: userId(PK), orgId(FK), userName
1, 1, Bob
2, 1, Alex
3, 1, Jim
4, 2, John
Expected Result
VIEW:
* **OrganizationStats**: numberOfProjects, numberOfUsers, numberOfTasks orgId(FK)
2, 3, 2, 1
3, 1, 1, 2
0, 0 ,0, 3

Consider:
select o.orgid,
count(distinct p.projid) as cnt_projects,
count(distinct u.userid) as cnt_users,
count(*) cnt_tasks
from organization o
inner join projects p on p.orgid = o.orgid
inner join users u on u.orgid = o.orgid
inner join tasks t on t.projid = p.projid and t.userid = u.userid
group by o.orgid

SELECT O.orgId
,COUNT(DISTINCT U.userId) AS 'numberOfUsers'
,COUNT(DISTINCT P.projId) AS 'numberOfProjects'
,COUNT(DISTINCT T.taskId) AS 'numberOfTasks'
FROM Organization O
LEFT JOIN Users U ON O.orgId = U.orgId
LEFT JOIN Project P ON O.orgId = P.orgId
LEFT JOIN Tasks T ON P.projId = T.projId
GROUP BY O.orgId
SCRIPT USED:
CREATE TABLE Organization (orgId INT , orgName VARCHAR(20))
CREATE TABLE Project(projId INT, orgId INT, projName VARCHAR(20))
CREATE TABLE Tasks (taskId INT, projId INT, taskName VARCHAR(20))
CREATE TABLE Users (userId INT, orgId INT, userName VARCHAR(20))
INSERT INTO Organization VALUES (1, 'ORG 1'), (2, 'ORG 2')
INSERT INTO Organization VALUES (3, 'ORG 3')
INSERT INTO Project VALUES (1,1, 'PRO 11'), (2,2, 'PRO 21'), (3,2, 'PRO 22')
INSERT INTO Tasks VALUES (1, 1, 'TASK 11'), (2, 1, 'TASK 21'), (3, 2, 'TASK 32'), (4, 2, 'TASK 42'), (5, 2, 'TASK 52')
INSERT INTO Users VALUES (1, 1, 'USER 11'), (2, 1, 'USER 12'), (3, 2, 'USER 21')
RESULTS :
+-------+---------------+------------------+---------------+
| orgId | numberOfUsers | numberOfProjects | numberOfTasks |
+-------+---------------+------------------+---------------+
| 1 | 2 | 1 | 2 |
+-------+---------------+------------------+---------------+
| 2 | 1 | 2 | 3 |
+-------+---------------+------------------+---------------+
| 3 | 0 | 0 | 0 |
+-------+---------------+------------------+---------------+

Related

How to display the ID's of all the leaves related to a given level along with this level

I have a tree structure:
CARS (ID = 3)
| AUDI (ID: 5, ParentId: 3)
| DIR1 (ID: 9, ParentId: 5)
| DIR2 (ID: 7, ParentId: 5)
| DIR3 (ID: 8, ParentId: 5)
| FIAT (ID: 10, ParentId: 3)
| FORD (ID: 11, ParentId: 3)
| DIR4 (ID: 12, ParentId: 11)
| RENAULT (ID: 6, ParentId: 3)
| TOYOTA (ID: 4, ParentId: 3)
I need to display the ID's of all the leaves related to a given level along with this level as follows:
ID
Name
ID2
Name2
3
CARS
3
3
CARS
5
AUDI
3
CARS
9
DIR1
3
CARS
7
DIR2
3
CARS
8
DIR3
3
CARS
10
FIAT
3
CARS
11
FORD
3
CARS
12
DIR4
3
CARS
6
RENAULT
3
CARS
4
TOYOTA
5
AUDI
5
5
AUDI
9
DIR1
5
AUDI
7
DIR2
5
AUDI
8
DIR3
9
DIR1
9
DIR1
7
DIR2
7
DIR2
8
DIR3
8
DIR3
10
FIAT
10
11
FORD
11
11
FORD
12
DIR4
12
DIR4
12
DIR4
6
RENAULT
6
4
TOYOTA
4
This is a dynamic structure, users can delete and add leaves so I need a function or procedure.
I have created the following code but maybe there is a better, more elegant way to get the results:
DECLARE #T AS TABLE
(
ID int,
Name varchar(255),
ParentId int,
Level int,
Leaf int
)
INSERT INTO #T
VALUES (3, 'CARS', NULL, 1, 0),
(4, 'TOYOTA', 3, 2, 1),
(5, 'AUDI', 3, 2, 2),
(6, 'RENAULT', 3, 2, 3),
(7, 'DIR2', 5, 3, 1),
(8, 'DIR3', 5, 3, 2),
(9, 'DIR1', 5, 3, 3),
(10, 'FIAT', 3, 2, 4),
(11, 'FORD', 3, 2, 5),
(12, 'DIR4', 11, 3, 1)
;WITH cte1 AS
(
SELECT
ID, Name, ParentId, Level, Leaf
FROM
#T
), cte2 AS
(
SELECT
ID, Name, ParentId, 1 AS [Level],
CAST((RTRIM(LTRIM(STR(ID, 15, 0)))) AS VARCHAR(MAX)) AS Ids
FROM
#T t1
WHERE
ParentId IS NULL
UNION ALL
SELECT
t2.ID, t2.Name, t2.ParentId, M.[level] + 1 AS [Level],
CAST((M.Ids + ',' + RTRIM(LTRIM(STR(t2.ID, 15, 0)))) AS VARCHAR(MAX)) AS Ids
FROM
#T AS t2
JOIN
cte2 AS M ON t2.ParentId = M.ID
)
SELECT
B.ID, B.Name AS Name,
cte2.ID AS ID2, cte2.Name
FROM
cte2
CROSS APPLY
(SELECT Split.a.value('.', 'VARCHAR(1024)') AS gId
FROM
(SELECT
CAST ('<M>' + REPLACE(Ids, ',', '</M><M>') + '</M>' AS XML) AS String) A
CROSS APPLY
String.nodes ('/M') AS Split(a)) AS A
CROSS APPLY
(SELECT CAST(gId as int) AS G_ID) AS G_ID
INNER JOIN
#T B ON B.Id = G_ID
Is it what you are after?
WITH cte AS (
SELECT ID, Name, ID id2, Name Name2, ParentId, 1 AS [Level]
, CAST(STR(ID,15,0) AS VARCHAR(MAX)) AS Ids
FROM tbl
UNION ALL
SELECT m.ID, m.Name, t2.id, t2.name, t2.ParentId, M.[level] + 1
, M.Ids + ',' + STR(t2.ID,15,0)
FROM tbl AS t2
JOIN cte AS M ON t2.ParentId = M.ID2
)
select ID, Name, id2, Name2, level, replace(ids, ' ', '')
from cte
order by id, ids;
All paths from a node.
db<>fiddle

SQL - How to get Treeview of GrandParent, Parent, Child by ID

I have 2 tables, where Customer table has customer data and relations table has relations of customer.
CREATE Table Customer
(
id int,
name nvarchar(10)
)
INSERT INTO Customer Values
(1, 'aaa'),
(2, 'bbb'),
(3, 'ccc'),
(4, 'ddd'),
(5, 'eee'),
(6, 'fff'),
(7, 'ggg'),
(8, 'hhh'),
(9, 'iii'),
(10, 'jjj'),
(11, 'kkk'),
(12, 'lll')
CREATE TABLE Relations
(
id int,
parentid int
)
INSERT INTO Relations VALUES
(2, 1),
(3, 1),
(4, 2),
(5, 2),
(6, 1),
(7, 4),
(8, 5),
(9, 8),
(10, 8),
(12, 7)
I want to find GrandParent, Parent and child by ID. For Ex: If I want to find all relations of ID=4, I should get the result in the below format. Ordered by Grand Parent at the top if the ID has a parent or Grand parent. If not, then it has to show the child of that ID.
Grand Parent | aaa
Parent | bbb
Child | ggg
Child | lll
Could you please help me with the above query in "SQL Server".
You can use recursives CTEs (Common Table Expressions) to get this information. For example, for ID = 4 you can do:
with
a as ( -- get ancestors
select 0 as lvl, id, name from customer where id = 4
union all
select a.lvl - 1, c.id, c.name
from a
join relations r on a.id = r.id
join customer c on c.id = r.parentid
),
d as ( -- get descendants
select 0 as lvl, id, name from customer where id = 4
union all
select d.lvl + 1, c.id, c.name
from d
join relations r on d.id = r.parentid
join customer c on c.id = r.id
)
select lvl, id, name from a
union
select lvl, id, name from d
order by lvl
Result:
lvl id name
--- -- ----
-2 1 aaa
-1 2 bbb
0 4 ddd
1 7 ggg
2 12 lll

sql get average salary in self reference table

I've seen several questions/answers on how to recursively query a self-referencing table, but I am struggling to apply the answers I've found to aggregate up to each parent, grandparent, etc. regardless of where the item sits in the hierarchy.
Need to get an average salary for each department including hierarchy.
It means department should include the average salary of each sub-department and so on.
I've got nex db schema:
CREATE TABLE Employee
(
Id INT NOT NULL ,
Name VARCHAR(200) NOT NULL ,
Department_Id INT NOT NULL ,
PRIMARY KEY ( Id )
);
CREATE TABLE Department
(
Id INT NOT NULL ,
DepartmentName VARCHAR(200) NOT NULL ,
Parent_Id INT ,
PRIMARY KEY ( Id )
);
CREATE TABLE Salary
(
Id INT NOT NULL ,
Date DATETIME NOT NULL ,
Amount INT NOT NULL ,
Employee_Id INT NOT NULL ,
PRIMARY KEY ( Id )
);
I've tried something like that but it includes only 1st level of a hierarchy.
SELECT d.Id ,
d.DepartmentName ,
( SELECT AVG(s.Amount)
FROM dbo.Department dd
LEFT JOIN dbo.Department sdd ON dd.Id = sdd.Parent_Id
JOIN dbo.Employee e ON e.Department_Id = sdd.Id
OR e.Department_Id = dd.Id
JOIN dbo.Salary s ON s.Employee_Id = e.Id
WHERE dd.Id = d.Id
) AS avg_dep_salary
FROM dbo.Department d
WHERE d.Parent_Id IS NULL;
How can get an average salary of all levels?
EDIT: Added some inserts
INSERT INTO Employee
( Id, Name, Department_Id )
VALUES ( 1, 'Peter', 1 ),
( 2, 'Alex', 1 ),
( 3, 'Sam', 2 ),
( 4, 'James', 2 ),
( 5, 'Anna', 3 ),
( 6, 'Susan', 3 ),
( 7, 'Abby', 4 ),
( 8, 'Endy', 4 );
INSERT INTO Department
( Id, DepartmentName, Parent_Id )
VALUES ( 1, 'IT', NULL ),
( 2, 'HR', NULL),
( 3, 'SubIT', 1 ),
( 4, 'SubSubIT', 3 );
INSERT INTO Salary
( Id, Date, Amount, Employee_Id )
VALUES ( 1, '2013-01-09 16:03:50.003', 3000, 1 ),
( 2, '2013-01-11 16:03:50.003', 5000, 2 ),
( 3, '2013-01-09 16:03:50.003', 2000, 3 ),
( 4, '2013-01-11 16:03:50.003', 1000, 4 ),
( 5, '2013-01-09 16:03:50.003', 4000, 5 ),
( 6, '2013-01-11 16:03:50.003', 6000, 6 ),
( 7, '2013-01-09 16:03:50.003', 7000, 7 ),
( 8, '2013-01-13 16:03:50.003', 9000, 8 );
Expected result is:
Department | Average_Salary
__________________________________
IT | ( X1 + X2 + X3 ) / 3
HR | ( Y1 ) / 1
SubIT | ( X2 + X3 ) / 2
SubSubIT | ( X3 ) / 1
Where:
X1 - Average salary of IT department
X2 - Average salary of SubIT department
X3 - Average salary of SubSubIT department
Y1 - Average
salary of HR department
Sample data
I've added few rows with a wider tree structure.
DECLARE #Employee TABLE
(
Id INT NOT NULL ,
Name VARCHAR(200) NOT NULL ,
Department_Id INT NOT NULL ,
PRIMARY KEY ( Id )
);
DECLARE #Department TABLE
(
Id INT NOT NULL ,
DepartmentName VARCHAR(200) NOT NULL ,
Parent_Id INT ,
PRIMARY KEY ( Id )
);
DECLARE #Salary TABLE
(
Id INT NOT NULL ,
Date DATETIME NOT NULL ,
Amount INT NOT NULL ,
Employee_Id INT NOT NULL ,
PRIMARY KEY ( Id )
);
INSERT INTO #Employee
( Id, Name, Department_Id )
VALUES
( 1, 'Peter', 1 ),
( 2, 'Alex', 1 ),
( 3, 'Sam', 2 ),
( 4, 'James', 2 ),
( 5, 'Anna', 3 ),
( 6, 'Susan', 3 ),
( 7, 'Abby', 4 ),
( 8, 'Endy', 4 ),
(10, 'e_A', 10),
(11, 'e_AB', 11),
(12, 'e_AC', 12),
(13, 'e_AD', 13),
(14, 'e_ACE', 14),
(15, 'e_ACF', 15),
(16, 'e_ACG', 16);
INSERT INTO #Department
( Id, DepartmentName, Parent_Id )
VALUES
( 1, 'IT', NULL ),
( 2, 'HR', NULL),
( 3, 'SubIT', 1 ),
( 4, 'SubSubIT', 3 ),
(10, 'A', NULL ),
(11, 'AB', 10),
(12, 'AC', 10),
(13, 'AD', 10),
(14, 'ACE', 12),
(15, 'ACF', 12),
(16, 'ACG', 12);
INSERT INTO #Salary
( Id, Date, Amount, Employee_Id )
VALUES
( 1, '2013-01-09 16:03:50.003', 3000, 1 ),
( 2, '2013-01-11 16:03:50.003', 5000, 2 ),
( 3, '2013-01-09 16:03:50.003', 2000, 3 ),
( 4, '2013-01-11 16:03:50.003', 1000, 4 ),
( 5, '2013-01-09 16:03:50.003', 4000, 5 ),
( 6, '2013-01-11 16:03:50.003', 6000, 6 ),
( 7, '2013-01-09 16:03:50.003', 7000, 7 ),
( 8, '2013-01-13 16:03:50.003', 9000, 8 ),
(10, '2013-01-13 16:03:50', 100, 10),
(11, '2013-01-13 16:03:50', 100, 11),
(12, '2013-01-13 16:03:50', 100, 12),
(13, '2013-01-13 16:03:50', 100, 13),
(14, '2013-01-13 16:03:50', 100, 14),
(15, '2013-01-13 16:03:50', 100, 15),
(16, '2013-01-13 16:03:50', 100, 16);
Query
WITH
CTE_Departments
AS
(
SELECT
D.Id
,D.Parent_Id
,D.DepartmentName
,SUM(Amount) AS DepartmentAmount
,COUNT(*) AS DepartmentCount
FROM
#Department AS D
INNER JOIN #Employee AS E ON E.Department_Id = D.Id
INNER JOIN #Salary AS S ON S.Employee_Id = E.Id
GROUP BY
D.Id
,D.Parent_Id
,D.DepartmentName
)
,CTE_Recursive
AS
(
SELECT
CTE_Departments.Id AS OriginalID
,CTE_Departments.DepartmentName AS OriginalName
,CTE_Departments.Id
,CTE_Departments.Parent_Id
,CTE_Departments.DepartmentName
,CTE_Departments.DepartmentAmount
,CTE_Departments.DepartmentCount
,1 AS Lvl
FROM CTE_Departments
UNION ALL
SELECT
CTE_Recursive.OriginalID
,CTE_Recursive.OriginalName
,CTE_Departments.Id
,CTE_Departments.Parent_Id
,CTE_Departments.DepartmentName
,CTE_Departments.DepartmentAmount
,CTE_Departments.DepartmentCount
,CTE_Recursive.Lvl + 1 AS Lvl
FROM
CTE_Departments
INNER JOIN CTE_Recursive ON CTE_Recursive.Id = CTE_Departments.Parent_Id
)
SELECT
OriginalID
,OriginalName
,SUM(DepartmentAmount) AS SumAmount
,SUM(DepartmentCount) AS SumCount
,SUM(DepartmentAmount) / SUM(DepartmentCount) AS AvgAmount
FROM CTE_Recursive
GROUP BY
OriginalID
,OriginalName
ORDER BY OriginalID
;
Result
+------------+--------------+-----------+----------+-----------+
| OriginalID | OriginalName | SumAmount | SumCount | AvgAmount |
+------------+--------------+-----------+----------+-----------+
| 1 | IT | 34000 | 6 | 5666 |
| 2 | HR | 3000 | 2 | 1500 |
| 3 | SubIT | 26000 | 4 | 6500 |
| 4 | SubSubIT | 16000 | 2 | 8000 |
| 10 | A | 700 | 7 | 100 |
| 11 | AB | 100 | 1 | 100 |
| 12 | AC | 400 | 4 | 100 |
| 13 | AD | 100 | 1 | 100 |
| 14 | ACE | 100 | 1 | 100 |
| 15 | ACF | 100 | 1 | 100 |
| 16 | ACG | 100 | 1 | 100 |
+------------+--------------+-----------+----------+-----------+
Run the query step-by-step, CTE-by-CTE to understand how it works.
CTE_Departments gives total amount and number of people for each department.
CTE_Recursive recursively generates child rows for each department, while keeping the OriginalID - the ID of the department where the recursion started.
Final query simply groups everything by this OriginalID.
Here is one way
with avg_per_dep as (
select
[Month] = eomonth(s.date), d.Id, d.DepartmentName
, avgDep = avg(s.Amount * 1.0)
from
Salary s
join Employee e on s.Employee_Id = e.Id
join Department d on e.Department_Id = d.Id
group by d.Id, d.DepartmentName, eomonth(s.date)
)
, rcte as (
select
i = Id, Id
, list = cast(',' + cast(Id as varchar(10)) + ',' as varchar(max))
, step = 1
from
Department
union all
select
a.i, b.Id, cast(a.list + cast(b.Id as varchar(10)) + ',' as varchar(max))
, step + 1
from
rcte a
join Department b on a.Id = b.Parent_Id
)
select
d.DepartmentName, c.[Month]
, Average_Salary = avg(c.avgDep)
from
(
select
top 1 with ties i, list
from
rcte
order by row_number() over (partition by i order by step desc)
) t
join avg_per_dep c on t.list like '%,' + cast(c.Id as varchar(10)) + ',%'
join Department d on t.i = d.Id
group by t.i, d.DepartmentName, c.[Month]
Output
DepartmentName [Month] Average_Salary
---------------------------------------------
IT 2013-01-31 5666.666666
HR 2013-01-31 1500.000000
SubIT 2013-01-31 6500.000000
SubSubIT 2013-01-31 8000.000000
Idea:
Calculate average salary per department
Get a list of departments with all childs with recursive CTE.
Join two table and calculate avg with childs
You could also use below query to get the expected result
WITH Department_Path
AS (SELECT Id, CAST(CONCAT('#', Id, '#') AS VARCHAR(255)) AS Path
FROM Department
WHERE Parent_Id IS NULL
UNION ALL
SELECT Child.Id, CAST(CONCAT(Parent.Path, Child.Id, '#') AS VARCHAR(255)) AS Path
FROM Department Child
INNER JOIN Department_Path Parent
ON Parent.Id = Child.Parent_Id)
SELECT Department.Id,
Department.DepartmentName,
AVG(Salary.Amount) As Average_Salary,
COUNT(Employee.Id) AS Employee_Count
FROM Department
INNER JOIN Department_Path
ON CHARINDEX(CONCAT('#', Department.Id, '#'), Department_Path.Path) > 0
INNER JOIN Employee
ON Employee.Department_Id = Department_Path.Id
INNER JOIN Salary
ON Salary.Employee_Id = Employee.Id
GROUP BY Department.Id,
Department.DepartmentName;
The idea is that each employee is belong to a list of hierarchy departments. For each department, we could retrieve all employee who belong to it and then calculate the average salary.

SQL query to reconstruct inherited EAV model

I have 5 tables in my database representing an inherited EAV model:
CREATE TABLE AttributeNames
("ID" int, "Name" varchar(8))
;
INSERT INTO AttributeNames
("ID", "Name")
VALUES
(1, 'Color'),
(2, 'FuelType'),
(3, 'Doors'),
(4, 'Price')
;
CREATE TABLE MasterCars
("ID" int, "Name" varchar(10))
;
INSERT INTO MasterCars
("ID", "Name")
VALUES
(5, 'BMW'),
(6, 'Audi'),
(7, 'Ford')
;
CREATE TABLE MasterCarAttributes
("ID" int, "AttributeNameId" int, "Value" varchar(10), "MasterCarId" int)
;
INSERT INTO MasterCarAttributes
("ID", "AttributeNameId", "Value", "MasterCarId")
VALUES
(100, 1, 'Red', 5),
(101, 2, 'Gas', 5),
(102, 3, '4', 5),
(102, 4, '$100K', 5),
(103, 1, 'Blue', 6),
(104, 2, 'Diesel', 6),
(105, 3, '3', 6),
(106, 4, '$80k', 6),
(107, 1, 'Green', 7),
(108, 2, 'Diesel', 7),
(109, 3, '5', 7),
(110, 4, '$60k', 7)
;
CREATE TABLE LocalCars
("ID" int, "MasterCarId" int)
;
INSERT INTO LocalCars
("ID", "MasterCarId")
VALUES
(8, '5'),
(9, '6'),
(10, NULL)
;
CREATE TABLE LocalCarAttributes
("ID" int, "AttributeNameId" int, "Value" varchar(6), "LocalCarId" int)
;
INSERT INTO LocalCarAttributes
("ID", "AttributeNameId", "Value", "LocalCarId")
VALUES
(43, 1, 'Yellow', 8),
(44, 3, '6', 9),
(45, 1, 'Red', 10),
(46, 2, 'Gas', 10),
(47, 3, '2', 10),
(48, 4, '$60k', 10)
;
I can retrieve all of master car attributes as follows:
SELECT MC.ID, MCA.AttributeNameId, MCA.Value
FROM MasterCars MC
left join MasterCarAttributes MCA on MC.ID = MCA.MasterCarId
order by MC.ID;
Likewise, I can retrieve all of the local car attributes as follows:
SELECT LC.ID, LCA.AttributeNameId, LCA.Value
FROM LocalCars LC
left join LocalCarAttributes LCA on LC.ID = LCA.LocalCarId
order by LC.ID;
If LocalCars.MasterCarId is not NULL, then that local car can inherit the attributes of that master car. A local car attribute with the same AttributeNameId overrides any master attribute with the same AttributeNameId.
So given the data above, I have 3 local cars each with 4 attributes (color, fuelType, doors, price). Inherited attribute values in bold:
Local Car Id = 1 (Yellow, Gas, 4, $100K)
Local Car Id = 2 (Blue, Diesel, 6, $80k)
Local Car Id = 3 (Red, Gas, 2, $60k)
I'm trying to find the necessary joins required to join the two queries above together to give a complete set of local cars attributes, some inherited:
LocalCarId AttributeNameId Value
------------------------------------------
1 1 Yellow
1 2 Gas
1 3 4
1 4 $100K
2 1 Blue
2 2 Diesel
2 3 6
2 4 $80K
3 1 Red
3 2 Gas
3 3 2
3 4 $60K
or possibly even:
LocalCarId AttributeNameId LocalValue MasterValue
-------------------------------------------------------------
1 1 Yellow Red
1 2 NULL Gas
1 3 NULL 4
1 4 NULL $100K
2 1 NULL Blue
2 2 NULL Diesel
2 3 6 3
2 4 NULL $80K
3 1 Red NULL
3 2 Gas NULL
3 3 2 NULL
3 4 $60K NULL
The problem can be solved by performing a union on all of your local car attributes and master car attributes. Each record is marked with an [IsMasterAttribute] flag. The next step is then use the ROW_NUMBER() window function to rank each of the duplicate attributes. The final step is to only select attributes which has a rank of 1.
;WITH CTE_CombinedAttributes
AS
(
SELECT 1 AS IsMasterAttribute
,LC.ID
,MC.ID AS MasterCarId
,MCA.AttributeNameId
,MCA.Value
FROM MasterCars MC
LEFT OUTER JOIN MasterCarAttributes MCA on MC.ID = MCA.MasterCarId
INNER JOIN LocalCars LC ON LC.MasterCarId = MC.ID
UNION ALL
SELECT 0 AS IsMasterAttribute
,LC.ID
,LC.MasterCarId
,LCA.AttributeNameId
,LCA.Value
FROM LocalCars LC
LEFT OUTER JOIN LocalCarAttributes LCA on LC.ID = LCA.LocalCarId
)
,
CTE_RankedAttributes
AS
(
SELECT [IsMasterAttribute]
,[ID]
,[AttributeNameId]
,[Value]
,ROW_NUMBER() OVER (PARTITION BY [ID], [AttributeNameId] ORDER BY [IsMasterAttribute]) AS [AttributeRank]
FROM CTE_CombinedAttributes
)
SELECT [IsMasterAttribute]
,[ID]
,[AttributeNameId]
,[Value]
FROM CTE_RankedAttributes
WHERE [AttributeRank] = 1
ORDER BY [ID]
The second output is also possible by performing a simple pivot on the final result:
;WITH CTE_CombinedAttributes
AS
(
SELECT 1 AS IsMasterAttribute
,LC.ID
,MC.ID AS MasterCarId
,MCA.AttributeNameId
,MCA.Value
FROM MasterCars MC
LEFT OUTER JOIN MasterCarAttributes MCA on MC.ID = MCA.MasterCarId
INNER JOIN LocalCars LC ON LC.MasterCarId = MC.ID
UNION ALL
SELECT 0 AS IsMasterAttribute
,LC.ID
,LC.MasterCarId
,LCA.AttributeNameId
,LCA.Value
FROM LocalCars LC
LEFT OUTER JOIN LocalCarAttributes LCA on LC.ID = LCA.LocalCarId
)
,
CTE_RankedAttributes
AS
(
SELECT [IsMasterAttribute]
,[ID]
,[AttributeNameId]
,[Value]
,ROW_NUMBER() OVER (PARTITION BY [ID], [AttributeNameId] ORDER BY [IsMasterAttribute]) AS [AttributeRank]
FROM CTE_CombinedAttributes
)
SELECT [ID]
,[AttributeNameId]
,MAX(
CASE [IsMasterAttribute]
WHEN 0 THEN [Value]
END
) AS LocalValue
,MAX(
CASE [IsMasterAttribute]
WHEN 1 THEN [Value]
END
) AS MasterValue
FROM CTE_RankedAttributes
GROUP BY [ID], [AttributeNameId]
ORDER BY [ID]
SQL Fiddle Demo
SELECT LC."ID" as LocalCarID,
COALESCE(LCA."AttributeNameId", MCA."AttributeNameId") as "AttributeNameId",
COALESCE(LCA."Value", MCA."Value") as "Value"
FROM LocalCars LC
LEFT JOIN MasterCars MC
ON LC."MasterCarId" = MC."ID"
LEFT JOIN MasterCarAttributes MCA
ON MC."ID" = MCA."MasterCarId"
LEFT JOIN LocalCarAttributes LCA
ON ( MCA."AttributeNameId" = LCA."AttributeNameId"
OR MCA."AttributeNameId" IS NULL)
-- This is the important part
-- Try to join with a MasterAtribute otherwise use the Car Atribute.
AND LC."ID" = LCA."ID"
OUTPUT
| LocalCarID | AttributeNameId | Value |
|------------|-----------------|--------|
| 1 | 1 | Blue |
| 1 | 2 | Gas |
| 2 | 1 | Green |
| 2 | 2 | Diesel |

Count when there are 6 or more unique occurrences in a column then in a new column have a string that identifies it

I have a table with patients, their services, date of services, ect... If a patient has 6+ unique services in a month then in my select statement
I want a new column to have the word "Full Month" in it.
You need a simple group by
Set up data
create table #patient
(
patientId int,
serviceId int,
dt date
)
insert into #patient values
(1, 1, '2016-01-02'),
(1, 1, '2016-01-05'),
(1, 1, '2016-01-07'),
(1, 1, '2016-01-12'),
(1, 1, '2016-01-16'),
(1, 1, '2016-01-20'),
(1, 1, '2016-01-25'),
(1, 2, '2016-01-02'),
(1, 2, '2016-01-02'),
(2, 1, '2016-01-02'),
(2, 1, '2016-01-02')
Get the result
select patientId, serviceId, count(*) AS Cnt
, case
when count(*) >= 6 then 'Full Month'
else 'Not Full Month'
end AS YourNewColumn
from #patient
group by patientId, serviceId, month(dt)
It will show
+-----------+-----------+-----+----------------+
| patientId | serviceId | Cnt | YourNewColumn |
+-----------+-----------+-----+----------------+
| 1 | 1 | 7 | Full Month |
| 1 | 2 | 2 | Not Full Month |
| 2 | 1 | 2 | Not Full Month |
+-----------+-----------+-----+----------------+
I suppose that you could use a GROUP BY query on your specific patient identifier to get all of the results for a specific patient over a date range as seen below :
SELECT PatientId,
CASE WHEN COUNT(ServiceId) > 5 THEN 1 ELSE 0 END AS FullMonth
FROM Patients
WHERE ServiceDate BETWEEN #Start AND #End
GROUP BY PatientId, ServiceId
You can see an overly simplified example of this here.