Dividing one SQL column into subgroups with headings - sql

I have two tables
STATUS
SNO | STATUS | DEPARTMENT_ID
1 In progress 1
2 Assigned 2
3 Quoted 2
4 Development 3
DEPARTMENTS
SNO | DEPARTMENT |
1 DESIGNING
2 MARKETING
3 PRODUCTION
Now I want a result like this using SQL stored procedure
Some Custom Column Name | DEPARTMENT_ID
DESIGNING -
In Progress 1
MARKETING -
Assigned 2
Quoted 2
PRODUCTION -
Development 3
The custom column will be used to populate a Telerik RadComboBox with DESIGNING, MARKETING and PRODUCTION acting as separators between statuses.

Select Department, -1 from Department_Table
Union
Select StatusName, Department_ID from Status_Table
Please elaborate your question so that we can provide better answer. Currently it seems you just want to return the joined data of both tables.

Often, this type of operation is more easily done at the application level. You can do it in SQL, using union all and order by, however:
select status as CustomColumnName, department
from ((select s.status, d.department, 1 as ordering
from status s join
departments d
on s.department_id = d.sno
) union all
(select d.department, NULL, 0 as ordering
from departments d
)
) dd
order by CustomColumnName, ordering;
Note: this treats the - as NULL.

Try this.Is it ok with other sample data ?
DECLARE #STATUS TABLE (
SNO INT
,[STATUS] VARCHAR(50)
,DEPARTMENT_ID INT
)
INSERT INTO #STATUS
VALUES (1,'In progress' ,1)
,(2,'Assigned',2)
,(3,'Quoted',2)
,(4,'Development',3)
DECLARE #DEPARTMENT TABLE (SNO INT,DEPARTMENT VARCHAR(50))
INSERT INTO #DEPARTMENT
VALUES ( 1,'DESIGNING'),(2,'MARKETING')
,(3,'PRODUCTION')
--select * from #STATUS
--select * from #DEPARTMENT
;
WITH CTE
AS (
SELECT DEPARTMENT [CustomeColumn]
,'-' DEPARTMENT_ID
,sno
FROM #DEPARTMENT
UNION ALL
SELECT [STATUS]
,cast(DEPARTMENT_ID AS VARCHAR(10))
,(
SELECT sno
FROM #DEPARTMENT
WHERE sno = a.DEPARTMENT_ID
)
FROM #STATUS A
)
SELECT *
FROM CTE
ORDER BY sno

Related

Select rows in one table, based on column values in another table?

