Grouping Hierarchical data (parentID+ID) and running sum? - sql

I have the following data:
ID parentID Text Price
1 Root
2 1 Flowers
3 1 Electro
4 2 Rose 10
5 2 Violet 5
6 4 Red Rose 12
7 3 Television 100
8 3 Radio 70
9 8 Webradio 90
I am trying to group this data with Reporting Services 2008 and have a sum of the price per group of level 1 (Flowers/Electro) and for level 0 (Root).
I have a table grouped on [ID] with a recursive parent of [parendID] and I am able to calculate the sum for the level 0 (just one more row in the table outside the group), but somehow I am not able to create sum's per group as SRSS does "create" groups per level. My desired result looks like so:
ID Text Price
1 Root
|2 Flowers
|-4 Rose 10
|-5 Violet 5
| |-6 Red Rose 12
| Group Sum-->27
|3 Electro
|-7 Television 100
|-8 Radio 70
|-9 Webradio 90
Group Sum-->260
----------------------
Total 287
(indentation of ID just added for level clarification)
With my current approach I cannot get the group sums, so I figured out I would need the following data structure:
ID parentID Text Price level0 level1 level2 level3
1 Root 1
2 1 Flowers 1 1
3 1 Electro 1 2
4 2 Rose 10 1 1 1
5 2 Violet 5 1 1 2
6 4 Red Rose 12 1 1 1 1
7 3 Television 100 1 2 1
8 3 Radio 70 1 2 2
9 8 Webradio 90 1 2 2 1
When having the above structure I can create an outer grouping of level0, with child groupings level1, level2, level3 accordingly . When now having a "group sum" on level1, and the total sum outside the group I have EXACTLY what I want.
My question is the following:
How do I either achieve my desired result with my current data structure, or how do I convert my current data structure (outer left joins?) into the "new data structure" temporarily - so I can run my report off of the temp table?
Thanks for taking your time,
Dennis

WITH q AS
(
SELECT id, parentId, price
FROM mytable
UNION ALL
SELECT p.id, p.parentID, q.price
FROM q
JOIN mytable p
ON p.id = q.parentID
)
SELECT id, SUM(price)
FROM q
GROUP BY
id
Update:
A test script to check:
DECLARE #table TABLE (id INT NOT NULL PRIMARY KEY, parentID INT, txt VARCHAR(200) NOT NULL, price MONEY)
INSERT
INTO #table
SELECT 1, NULL, 'Root', NULL
UNION ALL
SELECT 2, 1, 'Flowers', NULL
UNION ALL
SELECT 3, 1, 'Electro', NULL
UNION ALL
SELECT 4, 2, 'Rose', 10
UNION ALL
SELECT 5, 2, 'Violet', 5
UNION ALL
SELECT 6, 4, 'Red Rose', 12
UNION ALL
SELECT 7, 3, 'Television', 100
UNION ALL
SELECT 8, 3, 'Radio', 70
UNION ALL
SELECT 9, 8, 'Webradio', 90;
WITH q AS
(
SELECT id, parentId, price
FROM #table
UNION ALL
SELECT p.id, p.parentID, q.price
FROM q
JOIN #table p
ON p.id = q.parentID
)
SELECT t.*, psum
FROM (
SELECT id, SUM(price) AS psum
FROM q
GROUP BY
id
) qo
JOIN #table t
ON t.id = qo.id
Here's the result:
1 NULL Root NULL 287,00
2 1 Flowers NULL 27,00
3 1 Electro NULL 260,00
4 2 Rose 10,00 22,00
5 2 Violet 5,00 5,00
6 4 Red Rose 12,00 12,00
7 3 Television 100,00 100,00
8 3 Radio 70,00 160,00
9 8 Webradio 90,00 90,00

