Return results from multiple tables - sql

I am doing analysis on the Stack Overflow dump.
Problem statement: I have 4 tables and require result in the format given.
Table 1: UserID Year QuestionsOnTopicA
Table 2: UserID Year AnswersOnTopicA
Table 3: UserID Year QuestionsOnTopicB
Table 4: UserID Year AnswersOnTopicB
Desired Output:
UserID Year QuestionsOnTopicA AnswersOnTopicA QuestionsOnTopicB AnswersOnTopicB
UserID column should have entries from all the 4 tables.
I tried performing inner and outer join on the tables but the results were incorrect.
Inner join (returns userid present only in first table 1)
Outer join (returns other columns only for userid in table 1)
Not sure if union will make sense in this scenario.
Queries are being executed on data.stackexchange.com/stackoverflow
Example
Table 1: 1001, 2010, 5 || 1001, 2011, 3 || 1002, 2010, 4
Table 2: 1001, 2010, 10 || 1001, 2011, 7 || 1002, 2010, 5
Table 3: 1002, 2010, 5
Table 4: 1001, 2010, 10 || 1004, 2011, 5
Output:
1001, 2010, 5 , 10, 0, 10
1001, 2011, 3, 7, 0, 0
1002, 2010, 4, 5, 5, 0
1004, 2011, 0, 0, 0, 5

Ok, this works as intended:
SELECT COALESCE(A.UserID,B.UserID,C.UserID,D.UserID) UserID,
COALESCE(A.[Year],B.[Year],C.[Year],D.[Year]) [Year],
ISNULL(A.QuestionsOnTopicA,0) QuestionsOnTopicA,
ISNULL(B.AnswersOnTopicA,0) AnswersOnTopicA,
ISNULL(C.QuestionsOnTopicB,0) QuestionsOnTopicB,
ISNULL(D.AnswersOnTopicB,0) AnswersOnTopicB
FROM Table1 A
FULL JOIN Table2 B
ON A.UserID = B.UserID
AND A.[Year] = B.[Year]
FULL JOIN Table3 C
ON COALESCE(A.UserID,B.UserID) = C.UserID
AND COALESCE(A.[Year],B.[Year]) = C.[Year]
FULL JOIN Table4 D
ON COALESCE(A.UserID,B.UserID,C.UserID) = D.UserID
AND COALESCE(A.[Year],B.[Year],C.[Year]) = D.[Year]
Here is a sqlfiddle with a demo of this.
And the results are:
╔════════╦══════╦═══════════════════╦═════════════════╦═══════════════════╦═════════════════╗
║ UserID ║ Year ║ QuestionsOnTopicA ║ AnswersOnTopicA ║ QuestionsOnTopicB ║ AnswersOnTopicB ║
╠════════╬══════╬═══════════════════╬═════════════════╬═══════════════════╬═════════════════╣
║ 1001 ║ 2010 ║ 5 ║ 10 ║ 0 ║ 10 ║
║ 1001 ║ 2011 ║ 3 ║ 7 ║ 0 ║ 0 ║
║ 1002 ║ 2010 ║ 4 ║ 5 ║ 5 ║ 0 ║
║ 1004 ║ 2011 ║ 0 ║ 0 ║ 0 ║ 5 ║
╚════════╩══════╩═══════════════════╩═════════════════╩═══════════════════╩═════════════════╝

Use this SQL may be?
SELECT a.UserID, a.Year,
a.QuestionsOnTopicA,
b.AnswersOnTopicA,
c.QuestionsOnTopicB,
d.AnswersOnTopicB
FROM Table 1 a,
Table 2 b,
Table 3 c,
Table 4 d
WHERE a.UserID = b.UserID
AND b.UserID = c.UserID
AND c.UserID = d.UserID
AND d.UserID = a.UserID

select coalesce(a.UserID, b.UserID, c.UserID, d.UserID),
coalesce(a.Year, b.Year, c.Year, d.Year),
a.QuestionsOnTopicA, b.AnswersOnTopicA,
c.QuestionsOnTopicB, d.AnswersOnTopicB
from Table1 a full outer join Table2 b on a.UserID = b.UserID and a.Year = b.Year
full outer join Table3 c on (c.UserID = b.UserID or c.UserID = a.UserID)
and (c.Year = b.Year or c.Year = a.Year)
full outer join Table4 d on (d.UserID = c.UserID or d.UserID = b.UserID or d.UserID = a.UserID)
and (d.Year = a.Year or d.Year = b.Year or d.Year = a.Year);

First of all you should retrieve the data from the tables using inner join.
Then you should use SQL Server Pivot as shown in this link.

Related

How can I Ascertain the structure for each person from a self referencing table