I need to select one column for each matching row in another table. Sounds simple, but there's a twist.
I have one table that has Company IDs, and defines Companies.
I have another table that defines Departments in those companies.
I have a third table that contains the various Department StatusCodes.
The code that I have below works just fine, and is exactly what I want it to do. But I have to hardcode into the query which departments to look for. I'd like it to sub-select based on department codes it finds in the Departments table, and not require me to hard code each possible department that might exist at that company.
I need to select the matching department status from DepartmentStatus based upon what Departments exist for a company as defined in DepartmentsStatus table.
I suspect it's a pivot table, but they are a bit above my level.
(Company Table)
Company_ID Company_Name
-------------------------
1 Home Office
2 Stanton Office
3 NYC Office
(Departments Table)
CompanyID Department_Code
----------------------------
1 Sales
1 Inventory
1 Retail
1 Maint
2 OtherDept
2 ThatDept
2 BobsDept
(DepartmentStatus Table)
Company_ID Department StatusCode
-----------------------------------------
1 Sales InReview
1 Inventory InReview
1 Retail Ready
1 Maint Done
2 OtherDept InReview
2 ThatDept Research
2 BobsDept InReview
Note: I use "TOP 1", even though there is a unique index on Company_ID+Department, so there will never be more than one matching row.
So, for Company_ID=1:
select Company_ID,
(select top 1 StatusCode from DepartmentStatus ds where ds.Company_ID = cm.Company_ID and ds.Department='Sales') as SalesStatus
(select top 1 StatusCode from DepartmentStatus ds where ds.Company_ID = cm.Company_ID and ds.Department='Inventory') as InvStatus
(select top 1 StatusCode from DepartmentStatus ds where ds.Company_ID = cm.Company_ID and ds.Department='Retail') as RetailStatus
(select top 1 StatusCode from DepartmentStatus ds where ds.Company_ID = cm.Company_ID and ds.Department='Main') as MaintStatus
from Company cm
Where cm.CompanyID=1
Results:
Company_ID SalesStatus InvStatus RetailStatus MaintStatus
--------------- --------------- ---------- ------------- ------------
1 InReview InReview Ready Done
Or, for CompanyID=2:
select Company_ID,
(select top 1 StatusCode from DepartmentStatus ds where ds.CompanyID = cm.Company_ID and ds.Department='OtherDept') as OtherDeptStatus
(select top 1 StatusCode from DepartmentStatus ds where ds.CompanyID = cm.Company_ID and ds.Department='ThatDept') as ThatDeptStatus
(select top 1 StatusCode from DepartmentStatus ds where ds.CompanyID = cm.Company_ID and ds.Department='BobsDept') as BobsDeptStatus
from Company cm
Where cm.CompanyID=2
Results:
Company_ID OtherDeptStatus ThatDeptStatus BobsDeptStatus
---------- ---------------- -------------- --------------
2 InReview Research InReview
Thus, For Company 1, I need to go get the status for Departments Sales, Inventory, Retail, and Maint.
But for Company 2, I need to get the status for Departments OtherDept, ThatDept, and BobsDept.
The steps that I think describe what I am trying to do is:
Get list of Departments for Company 1 from Departments Table.
For each of these Departments in Step 1, query DepartmentStatus table for Department equals that department.
For Company 1, query get StatusCode for Departments "Sales, Inventory, Retail, and Maint"
For Company 2, query get StatusCode for Departments "OtherDept, thatDept, and BobsDept."
etc etc
The problem is, I do not know ahead of time (at query time) which departments exist for each Company, so I need to sub-select based upon what departments actually exist for each company.
There are a few other S.O. answers already that are close what I'm asking for, suggesting the use of a JOIN to accomplish this, however, they all assume that you know the values you want to query ahead of time when writing the join statement.
I'm looking for a solution to this problem, not just a correction on my current attempt. If you have a better idea entirely on how to accomplish this, I'd love to see it.
You seem to be looking for conditional aggregation. Using this technique, your query can be simplified as follows, to avoid the need for multiple inline subqueries:
select Company_ID,
max(case when ds.Department='Sales' then ds.StatusCode end) as SalesStatus,
max(case when ds.Department='Inventory' then ds.StatusCode end) as InvStatus,
max(case when ds.Department='Retail' then ds.StatusCode end) as RetailStatus,
max(case when ds.Department='Main' then ds.StatusCode end) as MaintStatus
from
Company cm
inner join DepartmentStatus ds on ds.Company_ID = cm.Company_ID
Where cm.CompanyID=1
group by cm.CompanyID
this would be my (untested) caveman way of solving your problem.
the only thing you need to know in advance is how many different departments you have in the company with the most departments.
DROP TABLE IF EXISTS Iddepartmentstatus;
DECLARE #Result TABLE(
Companyid INT,
Department1 NVARCHAR(MAX),
Department2 NVARCHAR(MAX),
Department3 NVARCHAR(MAX),
Department4 NVARCHAR(MAX),
Department5 NVARCHAR(MAX));
CREATE TABLE Iddepartmentstatus(
Num INT IDENTITY(1, 1),
Department NVARCHAR(MAX),
Statuscode NVARCHAR(MAX));
DECLARE #IDCompany TABLE(
Num INT IDENTITY(1, 1),
Companyid INT,
Companyname NVARCHAR(MAX));
INSERT INTO #IDCompany (Companyid,
Companyname)
SELECT Company_Id,
Company_Name
FROM Company;
DECLARE #Departmentcount INT = 0,
#Departmentstatuscount INT = 0,
#Companycount INT = 0;
WHILE #Companycount <=
(
SELECT MAX(Num)
FROM #IDCompany)
BEGIN
INSERT INTO Iddepartmentstatus (Department,
Statuscode)
SELECT Department,
Statuscode
FROM Departmentstatus
WHERE Company_Id = #Companycount;
INSERT INTO #Result (Companyid)
SELECT #Companycount AS T;
INSERT INTO #Result (Department1)
SELECT IIF(1 <=
(
SELECT MAX(Num)
FROM #IDCompany), Department + ': ' + Departmentstatus, NULL)
FROM Iddepartmentstatus
WHERE Num = 1;
INSERT INTO #Result (Department2)
SELECT IIF(2 <=
(
SELECT MAX(Num)
FROM #IDCompany), Department + ': ' + Departmentstatus, NULL)
FROM Iddepartmentstatus
WHERE Num = 2;
INSERT INTO #Result (Department3)
SELECT IIF(3 <=
(
SELECT MAX(Num)
FROM #IDCompany), Department + ': ' + Departmentstatus, NULL)
FROM Iddepartmentstatus
WHERE Num = 3;
INSERT INTO #Result (Department4)
SELECT IIF(4 <=
(
SELECT MAX(Num)
FROM #IDCompany), Department + ': ' + Departmentstatus, NULL)
FROM Iddepartmentstatus
WHERE Num = 4;
INSERT INTO #Result (Department5)
SELECT IIF(5 <=
(
SELECT MAX(Num)
FROM #IDCompany), Department + ': ' + Departmentstatus, NULL)
FROM Iddepartmentstatus
WHERE Num = 5;
TRUNCATE TABLE Iddepartmentstatus;
SET #Companycount = #Companycount + 1;
END;
DROP TABLE IF EXISTS Iddepartmentstatus;

T-SQL Execute Recursion for Each Value in Multiple Columns

I have a dataset with two columns. A person in the first column may be in the span of control of a person in the second column (i.e. everyone is in Michael's span of control, Dwight & Stanley are in Jim's span of control):
source_id source target_id target
1 Michael Scott 5 Kelly Kapoor
3 Dwight Schrute 2 Jim Halpert
4 Stanley Hudson 2 Jim Halpert
2 Jim Halpert 5 Kelly Kapoor
I have a table that lists each person and their supervisor:
person_id person supervisor_id supervisor
1 Michael Scott 0 None
2 Jim Halpert 1 Michael Scott
3 Dwight Schrute 2 Jim Halpert
4 Stanley Hudson 2 Jim Halpert
6 Ryan Howard 1 Michael Scott
5 Kelly Kapoor 6 Ryan Howard
I have a block of code that uses recursion to find a single person's span of control from the preceding table:
with anchor as
(
select person_id, supervisor_id from table where unique_id = #ID
union all
select a.person_id, a.supervisor_id
from table a
inner join Anchor b ON b.person_id = a.supervisor_id
)
select a.person_id
from anchor a
This block of code can be turned into a stored procedure or a function. Running it for Jim (for example), returns:
person_id
3 (Dwight Schrute)
4 (Stanley Hudson)
How do I compare each value in the initial dataset (from both the source and target columns) to the values in the column returned by the preceding block of code? In other words, for each row in source, I need to check if that name is within the span of control of target. In addition, for each row in target, I need to check if that name is within the span of control of source.
Desired End Result:
source_id source target_id target isEitherPersonInOther'sSOC
1 Michael Scott 5 Kelly Kapoor Yes
3 Dwight Schrute 2 Jim Halpert Yes
4 Stanley Hudson 2 Jim Halpert Yes
2 Jim Halpert 5 Kelly Kapoor No
I know iterations are bad (i.e. running the stored procedure for each row with a cursor or while loop). I also know cross apply and a function together may work, but I have been unable to figure my way through that.
Thank you for any insight you all may have!
What it looks like you need is to take your recursive CTE and output ID pairs into a temp table. I'm picturing something like this:
DECLARE #id int
DECLARE cx CURSOR FOR
SELECT person_id FROM PersonSupervisor
CREATE TABLE #tmpPS (Supervisor int, personInChain int)
OPEN cx
FETCH NEXT FROM cx
INTO #id
WHILE ##FETCH_STATUS = 0
BEGIN
with anchor as
(
select person_id, supervisor_id from table where unique_id = #ID
union all
select a.person_id, a.supervisor_id
from table a
inner join Anchor b ON b.person_id = a.supervisor_id
)
INSERT INTO #tmpPS
select #id, a.person_id
from anchor a
FETCH NEXT FROM cx
INTO #id
END
Close cx
Deallocate cx
This creates a table of all relationships, recursively expanded. Then you can output whether any given person is either above or below a given other person with this subquery. Add it to whatever query outputs your base grid:
SELECT
SourceId,
Source,
TargetId,
Target,
CASE
WHEN EXISTS (SELECT 1 FROM #tmpPS WHERE Supervisor = SourceId and PersonInChain = TargetId)
THEN 'Yes'
WHEN EXISTS (SELECT 1 FROM #tmpPS WHERE Supervisor = TargetId and PersonInChain = SourceId)
THEN 'Yes'
ELSE 'No'
END as [isEitherPersonInOther'sSOC]
FROM ....
This also implies a version of this where you can separate the relationships out - If the first query, the TargetId is a subordinate of the SourceId. If the second query, then TargetId is a superior to SourceId.
There has to be a better way to do this but this is definitely one way to go about it -- note the code creates permanent tables to use in a function:
create table dbo.[source]
(
source_id int,
[source] nvarchar(500),
target_id int,
[target] nvarchar(500)
)
insert into [source]
select 1, 'Michael Scott', 5, 'Kelly Kapoor'
union all select 3,'Dwight Schrute',2,'Jim Halpert'
union all select 4 ,'Stanley Hudson',2,'Jim Halpert'
union all select 2 ,'Jim Halpert',5,'Kelly Kapoor'
create table dbo.supervisors
(
person_id int,
person nvarchar(500),
supervisor_id int,
supervisor nvarchar(500)
)
insert into dbo.supervisors
select 1,'Michael Scott', 0,'None'
union all select 2,'Jim Halpert',1,'Michael Scott'
union all select 3,'Dwight Schrute',2,'Jim Halpert'
union all select 4,'Stanley Hudson',2,'Jim Halpert'
union all select 6,'Ryan Howard',1,'Michael Scott'
union all select 5 ,'Kelly Kapoor',6,'Ryan Howard'
go
create function dbo.fn_isinspanofcontrol
(
#sourceid int,
#targetid int
)
RETURNS varchar(1)
as
begin
declare #retVal varchar(1)
declare #tbl table
(
person_id int
)
;with anchor as
(
select person_id, supervisor_id from supervisors where person_id = #sourceid
union all
select a.person_id, a.supervisor_id
from supervisors a
inner join Anchor b ON b.person_id = a.supervisor_id
)
insert into #tbl
select a.person_id
from anchor a
where
a.person_id = #targetid
;with anchor as
(
select person_id, supervisor_id from supervisors where person_id = #targetid
union all
select a.person_id, a.supervisor_id
from supervisors a
inner join Anchor b ON b.person_id = a.supervisor_id
)
insert into #tbl
select a.person_id
from anchor a
where
a.person_id = #sourceid
if exists( select 1
from #tbl
)
begin
set #retVal = 'Y'
end
else
begin
set #retVal = 'N'
end
return #retVal
end
select
*, dbo.fn_isinspanofcontrol(source_id,target_id)
from [source]

PostgreSQL 9.3: Pivot table query

I want to show the pivot table(crosstab) for the given below table.
Table: Employee
CREATE TABLE Employee
(
Employee_Number varchar(10),
Employee_Role varchar(50),
Group_Name varchar(10)
);
Insertion:
INSERT INTO Employee VALUES('EMP101','C# Developer','Group_1'),
('EMP102','ASP Developer','Group_1'),
('EMP103','SQL Developer','Group_2'),
('EMP104','PLSQL Developer','Group_2'),
('EMP101','Java Developer',''),
('EMP102','Web Developer','');
Now I want to show the pivot table for the above data as shown below:
Expected Result:
Employee_Number TotalRoles TotalGroups Available Others Group_1 Group_2
---------------------------------------------------------------------------------------------------
EMP101 2 2 1 1 1 0
EMP102 2 2 1 1 1 0
EMP103 1 2 1 0 0 1
EMP104 1 2 1 0 0 1
Explanation: I want to show the Employee_Number, the TotalRoles which each employee has,
the TotalGroups which are present to all employees, the Available shows the employee available
in how many groups, the Others have to show the employee is available in other's also for which
the group_name have not assigned and finally the Group_Names must be shown in the pivot format.
SELECT * FROM crosstab(
$$SELECT grp.*, e.group_name
, CASE WHEN e.employee_number IS NULL THEN 0 ELSE 1 END AS val
FROM (
SELECT employee_number
, count(employee_role)::int AS total_roles
, (SELECT count(DISTINCT group_name)::int
FROM employee
WHERE group_name <> '') AS total_groups
, count(group_name <> '' OR NULL)::int AS available
, count(group_name = '' OR NULL)::int AS others
FROM employee
GROUP BY 1
) grp
LEFT JOIN employee e ON e.employee_number = grp.employee_number
AND e.group_name <> ''
ORDER BY grp.employee_number, e.group_name$$
,$$VALUES ('Group_1'::text), ('Group_2')$$
) AS ct (employee_number text
, total_roles int
, total_groups int
, available int
, others int
, "Group_1" int
, "Group_2" int);
SQL Fiddle demonstrating the base query, but not the crosstab step, which is not installed on sqlfiddle.com
Basics for crosstab:
PostgreSQL Crosstab Query
Special in this crosstab: all the "extra" columns. Those columns are placed in the middle, after the "row name" but before "category" and "value":
Pivot on Multiple Columns using Tablefunc
Once again, if you have a dynamic set of groups, you need to build this statement dynamically and execute it in a second call:
Selecting multiple max() values using a single SQL statement
You can use the crosstab function for this.
First of all you need to add the tablefunc extension if you haven't already:
CREATE EXTENSION tablefunc;
The crosstab functions require you to pass it a query returning the data you need to pivot, then a list of the columns in the output. (In other ways "tell me the input and the output format you want"). The sort order is important!
In your case, the input query is quite complicated - I think you need to do a load of separate queries, then UNION ALL them to get the desired data. I'm not entirely sure how you calculate the values "TotalGroups" and "Available", but you can modify the below in the relevant place to get what you need.
SELECT * FROM crosstab(
'SELECT employee_number, attribute, value::integer AS value FROM (with allemployees AS (SELECT distinct employee_number FROM employee) -- use a CTE to get distinct employees
SELECT employee_number,''attr_0'' AS attribute,COUNT(distinct employee_role) AS value FROM employee GROUP BY employee_number -- Roles by employee
UNION ALL
SELECT employee_number,''attr_1'' AS attribute,value from allemployees, (select count (distinct group_name) as value from employee where group_name <> '''') a
UNION ALL
SELECT employee_number,''attr_2'' AS attribute, COUNT(distinct group_name) AS value FROM employee where group_name <> '''' GROUP BY employee_number -- Available, do not know how this is calculate
UNION ALL
SELECT a.employee_number, ''attr_3'' AS attribute,coalesce(value,0) AS value FROM allemployees a LEFT JOIN -- other groups. Use a LEFT JOIN to avoid nulls in the output
(SELECT employee_number,COUNT(*) AS value FROM employee WHERE group_name ='''' GROUP BY employee_number) b on a.employee_number = b.employee_number
UNION ALL
SELECT a.employee_number, ''attr_4'' AS attribute,coalesce(value,0) AS value FROM allemployees a LEFT JOIN -- group 1
(SELECT employee_number,COUNT(*) AS value FROM employee WHERE group_name =''Group_1'' GROUP BY employee_number) b on a.employee_number = b.employee_number
UNION ALL
SELECT a.employee_number, ''attr_5'' AS attribute,coalesce(value,0) AS value FROM allemployees a LEFT JOIN -- group 2
(SELECT employee_number,COUNT(*) AS value FROM employee WHERE group_name =''Group_2'' GROUP BY employee_number) b on a.employee_number = b.employee_number) a order by 1,2')
AS ct(employee_number varchar,"TotalRoles" integer,"TotalGroups" integer,"Available" integer, "Others" integer,"Group_1" integer, "Group_2" integer)

Merge the common rows in SQL

I am getting the following output
Code Manager Employee
1 A Emp1
1 A Emp2
1 A Emp3
2 B Emp4
2 B Emp5
but I want result as
Code Manager Employee
1 A Emp1
Emp2
Emp3
2 B Emp4
Emp5
Code and manager columns should not repeat.It should be blank.
select case when Code = lag(Code) over(order by Code, Manager, Employee)
then null
else Code
end as Code,
case when Manager = lag(Manager) over(order by Code, Manager, Employee)
then null
else Manager
end as Manager,
Employee
from YourTable Y
order by Y.Code, Y.Manager, Y.Employee
Try on SQL Fiddle
You need something like Access or Crystal Reports to do this sort of formatting. Its not possible in plain SQL.
That is not possible by SQL. You should manually loop the data in code after receiving it from database.
Edit
After comments by Vashh and Lieven, I realized that it is possible. So if he needs for display purpose he can either use Func (suggested by Vaassh), Join with null (s. by Lieven) or may be loop and add to datagridview or table or whatever he wants to use.
For the fun of it, following is one way to do it but in the end, this is better done in the end-user application
;WITH YourTable (Code, Manager, Employee) AS(
SELECT * FROM (VALUES
(1, 'A', 'Emp1')
, (1, 'A', 'Emp2')
, (1, 'A', 'Emp3')
, (2, 'B', 'Emp4')
, (2, 'B', 'Emp5')
) a (b, c, d)
)
, q AS (
SELECT rn = ROW_NUMBER() OVER (ORDER BY Code, Manager, Employee), *
FROM YourTable
)
SELECT Code = CASE WHEN q1.Code = q2.Code THEN NULL ELSE q1.Code END
, Manager = CASE WHEN q1.Code = q2.Code THEN NULL ELSE q1.Manager END
, q1.Employee
FROM q q1
LEFT OUTER JOIN q q2 ON q1.rn = q2.rn + 1
I know you're asking for an answer in Oracle, but maybe this SQL Server example will help you (if you really, really need to do it like this and not in a reporting environment):
DECLARE #TBL TABLE(
Code INT,
Manager CHAR(1),
Employee VARCHAR(4))
INSERT #TBL VALUES (1,'A','Emp1')
INSERT #TBL VALUES (1,'A','Emp2')
INSERT #TBL VALUES (1,'A','Emp3')
INSERT #TBL VALUES (2,'B','Emp4')
INSERT #TBL VALUES (2,'B','Emp5')
;WITH cte
AS (SELECT Code
,Manager
,Employee
,ROW_NUMBER() OVER(ORDER BY Code) rownum
FROM #TBL)
SELECT CASE curr.Code
WHEN prev.Code THEN ''
ELSE CAST(curr.Code AS VARCHAR(20))
END AS _Code
,CASE curr.Manager
WHEN prev.Manager THEN ''
ELSE curr.Manager
END AS _Manager
,curr.Employee
FROM cte curr
LEFT JOIN cte prev
ON curr.rownum = prev.rownum + 1
If you're just using SQL Server 2005/2008, you can achieve this with the following.
declare #table table (
Code int,
Manager Varchar(1),
Employee varchar(10)
)
insert into #table values
(1,'A','Emp1'),
(1,'A','Emp2'),
(1,'A','Emp3'),
(2,'A','Emp4'),
(2,'A','Emp5')
select * from #table
select
case when number=1 then Code else null end as Code,
case when number=1 then Manager else null end as Manager,
employee
from (
select *,
row_number() over (partition by code, manager order by code,manager) as number
from #table
) x
Which will give you:
(5 row(s) affected)
Code Manager Employee
----------- ------- ----------
1 A Emp1
1 A Emp2
1 A Emp3
2 A Emp4
2 A Emp5
(5 row(s) affected)
Code Manager employee
----------- ------- ----------
1 A Emp1
NULL NULL Emp2
NULL NULL Emp3
2 A Emp4
NULL NULL Emp5
(5 row(s) affected)
Done:)
You can change NULL values to '' if you want to.
WITH
CTE1 AS (
SELECT DISTINCT [Code], [Manager],
( SELECT TOP 1 Employee
FROM [dbo].[table] t2
WHERE t1.Code = t2.Code AND t1.Manager = t2.Manager
ORDER BY Employee) AS [Employee]
FROM [dbo].[table] t1)
,
CTE2 AS (
SELECT * FROM [dbo].[table]
EXCEPT
SELECT * FROM CTE1)
SELECT * FROM CTE1
UNION
SELECT NULL as Code, NULL as Manager, Employee
FROM CTE2
ORDER BY Employee
My SQL*Plus is a little rusty but if that's the tool that you're using then it should be fairly simple but using the BREAK command.
As mentioned in one of the comments about, this is best left to your reporting tool rather than doing in the actual SQL because an individual row without all the values doesn't make any sense outside the context of the result set.
BREAK on code on manager
SELECT code, manager, employee
FROM yourTable y
order by code, manager;
Please not that my SQL*Plus is a little rusty so this might not work exactly but details of the BREAK command can be found in the Oracle documentation.

SUM by two different GROUP BY

I'm getting the wrong result from my report. Maybe i'm missing something simple.
The report is an inline table-valued-function that should count goods movement in our shop and how often these spareparts are claimed(replaced in a repair).
The problem: different spareparts in the shop-table(lets call it SP) can be linked to the same sparepart in the "repair-table"(TSP). I need the goods movement of every sparepart in SP and the claim-count of every distinct sparepart in TSP.
This is a very simplified excerpt of the relevant part:
create table #tsp(id int, name varchar(20),claimed int);
create table #sp(id int, name varchar(20),fiTsp int,ordered int);
insert into #tsp values(1,'1235-6044',300);
insert into #tsp values(2,'1234-5678',400);
insert into #sp values(1,'1235-6044',1,30);
insert into #sp values(2,'1235-6044',1,40);
insert into #sp values(3,'1235-6044',1,50);
insert into #sp values(4,'1234-5678',2,60);
WITH cte AS(
select tsp.id As TspID,tsp.name as TspName,tsp.claimed As Claimed
,sp.id As SpID,sp.name As SpName,sp.ordered As Ordered
from #sp sp inner join #tsp tsp
on sp.fiTsp=tsp.id
)
SELECT TspName, SUM(Claimed) As Claimed, Sum(Ordered) As Ordered
FROM cte
Group By TspName
drop table #tsp;
drop table #sp;
Result:
TspName Claimed Ordered
1234-5678 400 60
1235-6044 900 120
The Ordered-count is correct but the Claimed-count should be 300 instead of 900 for TspName='1235-6044'.
I need to group by Tsp.ID for the claim-count and group by Sp.ID for the order-count. But how in one query?
Edit: Actually the TVF looks like(note that getOrdered and getClaimed are SVFs and that i'm grouping in the outer select on TSP's Category):
CREATE FUNCTION [Gambio].[rptReusedStatistics](
#fromDate datetime
,#toDate datetime
,#fromInvoiceDate datetime
,#toInvoiceDate datetime
,#idClaimStatus varchar(50)
,#idSparePartCategories varchar(1000)
,#idSpareParts varchar(1000)
)
RETURNS TABLE AS
RETURN(
WITH ExclusionCat AS(
SELECT idSparePartCategory AS ID From tabSparePartCategory
WHERE idSparePartCategory IN(- 3, - 1, 6, 172,168)
), Report AS(
SELECT Cat.SparePartCategoryName AS Category
,TSP.SparePartDescription AS Part
,TSP.SparePartName AS PartNumber
,SP.Inventory
,Gambio.getGoodsIn(SP.idSparePart,#FromDate,#ToDate) GoodsIn
,Gambio.getOrdered(SP.idSparePart,#FromDate,#ToDate) Ordered
--,CASE WHEN TSP.idSparePart IS NULL THEN 0 ELSE
-- Gambio.getClaimed(TSP.idSparePart,#FromInvoiceDate,#ToInvoiceDate,#idClaimStatus,NULL)END AS Claimed
,CASE WHEN TSP.idSparePart IS NULL THEN 0 ELSE
Gambio.getClaimed(TSP.idSparePart,#FromInvoiceDate,#ToInvoiceDate,#idClaimStatus,1)END AS ClaimedReused
,CASE WHEN TSP.idSparePart IS NULL THEN 0 ELSE
Gambio.getCostSaving(TSP.idSparePart,#FromInvoiceDate,#ToInvoiceDate,#idClaimStatus)END AS Costsaving
FROM Gambio.SparePart AS SP
INNER JOIN tabSparePart AS TSP ON SP.fiTabSparePart = TSP.idSparePart
INNER JOIN tabSparePartCategory AS Cat
ON Cat.idSparePartCategory=TSP.fiSparePartCategory
WHERE Cat.idSparePartCategory NOT IN(SELECT ID FROM ExclusionCat)
AND (#idSparePartCategories IS NULL
OR TSP.fiSparePartCategory IN(
SELECT Item From dbo.Split(#idSparePartCategories,',')
)
)
AND (#idSpareParts IS NULL
OR TSP.idSparePart IN(
SELECT Item From dbo.Split(#idSpareParts,',')
)
)
)
SELECT Category
--, Part
--, PartNumber
, SUM(Inventory)As InventoryCount
, SUM(GoodsIn) As GoodsIn
, SUM(Ordered) As Ordered
--, SUM(Claimed) As Claimed
, SUM(ClaimedReused)AS ClaimedReused
, SUM(Costsaving) As Costsaving
, Count(*) AS PartCount
FROM Report
GROUP BY Category
)
Solution:
Thanks to Aliostad i've solved it by first grouping and then joining(actual TVF, reduced to a minimum):
WITH Report AS(
SELECT Cat.SparePartCategoryName AS Category
,TSP.SparePartDescription AS Part
,TSP.SparePartName AS PartNumber
,SP.Inventory
,SP.GoodsIn
,SP.Ordered
,Gambio.getClaimed(TSP.idSparePart,#FromInvoiceDate,#ToInvoiceDate,#idClaimStatus,1) AS ClaimedReused
,Gambio.getCostSaving(TSP.idSparePart,#FromInvoiceDate,#ToInvoiceDate,#idClaimStatus) AS Costsaving
FROM (
SELECT GSP.fiTabSparePart
,SUM(GSP.Inventory)AS Inventory
,SUM(Gambio.getGoodsIn(GSP.idSparePart,#FromDate,#ToDate))AS GoodsIn
,SUM(Gambio.getOrdered(GSP.idSparePart,#FromDate,#ToDate))AS Ordered
FROM Gambio.SparePart GSP
GROUP BY GSP.fiTabSparePart
)As SP
INNER JOIN tabSparePart TSP ON SP.fiTabSparePart = TSP.idSparePart
INNER JOIN tabSparePartCategory AS Cat
ON Cat.idSparePartCategory=TSP.fiSparePartCategory
)
SELECT Category
, SUM(Inventory)As InventoryCount
, SUM(GoodsIn) As GoodsIn
, SUM(Ordered) As Ordered
, SUM(ClaimedReused)AS ClaimedReused
, SUM(Costsaving) As Costsaving
, Count(*) AS PartCount
FROM Report
GROUP BY Category
You are JOINing first and then GROUPing by. You need to reverse it, GROUP BY first and then JOIN.
So here in my subquery, I group by first and then join:
select
claimed,
ordered
from
#tsp
inner JOIN
(select
fitsp,
SUM(ordered) as ordered
from
#sp
group by
fitsp) as SUMS
on
SUMS.fiTsp = id;
I think you just need to select Claimed and add it to the Group By in order to get what you are looking for.
WITH cte AS(
select tsp.id As TspID,tsp.name as TspName,tsp.claimed As Claimed
,sp.id As SpID,sp.name As SpName,sp.ordered As Ordered
from #sp sp inner join #tsp tsp
on sp.fiTsp=tsp.id )
SELECT TspName, Claimed, Sum(Ordered) As Ordered
FROM cte
Group By TspName, Claimed
Your cte is an inner join between tsp and sp, which means that the data you're querying looks like this:
SpID Ordered TspID TspName Claimed
1 30 1 1235-6044 300
2 40 1 1235-6044 300
3 50 1 1235-6044 300
4 60 2 1234-5678 400
Notice how TspID, TspName and Claimed all get repeated. Grouping by TspName means that the data gets grouped in two groups, one for 1235-6044 and one for 1234-5678. The first group has 3 rows on which to run the aggregate functions, the second group only one. That's why your sum(Claimed) will get you 300*3=900.
As Aliostad suggested, you should first group by TspID and do the sum of Ordered and then join to tsp.
No need to join, just subselect:
create table #tsp(id int, name varchar(20),claimed int);
create table #sp(id int, name varchar(20),fiTsp int,ordered int);
insert into #tsp values(1,'1235-6044',300);
insert into #tsp values(2,'1234-5678',400);
insert into #sp values(1,'1235-6044',1,30);
insert into #sp values(2,'1235-6044',1,40);
insert into #sp values(3,'1235-6044',1,50);
insert into #sp values(4,'1234-5678',2,60);
WITH cte AS(
select tsp.id As TspID,tsp.name as TspName,tsp.claimed As Claimed
,sp.id As SpID,sp.name As SpName,sp.ordered As Ordered
from #sp sp inner join #tsp tsp
on sp.fiTsp=tsp.id
)
SELECT id, name, SUM(claimed) as Claimed, (SELECT SUM(ordered) FROM #sp WHERE #sp.fiTsp = #tsp.id GROUP BY #sp.fiTsp) AS Ordered
FROM #tsp
GROUP BY id, name
drop table #tsp;
drop table #sp;
Produces:
id name Claimed Ordered
1 1235-6044 300 120
2 1234-5678 400 60
-- EDIT --
Based on the additional info, this is how I might try to split the CTE to form the data as per the example. I fully admit that Aliostad's approach may yield a cleaner query but here's an attempt (completely blind) using the subselect:
CREATE FUNCTION [Gambio].[rptReusedStatistics](
#fromDate datetime
,#toDate datetime
,#fromInvoiceDate datetime
,#toInvoiceDate datetime
,#idClaimStatus varchar(50)
,#idSparePartCategories varchar(1000)
,#idSpareParts varchar(1000)
)
RETURNS TABLE AS
RETURN(
WITH ExclusionCat AS (
SELECT idSparePartCategory AS ID From tabSparePartCategory
WHERE idSparePartCategory IN(- 3, - 1, 6, 172,168)
), ReportSP AS (
SELECT fiTabSparePart
,Inventory
,Gambio.getGoodsIn(idSparePart,#FromDate,#ToDate) GoodsIn
,Gambio.getOrdered(idSparePart,#FromDate,#ToDate) Ordered
FROM Gambio.SparePart
), ReportTSP AS (
SELECT TSP.idSparePart
,Cat.SparePartCategoryName AS Category
,TSP.SparePartDescription AS Part
,TSP.SparePartName AS PartNumber
,CASE WHEN TSP.idSparePart IS NULL THEN 0 ELSE
Gambio.getClaimed(TSP.idSparePart,#FromInvoiceDate,#ToInvoiceDate,#idClaimStatus,1)END AS ClaimedReused
,CASE WHEN TSP.idSparePart IS NULL THEN 0 ELSE
Gambio.getCostSaving(TSP.idSparePart,#FromInvoiceDate,#ToInvoiceDate,#idClaimStatus)END AS Costsaving
FROM tabSparePart AS TSP
INNER JOIN tabSparePartCategory AS Cat
ON Cat.idSparePartCategory=TSP.fiSparePartCategory
WHERE Cat.idSparePartCategory NOT IN(SELECT ID FROM ExclusionCat)
AND (#idSparePartCategories IS NULL
OR TSP.fiSparePartCategory IN(
SELECT Item From dbo.Split(#idSparePartCategories,',')
)
)
AND (#idSpareParts IS NULL
OR TSP.idSparePart IN(
SELECT Item From dbo.Split(#idSpareParts,',')
)
)
)
SELECT Category
--, Part
--, PartNumber
, (SELECT SUM(Inventory) FROM ReportSP WHERE ReportSP.fiTabSparePart = idSparePart GROUP BY fiTabSparePart) AS Inventory
, (SELECT SUM(GoodsIn) FROM ReportSP WHERE ReportSP.fiTabSparePart = idSparePart GROUP BY fiTabSparePart) AS GoodsIn
, (SELECT SUM(Ordered) FROM ReportSP WHERE ReportSP.fiTabSparePart = idSparePart GROUP BY fiTabSparePart) AS Ordered
, Claimed
, ClaimedReused
, Costsaving
, Count(*) AS PartCount
FROM ReportTSP
GROUP BY Category
)
Without a better understanding of the whole schema it's difficult to cover for all the eventualities but whether this works or not (I suspect PartCount will be 1 for all instances) hopefully it'll give you some fresh thoughts for alternate approaches.
SELECT
tsp.name
,max(tsp.claimed) as claimed
,sum(sp.ordered) as ordered
from #sp sp
inner join #tsp tsp
on sp.fiTsp=tsp.id
GROUP BY tsp.name