I found a really ugly way to do what I want - maybe there is something better?
SELECT A.Text, A.Price,
CASE
WHEN D.Text IS NULL
THEN
CASE
WHEN C.Text IS NULL
THEN
CASE
WHEN B.Text IS NULL
THEN
A.ID
ELSE B.ID
END
ELSE C.ID
END
ELSE D.ID
END
AS LEV0,
CASE
WHEN D.Text IS NULL
THEN
CASE
WHEN C.Text IS NULL
THEN
CASE
WHEN B.Text IS NULL
THEN
NULL
ELSE A.ID
END
ELSE B.ID
END
ELSE C.ID
END
AS LEV1,
CASE
WHEN D.Text IS NULL
THEN
CASE
WHEN C.Text IS NULL
THEN
NULL
ELSE A.ID
END
ELSE B.ID
END
AS LEV2,
CASE
WHEN D.Text IS NULL
THEN NULL
ELSE A.ID
END
AS LEV3
FROM dbo.testOld AS A LEFT OUTER JOIN
dbo.testOld AS B ON A.parentID = B.ID LEFT OUTER JOIN
dbo.testOld AS C ON B.parentID = C.ID LEFT OUTER JOIN
dbo.testOld AS D ON C.parentID = D.ID
Output of this is:
Text Price LEV0 LEV1 LEV2 LEV3
---------- ----------- ----------- ----------- ----------- -----------
Root NULL 1 NULL NULL NULL
Flowers NULL 1 3 NULL NULL
Electro NULL 1 4 NULL NULL
Television 100 1 4 5 NULL
Radio 70 1 4 6 NULL
Rose 10 1 3 7 NULL
Violet 5 1 3 8 NULL
Webradio 90 1 4 5 14
Red Rose 12 1 3 7 15
With this structure I can go ahead and create 4 nested groups on the LEV0-3 columns including subtotals per group (as shown above in my desired result).

Related

SQL - how to combine sequential rows based on next row