I have the following tables:
Employees
-------------
ClockNo int
CostCentre varchar
Department int
and
Departments
-------------
DepartmentCode int
CostCentreCode varchar
Parent int
Departments can have other departments as parents meaning there is infinite hierarchy. All departments belong to a cost centre and so will always have a CostCentreCode. If parent = 0 it is a top level department
Employees must have a CostCentre value but may have a Department of 0 meaning they are not in a department
What I want to try and generate is a query that will give the up to four levels of hierarchy. Like this:
EmployeesLevels
-----------------
ClockNo
CostCentre
DeptLevel1
DeptLevel2
DeptLevel3
DeptLevel4
I've managed to get something to display the department structure on it's own, but I can't work out how to link this to the employees without creating duplicate employee rows:
SELECT d1.Description AS lev1, d2.Description as lev2, d3.Description as lev3, d4.Description as lev4
FROM departments AS d1
LEFT JOIN departments AS d2 ON d2.parent = d1.departmentcode
LEFT JOIN departments AS d3 ON d3.parent = d2.departmentcode
LEFT JOIN departments AS d4 ON d4.parent = d3.departmentcode
WHERE d1.parent=0;
SQL To create Structure and some sample data:
CREATE TABLE Employees(
ClockNo integer NOT NULL PRIMARY KEY,
CostCentre varchar(20) NOT NULL,
Department integer NOT NULL);
CREATE TABLE Departments(
DepartmentCode integer NOT NULL PRIMARY KEY,
CostCentreCode varchar(20) NOT NULL,
Parent integer NOT NULL
);
CREATE INDEX idx0 ON Employees (ClockNo);
CREATE INDEX idx1 ON Employees (CostCentre, ClockNo);
CREATE INDEX idx2 ON Employees (CostCentre);
CREATE INDEX idx0 ON Departments (DepartmentCode);
CREATE INDEX idx1 ON Departments (CostCentreCode, DepartmentCode);
INSERT INTO Employees VALUES (1, 'AAA', 0);
INSERT INTO Employees VALUES (2, 'AAA', 3);
INSERT INTO Employees VALUES (3, 'BBB', 0);
INSERT INTO Employees VALUES (4, 'BBB', 4);
INSERT INTO Employees VALUES (5, 'CCC', 0);
INSERT INTO Employees VALUES (6, 'AAA', 1);
INSERT INTO Employees VALUES (7, 'AAA', 5);
INSERT INTO Employees VALUES (8, 'AAA', 15);
INSERT INTO Departments VALUES (1, 'AAA', 0);
INSERT INTO Departments VALUES (2, 'AAA', 1);
INSERT INTO Departments VALUES (3, 'AAA', 1);
INSERT INTO Departments VALUES (4, 'BBB', 0);
INSERT INTO Departments VALUES (5, 'AAA', 3);
INSERT INTO Departments VALUES (12, 'AAA', 5);
INSERT INTO Departments VALUES (15, 'AAA', 12);
This gives the following structure (employee clock numbers in square brackets):
Root
|
|---AAA [1]
| \---1 [6]
| |---2
| \---3 [2]
| \---5 [7]
| \---12
| \---15 [8]
|
|---BBB [3]
| \---4 [4]
|
\---CCC [5]
The query should return the following:
ClockNo CostCentre Level1 Level2 Level3 Level4
1 AAA
2 AAA 1 3
3 BBB
4 BBB 4
5 CCC
6 AAA 1
7 AAA 1 3 5
8 AAA 1 3 5 12 *
* In the case of Employee 8, they are in level5. Ideally I would like to show all their levels down to level4, but I am happy just to show the CostCentre in this case
When we join the tables we should stop further traversal of the path when we found proper department that belongs to the Employee at previous level.
Also we have exceptional case when Employee.Department=0. In this case we shouldn't join any of departments, because in this case Department is the Root.
We need to choose only those records which contains employee's Department at one of the levels.
In case if employee's department level is greater than 4 we should expand all 4 levels of departments and show them as is (even if can't reach the desired department level and didn't find it within expanded ones).
select e.ClockNo,
e.CostCentre,
d1.DepartmentCode as Level1,
d2.DepartmentCode as Level2,
d3.DepartmentCode as Level3,
d4.DepartmentCode as Level4
from Employees e
left join Departments d1
on e.CostCentre=d1.CostCentreCode
and d1.Parent=0
and ((d1.DepartmentCode = 0 and e.Department = 0) or e.Department <> 0)
left join Departments d2
on d2.parent=d1.DepartmentCode
and (d1.DepartMentCode != e.Department and e.Department<>0)
left join Departments d3
on d3.parent=d2.DepartmentCode
and (d2.DepartMentCode != e.Department and e.Department<>0)
left join Departments d4
on d4.parent=d3.DepartmentCode
and (d3.DepartMentCode != e.Department and e.Department<>0)
where e.Department=d1.DepartmentCode
or e.Department=d2.DepartmentCode
or e.Department=d3.DepartmentCode
or e.Department=d4.DepartmentCode
or e.Department=0
or (
(d1.DepartmentCode is not null) and
(d2.DepartmentCode is not null) and
(d3.DepartmentCode is not null) and
(d4.DepartmentCode is not null)
)
order by e.ClockNo;
SELECT [ClockNo]
, [CostCentre]
, CASE
WHEN Department <> 0 THEN dept.[Level1]
END AS [Level1]
, CASE
WHEN Department <> 0 THEN dept.[Level2]
END AS [Level2]
, CASE
WHEN Department <> 0 THEN dept.[Level3]
END AS [Level3]
, CASE
WHEN Department <> 0 THEN dept.[Level4]
END AS [Level4]
FROM [Employees] emp
LEFT JOIN
(
SELECT
CASE
WHEN d4.[DepartmentCode] IS NOT NULL THEN d4.[DepartmentCode]
WHEN d3.[DepartmentCode] IS NOT NULL THEN d3.[DepartmentCode]
WHEN d2.[DepartmentCode] IS NOT NULL THEN d2.[DepartmentCode]
ELSE d1.[DepartmentCode]
END AS [Level1]
, CASE
WHEN d4.[DepartmentCode] IS NOT NULL THEN d3.[DepartmentCode]
WHEN d3.[DepartmentCode] IS NOT NULL THEN d2.[DepartmentCode]
WHEN d2.[DepartmentCode] IS NOT NULL THEN d1.[DepartmentCode]
ELSE NULL
END AS [Level2]
, CASE
WHEN d4.[DepartmentCode] IS NOT NULL THEN d2.[DepartmentCode]
WHEN d3.[DepartmentCode] IS NOT NULL THEN d1.[DepartmentCode]
ELSE NULL
END AS [Level3]
, CASE
WHEN d4.[DepartmentCode] IS NOT NULL THEN d1.[DepartmentCode]
ELSE NULL
END AS [Level4]
, d1.[DepartmentCode] AS [DepartmentCode]
, d1.[CostCentreCode] AS [CostCenter]
FROM [Departments] d1
LEFT JOIN
[Departments] d2
ON d1.[Parent] = d2.[DepartmentCode]
LEFT JOIN
[Departments] d3
ON d2.[Parent] = d3.[DepartmentCode]
LEFT JOIN
[Departments] d4
ON d3.[Parent] = d4.[DepartmentCode]
) AS dept
ON emp.[Department] = dept.[DepartmentCode]
ORDER BY emp.[ClockNo]
The main challenge here is that the employee's department might need to be displayed in column Level1, Level2, Level3, or Level4, depending on how many upper levels there are for that department in the hierarchy.
I would suggest to first query the number of department levels there are for each employee in an inner query, and then to use that information to put the department codes in the right column:
SELECT ClockNo, CostCentre,
CASE LevelCount
WHEN 1 THEN Dep1
WHEN 2 THEN Dep2
WHEN 3 THEN Dep3
ELSE Dep4
END Level1,
CASE LevelCount
WHEN 2 THEN Dep1
WHEN 3 THEN Dep2
WHEN 4 THEN Dep3
END Level2,
CASE LevelCount
WHEN 3 THEN Dep1
WHEN 4 THEN Dep2
END Level3,
CASE LevelCount
WHEN 4 THEN Dep1
END Level4
FROM (SELECT e.ClockNo, e.CostCentre,
CASE WHEN d2.DepartmentCode IS NULL THEN 1
ELSE CASE WHEN d3.DepartmentCode IS NULL THEN 2
ELSE CASE WHEN d4.DepartmentCode IS NULL THEN 3
ELSE 4
END
END
END AS LevelCount,
d1.DepartmentCode Dep1, d2.DepartmentCode Dep2,
d3.DepartmentCode Dep3, d4.DepartmentCode Dep4
FROM Employees e
LEFT JOIN departments AS d1 ON d1.DepartmentCode = e.Department
LEFT JOIN departments AS d2 ON d2.DepartmentCode = d1.Parent
LEFT JOIN departments AS d3 ON d3.DepartmentCode = d2.Parent
LEFT JOIN departments AS d4 ON d4.DepartmentCode = d3.Parent) AS Base
ORDER BY ClockNo
SQL Fiddle
Alternatively, you could do a plain UNION ALL of the 5 possible scenarios in terms of existing levels (chains of 0 to 4 departments):
SELECT ClockNo, CostCentre, d4.DepartmentCode Level1,
d3.DepartmentCode Level2, d2.DepartmentCode Level3,
d1.DepartmentCode Level4
FROM Employees e
INNER JOIN departments AS d1 ON d1.DepartmentCode = e.Department
INNER JOIN departments AS d2 ON d2.DepartmentCode = d1.Parent
INNER JOIN departments AS d3 ON d3.DepartmentCode = d2.Parent
INNER JOIN departments AS d4 ON d4.DepartmentCode = d3.Parent
UNION ALL
SELECT ClockNo, CostCentre, d3.DepartmentCode,
d2.DepartmentCode, d1.DepartmentCode, NULL
FROM Employees e
INNER JOIN departments AS d1 ON d1.DepartmentCode = e.Department
INNER JOIN departments AS d2 ON d2.DepartmentCode = d1.Parent
INNER JOIN departments AS d3 ON d3.DepartmentCode = d2.Parent
WHERE d3.Parent = 0
UNION ALL
SELECT ClockNo, CostCentre, d2.DepartmentCode,
d1.DepartmentCode, NULL, NULL
FROM Employees e
INNER JOIN departments AS d1 ON d1.DepartmentCode = e.Department
INNER JOIN departments AS d2 ON d2.DepartmentCode = d1.Parent
WHERE d2.Parent = 0
UNION ALL
SELECT ClockNo, CostCentre, d1.DepartmentCode Level1,
NULL, NULL, NULL
FROM Employees e
INNER JOIN departments AS d1 ON d1.DepartmentCode = e.Department
WHERE d1.Parent = 0
UNION ALL
SELECT ClockNo, CostCentre, NULL, NULL, NULL, NULL
FROM Employees e
WHERE e.Department = 0
ORDER BY ClockNo
SQL Fiddle
SunnyMagadan's query is good. But depending on number of employees in a department you may wish to try the following one which leaves DB optimizer an opportunity to traverse department hierarchy only once for a department instead of repeating it for every employee in a department.
SELECT e.ClockNo, e.CostCentre, Level1, Level2, Level3, Level4
FROM Employees e
LEFT JOIN
(SELECT
d1.departmentcode
, d1.CostCentreCode
, coalesce (d4.departmentcode, d3.departmentcode
, d2.departmentcode, d1.departmentcode) AS Level1
, case when d4.departmentcode is not null then d3.departmentcode
when d3.departmentcode is not null then d2.departmentcode
when d2.departmentcode is not null then d1.departmentcode end as Level2
, case when d4.departmentcode is not null then d2.departmentcode
when d3.departmentcode is not null then d1.departmentcode end as Level3
, case when d4.departmentcode is not null then d1.departmentcode end as Level4
FROM departments AS d1
LEFT JOIN departments AS d2 ON d1.parent = d2.departmentcode
LEFT JOIN departments AS d3 ON d2.parent = d3.departmentcode
LEFT JOIN departments AS d4 ON d3.parent = d4.departmentcode) d
ON d.DepartmentCode = e.Department AND d.CostCentreCode = e.CostCentre
;
EDIT Regarding level 5+ departments.
Any fixed step query can not get top 4 levels for them. So change above query just to mark them some way, -1 for example.
, case when d4.Parent > 0 then NULL else
coalesce (d4.departmentcode, d3.departmentcode
, d2.departmentcode, d1.departmentcode) end AS Level1
and so on.
Try this query. Not sure how it will show itself performance-wise on large data with this COALESCE in place.
The idea is to build a derived table of hierarchies leading to each Department
lev1 lev2 lev3 lev4
1 NULL NULL NULL
1 2 NULL NULL
1 3 NULL NULL
1 3 5 NULL
4 NULL NULL NULL
and then use rightmost department to join it with Employees. Here's the full query:
SELECT
ClockNo,
CostCentre,
lev1,
lev2,
lev3,
lev4
FROM Employees
LEFT JOIN
(
SELECT
d1.DepartmentCode AS lev1,
NULL as lev2,
NULL as lev3,
NULL as lev4
FROM departments AS d1
WHERE d1.parent=0
UNION ALL
SELECT
d1.DepartmentCode AS lev1,
d2.DepartmentCode as lev2,
NULL as lev3,
NULL as lev4
FROM departments AS d1
JOIN departments AS d2 ON d2.parent = d1.departmentcode
WHERE d1.parent=0
UNION ALL
SELECT
d1.DepartmentCode AS lev1,
d2.DepartmentCode as lev2,
d3.DepartmentCode as lev3,
NULL as lev4
FROM departments AS d1
JOIN departments AS d2 ON d2.parent = d1.departmentcode
JOIN departments AS d3 ON d3.parent = d2.departmentcode
WHERE d1.parent=0
UNION ALL
SELECT
d1.DepartmentCode AS lev1,
d2.DepartmentCode as lev2,
d3.DepartmentCode as lev3,
d4.DepartmentCode as lev4
FROM departments AS d1
JOIN departments AS d2 ON d2.parent = d1.departmentcode
JOIN departments AS d3 ON d3.parent = d2.departmentcode
JOIN departments AS d4 ON d4.parent = d3.departmentcode
WHERE d1.parent=0
) Department
ON COALESCE(Department.lev4, Department.lev3, Department.lev2, Department.lev1) = Employees.Department
ORDER BY ClockNo
I would suggest that you seperate the query for getting the employee and getting his / her department hierarchy.
To get the hierarchy of the department, I would suggest you use recursive CTE something like this:
with DepartmentList (DepartmentCode, CostCentreCode, Parent) AS
(
SELECT
parentDepartment.DepartmentCode,
parentDepartment.CostCentreCode,
parentDepartment.Parent
FROM Departments parentDepartment
WHERE DepartmentCode = #departmentCode
UNION ALL
SELECT
childDepartment.DepartmentCode
childDepartment.CostCentreCode,
childDepartment.Parent,
FROM Departments childDepartment
JOIN DepartmentList
ON childDepartment.Parent = DepartmentList.DepartmentCode
)
SELECT * FROM DepartmentList
This is not the direct answer to your question, but this will give you option and idea. Hope this helps.
So I've taken two steps in order to get this done:
I had to generate levels for deparments recursively
Generate all possible parent nodes so that I could display them in pivoted view
This recursive query builds DepartmentLevels:
;WITH CTE (DepartmentCode, CostCentreCode, Parent, DepartmentLevel)
AS (
SELECT D.DepartmentCode, D.CostCentreCode, D.Parent, 1
FROM dbo.Departments AS D
WHERE D.Parent = 0
UNION ALL
SELECT D.DepartmentCode, D.CostCentreCode, D.Parent, C.DepartmentLevel + 1
FROM dbo.Departments AS D
INNER JOIN CTE AS C
ON C.DepartmentCode = D.Parent
AND C.CostCentreCode = D.CostCentreCode
)
SELECT *
INTO #DepartmentLevels
FROM CTE;
That's the output:
╔════════════════╦════════════════╦════════╦═════════════════╗
║ DepartmentCode ║ CostCentreCode ║ Parent ║ DepartmentLevel ║
╠════════════════╬════════════════╬════════╬═════════════════╣
║ 1 ║ AAA ║ 0 ║ 1 ║
║ 4 ║ BBB ║ 0 ║ 1 ║
║ 2 ║ AAA ║ 1 ║ 2 ║
║ 3 ║ AAA ║ 1 ║ 2 ║
║ 5 ║ AAA ║ 3 ║ 3 ║
╚════════════════╩════════════════╩════════╩═════════════════╝
Now this query will generate all possible parent nodes for each node (a kind of a mapping table):
;WITH CTE (DepartmentCode, CostCentreCode, Parent, DepartmentLevelCode)
AS (
SELECT D.DepartmentCode, D.CostCentreCode, D.Parent, D.DepartmentCode
FROM dbo.Departments AS D
UNION ALL
SELECT D.DepartmentCode, D.CostCentreCode, D.Parent, C.DepartmentLevelCode
FROM dbo.Departments AS D
INNER JOIN CTE AS C
ON C.Parent = D.DepartmentCode
)
SELECT *
FROM CTE;
Which gives us this result:
╔════════════════╦════════════════╦════════╦═════════════════════╗
║ DepartmentCode ║ CostCentreCode ║ Parent ║ DepartmentLevelCode ║
╠════════════════╬════════════════╬════════╬═════════════════════╣
║ 1 ║ AAA ║ 0 ║ 1 ║
║ 2 ║ AAA ║ 1 ║ 2 ║
║ 3 ║ AAA ║ 1 ║ 3 ║
║ 4 ║ BBB ║ 0 ║ 4 ║
║ 5 ║ AAA ║ 3 ║ 5 ║
║ 3 ║ AAA ║ 1 ║ 5 ║
║ 1 ║ AAA ║ 0 ║ 5 ║
║ 1 ║ AAA ║ 0 ║ 3 ║
║ 1 ║ AAA ║ 0 ║ 2 ║
╚════════════════╩════════════════╩════════╩═════════════════════╝
Now we can combine these three buddies together with Employees table and get desired output:
;WITH CTE (DepartmentCode, CostCentreCode, Parent, DepartmentLevelCode)
AS (
SELECT D.DepartmentCode, D.CostCentreCode, D.Parent, D.DepartmentCode
FROM dbo.Departments AS D
UNION ALL
SELECT D.DepartmentCode, D.CostCentreCode, D.Parent, C.DepartmentLevelCode
FROM dbo.Departments AS D
INNER JOIN CTE AS C
ON C.Parent = D.DepartmentCode
)
SELECT E.ClockNo
, E.CostCentre
, C.Level1
, C.Level2
, C.Level3
, C.Level4
FROM dbo.Employees AS E
OUTER APPLY (
SELECT MAX(CASE WHEN DL.DepartmentLevel = 1 THEN C.DepartmentCode END)
, MAX(CASE WHEN DL.DepartmentLevel = 2 THEN C.DepartmentCode END)
, MAX(CASE WHEN DL.DepartmentLevel = 3 THEN C.DepartmentCode END)
, MAX(CASE WHEN DL.DepartmentLevel = 4 THEN C.DepartmentCode END)
FROM CTE AS C
INNER JOIN #DepartmentLevels AS DL
ON DL.DepartmentCode = C.DepartmentCode
WHERE C.DepartmentLevelCode = E.Department
) AS C(Level1, Level2, Level3, Level4);
It will give this:
╔═════════╦════════════╦════════╦════════╦════════╦════════╗
║ ClockNo ║ CostCentre ║ Level1 ║ Level2 ║ Level3 ║ Level4 ║
╠═════════╬════════════╬════════╬════════╬════════╬════════╣
║ 1 ║ AAA ║ ║ ║ ║ ║
║ 2 ║ AAA ║ 1 ║ 3 ║ ║ ║
║ 3 ║ BBB ║ ║ ║ ║ ║
║ 4 ║ BBB ║ 4 ║ ║ ║ ║
║ 5 ║ CCC ║ ║ ║ ║ ║
║ 6 ║ AAA ║ 1 ║ ║ ║ ║
║ 7 ║ AAA ║ 1 ║ 3 ║ 5 ║ ║
╚═════════╩════════════╩════════╩════════╩════════╩════════╝
This query will find coresponding DepartmentLevelCode based on DepartmentCode and will pivot stuff based on the DepartmentLevel. Hopefully it's right.