There is a table I need to join back to itself to created a purchased parts report. But, I keep getting duplicated rows.
How to create a sql that will combine rows based on value of the next row?
Order No and Order Line fields uniquely identify a row in a sales order.
CP_COMP_SEQ is simply a list of all the components needed to manufacture Sales Order and Order Line.
MFG_PURCH_FLG is a flag 'M' means it's a Make part. 'P' means it's a part we need to Purchase.
If an M row follows another M row, then the first component has no purchased parts. If one or more P row(s) follow an M row, then all those sequential rows need to be purchased. Meaning we need to add them to a Purchase Parts report for a buyer to fill out a Purchase Order.
So, in the image below, Row 1 needs two purchased parts to complete. Row 4 doesn't need any purchased parts (because it's followed by another M row). Row 5 needs three purchased parts. And row 9 doesn't need any purchased parts.
Source table
order_no
order_line_no
cp_comp_seq
inv_item
mfg_purch_flg
qty
1
2
1
146FV
M
2
1
2
2
2085
P
4
1
2
3
2095
P
4
1
2
4
ZBAR007
M
1
1
2
5
1467V
M
1
1
2
6
2085
P
2
1
2
7
2095
P
2
1
2
8
3060
P
1
1
2
9
ZBAR007
M
1
2
1
1
xxx
x
x
2
1
2
xxx
x
x
I would like to have the results be one row per item that needs to be purchased based on the previous row that is a 'M' make item. Below is what I would like as the result:Result table
order_no
order_line_no
cp_comp_seq
inv_item
purchase_item
qty
1
2
1
146FV
2085
4
1
2
1
146FV
2095
4
1
2
5
147FV
2085
2
1
2
5
147FV
2095
2
1
2
5
147FV
3060
1
Any help would be greatly appreciated! Thanks in advance.
I tried joining based on rownumber - 1, but that doesn't stop when the Purchase flag changes. I tried looking at Run Groups but was unable to make that work as well.
Any help would be greatly appreciated! Thanks in advance.
You could use analytic function to get the sequence number of M flag of every P flag:
Select t.ORDER_NO, t.ORDER_LINE_NO, t.CP_COMP_SEQ, t.INV_ITEM, t.MFG_PURCH_FLG, t.QTY,
MAX(CASE WHEN t.MFG_PURCH_FLG = 'M' THEN t.CP_COMP_SEQ END) OVER(Order By t.CP_COMP_SEQ Rows Between Unbounded Preceding And Current Row) "M_SEQ"
From tbl t
ORDER_NO ORDER_LINE_NO CP_COMP_SEQ INV_ITEM MFG_PURCH_FLG QTY M_SEQ
---------- ------------- ----------- -------- ------------- --- ----------
1 2 1 146FV M 2 1
1 2 2 2085 P 4 1
1 2 3 2095 P 4 1
1 2 4 ZBAR007 M 1 4
1 2 5 1467V M 1 5
1 2 6 2085 P 2 5
1 2 7 2095 P 2 5
1 2 8 3060 P 1 5
1 2 9 ZBAR007 M 1 9
2 1 10 xxx x x 9
2 1 11 xxx x x 9
If you put it as a subquery in the FROM clause like here:
SELECT t.ORDER_NO, t.ORDER_LINE_NO,
(Select CP_COMP_SEQ From tbl Where CP_COMP_SEQ = t.M_SEQ) "CP_COMP_SEQ",
(Select INV_ITEM From tbl Where CP_COMP_SEQ = t.M_SEQ) "INV_ITEM",
t.INV_ITEM "PURCHASE_ITEM", t.QTY
FROM ( Select t.ORDER_NO, t.ORDER_LINE_NO, t.CP_COMP_SEQ, t.INV_ITEM, t.MFG_PURCH_FLG, t.QTY,
MAX(CASE WHEN t.MFG_PURCH_FLG = 'M' THEN t.CP_COMP_SEQ END) OVER(Order By t.CP_COMP_SEQ Rows Between Unbounded Preceding And Current Row) "M_SEQ"
From tbl t
) t
WHERE t.MFG_PURCH_FLG = 'P'
ORDER BY t.CP_COMP_SEQ
... then, with your sample data ...
WITH
tbl (ORDER_NO, ORDER_LINE_NO, CP_COMP_SEQ, INV_ITEM, MFG_PURCH_FLG, QTY) AS
(
Select 1, 2, 1, '146FV', 'M', '2' From Dual Union All
Select 1, 2, 2, '2085', 'P', '4' From Dual Union All
Select 1, 2, 3, '2095', 'P', '4' From Dual Union All
Select 1, 2, 4, 'ZBAR007', 'M', '1' From Dual Union All
Select 1, 2, 5, '1467V', 'M', '1' From Dual Union All
Select 1, 2, 6, '2085', 'P', '2' From Dual Union All
Select 1, 2, 7, '2095', 'P', '2' From Dual Union All
Select 1, 2, 8, '3060', 'P', '1' From Dual Union All
Select 1, 2, 9, 'ZBAR007', 'M', '1' From Dual Union All
Select 2, 1, 10, 'xxx', 'x', 'x' From Dual Union All
Select 2, 1, 11, 'xxx', 'x', 'x' From Dual
)
... the result would be:
ORDER_NO ORDER_LINE_NO CP_COMP_SEQ INV_ITEM PURCHASE_ITEM QTY
---------- ------------- ----------- -------- ------------- ---
1 2 1 146FV 2085 4
1 2 1 146FV 2095 4
1 2 5 1467V 2085 2
1 2 5 1467V 2095 2
1 2 5 1467V 3060 1
Take a look at the cross apply syntax. This syntax allows you to drive the inner query off each row in the other query.
select *
from AnyTableYouWant T1
cross apply
(
select top 1 ID from AnyTableYouWant T2
where T1.ID = T2.ID
order by T2.ID desc
) T3
T1 or T2 can be the same or different tables. As long as you come up with join criteria. A cross apply is different from a typical left, right or inner join. There's plenty of literature on these queries. This answer is not meant to be a diatribe on how it works.
After reading this question more closely, it would benefit from just walking through a result set sequentially and not trying to do it all in SQL. If you want to do it all in SQL, lookup up CURSORs. For maintenance reasons using python or C# or any other procedural language. Query all the records from the parts table make sure it's sorted the way you have it listed, then walk through the results one record at a time and apply the business logic you described.

SQL - Query to identify the parentid in a hierarchy with some conditions