SQL - Group rows via criteria until exception is found

I am trying to add a Group column to a data set based on some criteria. For a simple example:
╔════╦══════╗
║ ID ║ DATA ║
╠════╬══════╣
║ 1 ║ 12 ║
║ 2 ║ 20 ║
║ 3 ║ 3 ║
║ 4 ║ 55 ║
║ 5 ║ 11 ║
╚════╩══════╝
Let's say our criteria is that the Data should be greater than 10. Then the result should be similar to:
╔════╦══════╦═══════╗
║ ID ║ DATA ║ GROUP ║
╠════╬══════╬═══════╣
║ 1 ║ 12 ║ 1 ║
║ 2 ║ 20 ║ 1 ║
║ 3 ║ 3 ║ 2 ║
║ 4 ║ 55 ║ 3 ║
║ 5 ║ 11 ║ 3 ║
╚════╩══════╩═══════╝
So, all the rows that satisfied the criteria until an exception to the criteria occurred became part of a group. The numbering of the group doesn't necessarily need to follow this pattern, I just felt like this was a logical/simple numbering to explain the solution I am looking for.
You can calculate the group identifier by finding each row where data <= 10. Then, the group identifier is simply the number of rows where that condition is true, before the given row.
select t.*,
(select count(*)
from t t2
where t2.id <= t.id and
t2.data <= 10
) as groupId
from t;
SQL Server 2012 has cumulative sum syntax. The statement would be simpler in that database:
select t.*,
sum(case when t2.data <= 10) over (order by id) as groupId
from t;
EDIT:
The above does not take into account that the values less than 10 are in their own group. The logic above is that they start a new group.
The following assigns a group id with this constraint:
select t.*,
((select 2*count(*)
from t t2
where t2.id < t.id and
t2.data <= 10
) + (case when t.id <= 10 then 1 else 0 end)
) as groupId
from t;
This can be done easily with a recursive query:
;WITH CTE
AS (SELECT *,
1 AS [GROUP]
FROM TABLEB
WHERE ID = 1
UNION ALL
SELECT T1.ID,
T1.DATA,
CASE
WHEN T1.DATA < 10 THEN T2.[GROUP] + 1
ELSE T2.[GROUP]
END [GROUP]
FROM TABLEB T1
INNER JOIN CTE T2
ON T1.ID = T2.ID + 1)
SELECT *
FROM CTE
A working example can be found on SQL Fiddle.
Good Luck!