I have a table in SQL azure database with a hierarchy of parents and i want to identify in the total hierarchy of the parents the parent id for each item that meets some conditions.
As an example this could be a great example
categoryId parentId Typeid
1 null 1
2 1 2
3 2 3
4 3 4
5 3 4
6 null 5
7 6 1
8 7 2
9 8 3
10 9 4
11 9 4
And I want to find for each categoryID the ParentId in the hierarchy that the type is 1 and if this category is the type 1 put the same category, in order to receive something like this.
categoryId parentId Typeid ParentSearch
1 null 1 1
2 1 2 1
3 2 3 1
4 3 4 1
5 3 4 1
6 null 5 null
7 6 1 7
8 7 2 7
9 8 3 7
10 9 4 7
11 9 4 7
As you can see all the categories from 1 to 5 the parent with type 1 is the category 1
and for the categories from 6 to 11, the 6 need to be null and the rest the parent is 7.
It could be possible?
Maybe creating a path or something. i have done doing several left joins at leves and it is fine but i dont want to create 500leves in order to be sure that we never have 500 levels childs.
Thanks and regards!
A recursive CTE is your friend:
WITH parents AS
(SELECT categoryid, parentid, typeid
, CASE WHEN typeid = 1 THEN categoryid ELSE NULL END AS parentsearch
FROM mytable
WHERE parentid IS NULL
UNION ALL
SELECT c.categoryid, c.parentid, c.typeid
, coalesce(p.parentsearch, CASE WHEN c.typeid = 1 THEN c.categoryid ELSE null END)
FROM mytable AS c
JOIN parents AS p ON c.parentid = p.categoryid)
SELECT * FROM parents ORDER BY categoryid;
categoryid parentid typeid parentsearch
---------- ---------- ---------- ------------
1 (null) 1 1
2 1 2 1
3 2 3 1
4 3 4 1
5 3 4 1
6 (null) 5 (null)
7 6 1 7
8 7 2 7
9 8 3 7
10 9 4 7
11 9 4 7
Sql Server db<>fiddle example.
Another solution that uses a recursive CTE.
But this one seeds from the childs.
WITH RCTE AS
(
SELECT t.categoryId, t.parentId, t.typeId,
0 as lvl,
t.categoryId as nextCategoryId,
t.parentId as nextParentId,
t.typeId as nextTypeId
FROM CategoryRelations t
UNION ALL
SELECT c.categoryId, c.parentId, c.typeId,
c.lvl + 1,
t.categoryId,
t.parentId,
t.typeId
FROM RCTE c
JOIN CategoryRelations t ON t.categoryId = c.nextParentId
WHERE c.typeId != 1
)
SELECT
c.categoryId,
c.parentId,
c.typeId,
(case when c.nextTypeId = 1 then c.nextCategoryId end) as ParentSearch
FROM RCTE c
WHERE (c.nextTypeId = 1 or c.parentId is null)
ORDER BY categoryId;
A test on rextester here

SQL recursive hierarchy

I am struggling to get one recursive CTE to work as desired but still with no chance..
So, I have the following similar table structures:
tblMapping:
map_id | type_id | name | parent_id
1 1 A1 0
2 1 A2 0
3 1 A3 1
4 1 A4 3
5 2 B1 0
6 2 B2 5
7 2 B3 6
8 1 A5 4
9 2 B4 0
tblRoleGroup:
role_group_id | type_id | map_id | desc_id
1 1 0 null
1 2 0 null
2 1 3 1
2 2 6 0
3 1 8 1
3 2 9 1
In tblRoleGroup, the desc_id field means:
null - allow all (used only in combination with map_id=0)
0 - allow all from parent including parent
1 - allow only current node
Still in tblRoleGroup if map_id=0 then the query should get all elements from same type_id
The query result should look like this:
role_group_id | type_id | map_id | path
1 1 1 A1
1 1 2 A2
1 1 3 A1.A3
1 1 4 A1.A3.A4
1 1 8 A1.A3.A4.A5
1 2 5 B1
1 2 6 B1.B2
1 2 7 B1.B2.B3
1 2 9 B4
2 1 3 A1.A3
2 2 6 B1.B2
2 2 7 B1.B2.B3
3 1 8 A1.A3.A4.A5
3 2 9 B4
The query below solves only a part of the expected result, but I wasn't able to make it work as the expected result..
WITH Hierarchy(map_id, type_id, name, Path) AS
(
SELECT t.map_id, t.type_id, t.name, CAST(t.name AS varchar(MAX)) AS Expr1
FROM dbo.tblMapping AS t
LEFT JOIN dbo.tblMapping AS t1 ON t1.map_id = t.parent_id
WHERE (t1.parent_id=0)
UNION ALL
SELECT t.map_id, t.type_id, t.name, CAST(h.Path + '.' + t.name AS varchar(MAX)) AS Expr1
FROM Hierarchy AS h
JOIN dbo.tblMapping AS t ON t.parent_id = h.map_id
)
SELECT h.map_id, h.type_id, t.role_group_id, h.Path AS Path
FROM Hierarchy AS h
LEFT JOIN dbo.tblRoleGroup t ON t.map_id = h.map_id
Could someone help me on this?
Thank you
At first I create a function that brings all descendants of passed map_id:
CREATE FUNCTION mapping (#map_id int)
RETURNS TABLE
AS
RETURN
(
WITH rec AS (
SELECT map_id,
[type_id],
CAST(name as nvarchar(max)) as name,
parent_id
FROM tblMapping
WHERE map_id = #map_id
UNION ALL
SELECT m.map_id,
m.[type_id],
r.name+'.'+m.name,
m.parent_id
FROM rec r
INNER JOIN tblMapping m
ON m.parent_id = r.map_id
)
SELECT *
FROM rec
);
GO
Then run this:
;WITH rec AS (
SELECT map_id,
[type_id],
CAST(name as nvarchar(max)) as name,
parent_id
FROM tblMapping
WHERE parent_id=0
UNION ALL
SELECT m.map_id,
m.[type_id],
r.name+'.'+m.name,
m.parent_id
FROM rec r
INNER JOIN tblMapping m
ON m.parent_id = r.map_id
)
SELECT t.role_group_id,
r.[type_id],
r.map_id,
r.name as [path]
FROM tblRoleGroup t
CROSS JOIN rec r
WHERE r.[type_id] = CASE WHEN t.desc_id IS NULL AND t.map_id = 0 THEN t.[type_id] ELSE NULL END
OR r.map_id = CASE WHEN t.desc_id = 1 THEN t.map_id ELSE NULL END
OR r.map_id IN (
SELECT map_id
FROM dbo.mapping (CASE WHEN t.desc_id = 0 THEN t.map_id ELSE NULL END)
)
ORDER BY role_group_id, r.[type_id], r.map_id
Will give you:
role_group_id type_id map_id path
1 1 1 A1
1 1 2 A2
1 1 3 A1.A3
1 1 4 A1.A3.A4
1 1 8 A1.A3.A4.A5
1 2 5 B1
1 2 6 B1.B2
1 2 7 B1.B2.B3
1 2 9 B4
2 1 3 A1.A3
2 2 6 B1.B2
2 2 7 B1.B2.B3
3 1 8 A1.A3.A4.A5
3 2 9 B4

Sum Amount, display full resultset where Groups of Column Values Match in Same Table Oracle SQL

I need to get the sum of all TOTAL fields where the ID, RECNO and DRCR fields have the same value, while also displaying these fields in the result set.
eg
ID RECNO SECRECNO DRCR TOTAL
1 9 5 D 25
1 9 12 D 22
1 9 6 C 33
1 9 5 D 50
1 8 2 D 12
1 8 2 C 23
2 9 5 D 100
So the results of the query should be
ID RECNO SECRECNO DRCR SUM(TOTAL)
1 9 5 D 75
1 9 12 D 22
1 9 6 C 33
1 8 2 D 12
1 8 2 C 23
2 9 5 D 100
This query will give the results set, without the TOTAL:
select distinct t1.recno, t1.secrecno
from table t1, table t2
where t1.recno = t2.recno and t.id = '1' and t1.drcr = 'D'
But I can't see how to SUM the TOTAL of these results.
How to do this?
select t1.id,
t1.recno,
t1.secrecno,
t1.drcr,
SUM( TOTAL )
from table t1
INNER JOIN
table t2
ON ( t1.recno = t2.recno )
WHERE t1.id = '1'
AND t1.drcr = 'D'
GROUP BY
t1.id,
t1.recno,
t1.secrecno,
t1.drcr

SQL join multiple tables with/without data

I have no idea how to create an SQL statement to join 4 tables.
1) The 'Vendor Table will always match entries from each table on Vendor #
2) Each of the remaining 3 will match to each other by Vendor # & Seq #
3) Any combination of the 3 can have data (or not)
4) I don't want to select from the Vendor table unless I get a hit on at least one of the 3
VENDOR
Vendor # Name
-------- ----
1 Tom Smith
2 Bruce Lee
3 Seamus O’Leary
4 Jonathan Stewart
5 Benjamin Franklin
Month Range Selected
Vendor # Seq # MonthFrom MonthTo
-------- ----- --------- -------
1 1 3 6
1 2 7 9
3 2 5 6
Week Selected
Vendor # Seq # Week #
-------- ----- ------
1 1 3
3 1 4
4 1 1
Day Selected
Vendor # Seq # Day #
1 1 15
1 2 25
2 1 12
4 1 05
5 1 19
Desired Table (Joined)
Vendor# Name Seq# MonthFrom MonthTo Week# Day#
1 Tom Smith 1 3 6 3 15
1 Tom Smith 2 7 9 NULL 25
2 Bruce Lee 1 NULL NULL NULL 12
3 Seamus O’Leary 1 NULL NULL 4 NULL
3 Seamus O’Leary 2 5 6 NULL NULL
4 Jonathan Stewart 1 NULL NULL 1 05
5 Benjamin Franklin 1 NULL NULL NULL 19
The trick being that any of the 3 (not including 'Vendor') can or cannot have data and I only want a row returned if there is something from one or more of the 3.
Any Advice?
To join it on Vendor and Seq, we first need to have all possible combinations. Then we can filter the tables based on these combinations. I've ran the following in SQL server:
Setup
declare #Vendors table(id int, name varchar(20));
declare #MonthRangeSelected table (vendor int, seq int null, monthFrom int null, monthTo int null);
declare #WeekSelected table (vendor int, seq int null, week int null);
declare #DaySelected table (vendor int, seq int null, day int null);
insert into #Vendors
select 1, 'Tom Smith'
union all
select 2, 'Bruce Lee'
union all
select 3, 'Seamus O’Leary'
union all
select 4, 'Jonathan Stewart'
union all
select 5, 'Benjamin Franklin';
insert into #MonthRangeSelected
select 1, 1, 3, 6
union all
select 1, 2, 7, 9
union all
select 3, 2, 5, 6;
insert into #WeekSelected
select 1, 1, 3
union all
select 3, 1, 4
union all
select 4, 1, 1;
insert into #DaySelected
select 1, 1, 15
union all
select 1, 2, 25
union all
select 2, 1, 12
union all
select 4, 1, 05
union all
select 5, 1, 19;
Query
select v.Id, v.name, combinations.seq, MonthFrom, MonthTo, Week, Day
from #Vendors v
inner join (select m.vendor, m.seq
from #MonthRangeSelected m
union
select w.vendor, w.seq
from #WeekSelected w
union
select d.vendor, d.seq
from #DaySelected d) combinations
on combinations.vendor = v.id
left join #MonthRangeSelected m
on m.Vendor = combinations.vendor
and m.seq = combinations.seq
left join #WeekSelected w
on w.Vendor = combinations.vendor
and w.seq = combinations.seq
left join #DaySelected d
on d.Vendor = combinations.vendor
and d.seq = combinations.seq
where (MonthFrom is not null
or MonthTo is not null
or Week is not null
or Day is not null)
And this is the result:
Id name seq MonthFrom MonthTo Week Day
1 Tom Smith 1 3 6 3 15
1 Tom Smith 2 7 9 NULL 25
2 Bruce Lee 1 NULL NULL NULL 12
3 Seamus O’Leary 1 NULL NULL 4 NULL
3 Seamus O’Leary 2 5 6 NULL NULL
4 Jonathan Stewart 1 NULL NULL 1 5
5 Benjamin Franklin 1 NULL NULL NULL 19
This is more complicated than it sounds. According to the result, you do not want a cartesian product when there are multiple matches in a table. So, you need to take seqnum into account.
select v.Vendor, v.name, coalesce(m.seq, w.seq, d.seq) as Seq,
m.MonthFrom, m.MonthTo, w.Week, d.Day
from Vendors v left join
SMonthRangeSelected m
on v.Vendor = m.Vendor full join
WeekSelected w
on v.Vendor = w.Vendor and m.seq = w.seq full join
DaySelected d
on v.Vendor = d.Vendor and d.seq in (w.seq, m.seq)
where m.Vendor is not null or
w.Vendor is not null or
d.Vendor is not null;
Strange things can happen when using full join, particularly if you want any filtering. An alternative approach uses union all and group by:
select mwd.Vendor, v.name, mwd.seq,
max(MonthFrom) as MonthFrom, max(MonthTo) as monthTo,
max(Week) as week, max(Day) as day
from ((select m.Vendor, m.seq, m.MonthFrom, m.MonthTo, NULL as week, NULL as day
from month m
) union all
(select w.Vendor, w.seq, NULL as MonthFrom, NULL as MonthTo, w.week, NULL as day
from week
) union all
(select d.Vendor, d.seq, NULL as MonthFrom, NULL as MonthTo, NULL as week, d.day
from day d
)
) mwd join
Vendor v
on v.vendor = vmwd.vendor
group by mwd.Vendor, v.vname, mwd.seq;
Note that this version does not require the Vendor table.
You should left outer join to each of the 3 tables, and then include the following in your where clause:
(MonthFrom is not null or Week# is not null or Day# is not null)
it sounds like you can inner join to the Vendor table
I believe that this will do what you need:
SELECT
V.[Vendor#], -- I'll never understand why people insist on using names that require brackets
V.Name,
COALESCE(M.[Seq#], W.[Seq#], D.[Seq#]) AS [Seq#],
M.MonthFrom,
M.MonthTo,
W.[Week#],
D.[Day#]
FROM
Vendor V
LEFT OUTER JOIN MonthRange M ON M.[Vendor#] = V.[Vendor#]
LEFT OUTER JOIN Week W ON W.[Vendor#] = V.[Vendor#]
LEFT OUTER JOIN Day D ON D.[Vendor#] = V.[Vendor#]
WHERE
(
M.[Vendor#] IS NOT NULL OR
W.[Vendor#] IS NOT NULL OR
D.[Vendor#] IS NOT NULL
) AND
(M.[Seq#] = W.[Seq#] OR M.[Seq#] IS NULL OR W.[Seq#] IS NULL) AND
(M.[Seq#] = D.[Seq#] OR M.[Seq#] IS NULL OR D.[Seq#] IS NULL) AND
(D.[Seq#] = W.[Seq#] OR D.[Seq#] IS NULL OR W.[Seq#] IS NULL)
You need full outer joins on the three tables, so as to get all vendor and seq number combinations. Join these with vendor and you are done:
select vendorno, v.name, x.seqno, x.monthfrom, x.monthto, x.weekno, x.dayno
from vendor v
join
(
select vendorno, seqno, m.monthfrom, m.monthto, w.weekno, d.dayno
from monthsel m
full outer join weeksel w using (vendorno, seqno)
full outer join daysel d using (vendorno, seqno)
) x using(vendorno)
order by vendorno, x.seqno;
UPDATE: Without a USING clause the same query get slightly less readable (and thus slightly more error-prone):
select v.vendorno, v.name, x.seqno, x.monthfrom, x.monthto, x.weekno, x.dayno
from vendor v
join
(
select
coalesce(m.vendorno, w.vendorno, d.vendorno) as vendorno,
coalesce(m.seqno, w.seqno, d.seqno) as seqno,
m.monthfrom, m.monthto, w.weekno, d.dayno
from monthsel m
full outer join weeksel w on w.vendorno = m.vendorno and w.seqno = m.seqno
full outer join daysel d on d.vendorno in (m.vendorno, w.vendorno)
and d.seqno in (m.seqno, w.segno)
) x on x.vendorno = v.vendorno
order by v.vendorno, x.seqno;
(Hope I didn't mix things up here. It's easy to make copy & paste errors with such a query. So if it doesn't work properly, look out for typos.)