Using a SQL Server table, I need to create a new table recursive?

I have a simple table of related items, like so (SQL Server db)
id Item Parent
1 2 5
2 4 5
3 5 12
4 6 2
5 10 6
I'd like to output a table that shows, for each Item a full path of all inter-related items (up to 4 "levels"), like so
id Item ParentL1 ParentL2 ParentL3 ParentL4
1 2 5 12
2 4 5 12
3 5 12
4 6 2 5 12
5 10 6 2 5 12
Thanks!
This is the simple approach.
SELECT id, t1.Item as Item,
t1.Parent as ParentL1,
t2.Parent as ParentL2,
t3.Parent as ParentL3,
t4.Parent as ParentL4
FROM Items t1
LEFT JOIN Items t2 ON t1.Parent = t2.Id
LEFT JOIN Items t3 ON t2.Parent = t3.Id
LEFT JOIN Items t4 ON t3.Parent = t4.Id
The follwoing query should do the trick
SELECT t1.id, t1.Item, t1.Parent [ParentL1], t2.Parent [ParentL2], t3.Parent [ParentL3], t4.Parent [ParentL4]
FROM MyTable t1
LEFT JOIN MyTable t2
ON t1.Parent = t2.Item
LEFT JOIN MyTable t3
ON t2.Parent = t3.Item
LEFT JOIN MyTable t4
ON t3.Parent = t4.Item
Used the following to create the test table, MyTable to confirm the resultset
CREATE TABLE MyTable
(
id Int IDENTITY,
Item Int,
Parent Int
)
INSERT MyTable
VALUES (2, 5),
(4, 5),
(5, 12),
(6, 2),
(10, 6)
Ok, even though the LEFT JOINs are the simplest way in this case (when only 4 levels of recursion are needed), this is another option using recursive CTEs (SQL Server 2005+):
;WITH CTE AS
(
SELECT *, 1 RecursionLevel
FROM YourTable
UNION ALL
SELECT B.id, A.Item, B.Parent, RecursionLevel + 1
FROM CTE A
INNER JOIN YourTable B
ON A.Parent = B.Item
)
SELECT Item,
MIN(CASE WHEN RecursionLevel = 1 THEN Parent END) ParentL1,
MIN(CASE WHEN RecursionLevel = 2 THEN Parent END) ParentL2,
MIN(CASE WHEN RecursionLevel = 3 THEN Parent END) ParentL3,
MIN(CASE WHEN RecursionLevel = 4 THEN Parent END) ParentL4
FROM CTE
WHERE RecursionLevel <= 4
GROUP BY Item
This is the result:
╔══════╦══════════╦══════════╦══════════╦══════════╗
║ Item ║ ParentL1 ║ ParentL2 ║ ParentL3 ║ ParentL4 ║
╠══════╬══════════╬══════════╬══════════╬══════════╣
║ 2 ║ 5 ║ 12 ║ NULL ║ NULL ║
║ 4 ║ 5 ║ 12 ║ NULL ║ NULL ║
║ 5 ║ 12 ║ NULL ║ NULL ║ NULL ║
║ 6 ║ 2 ║ 5 ║ 12 ║ NULL ║
║ 10 ║ 6 ║ 2 ║ 5 ║ 12 ║
╚══════╩══════════╩══════════╩══════════╩══════════╝
And here is a sqlfiddle with a demo of this.

Concatenate row record base on group by in SQL Server 2008

I have one table (tblproduct) with fields: dept, product, qty.
sample data below:
dept product qty
IT A 2
IT B 1
PU C 4
SAL D 1
SER D 2
SER A 4
I want to create stored pro in sql server with the result below:
product qty remark
A 6 IT=2,SER=4
B 1 IT=1
C 4 PU=4
D 3 SAL=1,SER=2
this is my stored pro
select product,
sum(qty)
from tblproduct
group by product
order by product
Pls. any help. thanks.
SELECT
[product], SUM(qty) Total_Qty,
STUFF(
(SELECT ',' + dept + '=' + CAST(qty AS VARCHAR(10))
FROM TableName
WHERE [product] = a.[product]
FOR XML PATH (''))
, 1, 1, '') AS Remark
FROM TableName AS a
GROUP BY [product]
SQLFiddle Demo
OUTPUT
╔═════════╦═══════════╦═════════════╗
║ PRODUCT ║ TOTAL_QTY ║ REMARK ║
╠═════════╬═══════════╬═════════════╣
║ A ║ 6 ║ IT=2,SER=4 ║
║ B ║ 1 ║ IT=1 ║
║ C ║ 4 ║ PU=4 ║
║ D ║ 3 ║ SAL=1,SER=2 ║
╚═════════╩═══════════╩═════════════╝
Please try:
SELECT product, SUM(qty) Qty,
STUFF(
(SELECT ','+b.dept+'='+CAST(qty as nvarchar(10))
FROM YourTable b where b.product=a.product
FOR XML PATH(''),type).value('.','nvarchar(max)'), 1, 1, '')
from YourTable a
group by product

Using GROUP BY and ORDER BY on an INNER JOIN SQL query

I am using the following query to group work times and expenses for clients from three tables, one for clients, one for work times and one for expenses:
SELECT a.*,
COALESCE(b.totalCount, 0) AS CountWork,
COALESCE(b.totalAmount, 0) AS WorkTotal,
COALESCE(c.totalCount, 0) AS CountExpense,
COALESCE(c.totalAmount, 0) AS ExpenseTotal
FROM clients A
LEFT JOIN
(
SELECT Client,
COUNT(*) totalCount,
SUM(Amount) totalAmount
FROM work_times
WHERE DATE BETWEEN '2013-01-01' AND '2013-02-01'
GROUP BY Client
) b ON a.Client = b.Client
LEFT JOIN
(
SELECT Client,
COUNT(*) totalCount,
SUM(Amount) totalAmount
FROM expenses
WHERE DATE BETWEEN '2013-01-01' AND '2013-02-01'
GROUP BY Client
) c ON a.Client = c.Client
WHERE b.Client IS NOT NULL OR
c.Client IS NOT NULL
You can see the query working in a fiddle here.
I am trying to amend this query so that there is a row for each client for each month sorted by month and then client. I am trying to do so with the following amended query:
SELECT a.*,
COALESCE(b.totalCount, 0) AS CountWork,
COALESCE(b.totalAmount, 0) AS WorkTotal,
COALESCE(c.totalCount, 0) AS CountExpense,
COALESCE(c.totalAmount, 0) AS ExpenseTotal
FROM clients A
LEFT JOIN
(
SELECT Client,
COUNT(*) totalCount,
SUM(Amount) totalAmount,
SUBSTR(Date, 1, 7) as Month
FROM work_times
GROUP BY Month,Client
ORDER BY Month
) b ON a.Client = b.Client
LEFT JOIN
(
SELECT Client,
COUNT(*) totalCount,
SUM(Amount) totalAmount,
SUBSTR(Date, 1, 7) as Month
FROM expenses
GROUP BY Month,Client
ORDER BY Month,Client
) c ON a.Client = c.Client
WHERE b.Client IS NOT NULL OR
c.Client IS NOT NULL
You can see the amended query in action here.
It's not working quite right though. Only one row is returned for Client B even though there is a work time in January 2013 and an expense in February 2013 (so there should be 2 rows) and it appears that the rows are being ordered by Client as opposed to Month. Could someone suggest how to amend the query to get the desired output which for the example on the second fiddle would be:
╔════════╦═══════════╦═══════════╦══════════════╦══════════════╗
║ CLIENT ║ COUNTWORK ║ WORKTOTAL ║ COUNTEXPENSE ║ EXPENSETOTAL ║
╠════════╬═══════════╬═══════════╬══════════════╬══════════════╣
║ A ║ 1 ║ 10 ║ 1 ║ 10 ║
║ B ║ 1 ║ 20 ║ 0 ║ 0 ║
║ A ║ 1 ║ 15 ║ 0 ║ 0 ║
║ B ║ 0 ║ 0 ║ 1 ║ 10 ║
║ C ║ 1 ║ 10 ║ 0 ║ 0 ║
╚════════╩═══════════╩═══════════╩══════════════╩══════════════╝
Unless I am missing something in the requirements, what you need to do is get a list of the clients and the dates and then join that to your subqueries. So your query will be:
SELECT a.*,
COALESCE(b.totalCount, 0) AS CountWork,
COALESCE(b.totalAmount, 0) AS WorkTotal,
COALESCE(c.totalCount, 0) AS CountExpense,
COALESCE(c.totalAmount, 0) AS ExpenseTotal
FROM
(
select distinct c.Client, d.Month
from clients c
cross join
(
select SUBSTR(Date, 1, 7) as Month
from work_times
union
select SUBSTR(Date, 1, 7) as Month
from expenses
) d
) A
LEFT JOIN
(
SELECT Client,
COUNT(*) totalCount,
SUM(Amount) totalAmount,
SUBSTR(Date, 1, 7) as Month
FROM work_times
GROUP BY Month,Client
ORDER BY Month,Client
) b
ON a.Client = b.Client
and a.month = b.month
LEFT JOIN
(
SELECT Client,
COUNT(*) totalCount,
SUM(Amount) totalAmount,
SUBSTR(Date, 1, 7) as Month
FROM expenses
GROUP BY Month,Client
ORDER BY Month,Client
) c
ON a.Client = c.Client
and a.month = c.month
WHERE b.Client IS NOT NULL OR
c.Client IS NOT NULL
order by a.month, a.client
See SQL Fiddle with Demo.
The result is:
| CLIENT | MONTH | COUNTWORK | WORKTOTAL | COUNTEXPENSE | EXPENSETOTAL |
--------------------------------------------------------------------------
| A | 2013-01 | 1 | 10 | 1 | 10 |
| B | 2013-01 | 1 | 20 | 0 | 0 |
| A | 2013-02 | 1 | 15 | 0 | 0 |
| B | 2013-02 | 0 | 0 | 1 | 20 |
| C | 2013-02 | 1 | 10 | 0 | 0 |
If you do an order by in a sub-query, it doesn't matter, because the outer query may (and may need to) re-order the results. You want to add an order by to the outer query.
Your problem is that you are trying to order by the month and client of the B table, and also order by the month and client of the C table. You need to define the order of B.month, B.client, and C.month and put it into an order by for the outer query.
BTW, if you only group by month in the sub-query for the C table, then client is not meaningful. Some databases, like DB2, do not allow you to put an unaggregated field in a select if it is not in the group by.