How to add new row from another table with union? - sql

I should add a new row from table products which owner is sales(sh) to product_name_us and prod_desc to product_desc_us using an union .
SELECT temp.*
FROM (SELECT DISTINCT CASE WHEN pd.LANGUAGE_ID = 'US' THEN
COALESCE(TO_CHAR(pd.TRANSLATED_NAME), ' ')
END AS PRODUCT_NAME_US,
CASE WHEN pd.LANGUAGE_ID = 'US' THEN
CONCAT(SUBSTR(pd.TRANSLATED_DESCRIPTION, 1, 30), '...')
END AS PRODUCT_DESC_US,
CASE WHEN pd.LANGUAGE_ID = 'RU' AND pd.translated_name
IS NULL THEN COALESCE(TO_CHAR(pd.TRANSLATED_NAME), ' ')
END AS PRODUCT_NAME_RU,
CASE WHEN pd.LANGUAGE_ID = 'RU' AND
pd.translated_description IS NULL THEN
CONCAT(SUBSTR(pd.TRANSLATED_DESCRIPTION, 1, 30), '...')
END AS PRODUCT_DESC_RU,
CASE
WHEN EXTRACT(MONTH FROM pin.WARRANTY_PERIOD) =
'00' THEN EXTRACT(YEAR FROM pin.WARRANTY_PERIOD) || ' years'
END AS "Warranty", inv.QUANTITY_ON_HAND AS IN_STOCK
FROM PRODUCT_DESCRIPTIONS pd JOIN PRODUCT_INFORMATION pin
ON pd.PRODUCT_ID = pin.PRODUCT_ID
JOIN INVENTORIES inv
ON inv.PRODUCT_ID = pin.PRODUCT_ID) temp
WHERE temp.PRODUCT_NAME_US LIKE '%Monitor%'
AND temp.IN_STOCK < 100
union all (select PROD_NAME,PROD_DESC from sh.products);

If I understand right you want to add a row as a footer to your first select.
Using union is only available if your 2 selects having same amount of columns
select 'field1' as a, 'field2' as b, 'field3' as c from dual
union all
select '1', '2', null from dual;
enter image description here
But if you want to use in your second query column from your first one to match it is impossible because its outside the select statement

Related

Is it possible to replace values in row to values from the same table by the reference in Oracle SQL

There is a table that contains values that are used in formulas. There are simple variables, that do not contain any expression, and also there are some variables that combined from simple variables into formula. I need to figure out if is it possible to do a SELECT query to get a readable formula based on aliases it contains. Each of these aliases could be used in other formulas.
Let's say that there are two tables:
ITEM TABLE
ID
Name
FORMULA_ID
1
Item name 1
f_3
2
Item name 2
f_26
FORMULA TABLE
ID
EXPRESSION
ALIASE
NAME
f_1
null
var_100
Ticket
f_2
null
var_200
Amount
f_3
var_100 * var_200
var_300
Some description
So is there any chance to query, with result like:
ITEM_NAME
READABLE_EXPRESSION
Item name 1
Ticket * Amount
Try this:
with items(ID,Name,Formula_Id) AS (
select 1, 'Item name 1', 'f_3' from dual union all
select 2, 'Item name 2', 'f_26' from dual
),
formulas (ID, EXPRESSION, ALIAS, NAME) as (
select 'f_1', null, 'var_100', 'Ticket' from dual union all
select 'f_2', null, 'var_200', 'Amount' from dual union all
select 'f_3', 'var_100 * var_200', 'var_300', 'Some description' from dual
),
rnformulas (id, EXPRESSION, ALIAS, NAME, rn) as (
select fm.*, row_number() over(order by id) as rn from formulas fm
),
recsubstitute( lvl, item_id, rn, expression ) as (
select 1, it.id, 0, fm.expression
from items it
join rnformulas fm on it.formula_id = fm.id
union all
select lvl+1, item_id, fm.rn, replace(r.expression, fm.alias, fm.name)
from recsubstitute r
join rnformulas fm on instr(r.expression, fm.alias) > 0 and fm.rn > r.rn
)
select item_id, expression from (
select item_id, expression, row_number() over(partition by item_id order by lvl desc, rn asc) as rn
from recsubstitute
)
where rn = 1
;
ITEM_ID EXPRESSION
---------- ------------------------------------------------------------
1 Ticket * Amount
Note that it's far to be bullet proof against all situations, especially recursion in the aliases.
Some improvement with another set of data:
with items(ID,Name,Formula_Id) AS (
select 1, 'Item name 1', 'f_3' from dual union all
select 2, 'Item name 2', 'f_4' from dual
),
formulas (ID, EXPRESSION, ALIAS, NAME) as (
select 'f_1', null, 'var_100', 'Ticket' from dual union all
select 'f_2', null, 'var_200', 'Amount' from dual union all
select 'f_3', 'var_100 * var_200', 'var_300', 'Some description' from dual union all
select 'f_4', 'var_300', null, 'Other description' from dual
),
rnformulas (id, EXPRESSION, ALIAS, NAME, rn) as (
select fm.*, row_number() over(order by id) as rn from formulas fm
),
recsubstitute( lvl, item_id, rn, expression ) as (
select 1, it.id, 0, fm.expression
from items it
join rnformulas fm on it.formula_id = fm.id
union all
select lvl+1, item_id, fm.rn, replace(r.expression, fm.alias, nvl(fm.expression,fm.name))
from recsubstitute r
join rnformulas fm on instr(r.expression, fm.alias) > 0
)
select item_id, expression from (
select item_id, expression, row_number() over(partition by item_id order by lvl desc, rn asc) as rn
from recsubstitute
)
where rn = 1
;
1 Ticket * Amount
2 Ticket * Amount
Split the space-delimited formulas into rows. Join the expression parts to the aliases and replace the alias with the name. Join this to the item_table using LISTAGG to concatenate the rows back into a single column.
WITH formula_split AS (
SELECT DISTINCT ft.id
,level lvl
,regexp_substr(ft.expression,'[^ ]+',1,level) expression_part
FROM formula_table ft
CONNECT BY ( ft.id = ft.id
AND level <= length(ft.expression) - length(replace(ft.expression,' ')) + 1 ) START WITH ft.expression IS NOT NULL
),readable_tbl AS (
SELECT ft.id
,ft.lvl
,replace(ft.expression_part,ftn1.aliase,ftn1.name) readable_expression
FROM formula_split ft
LEFT JOIN formula_table ftn1 ON ( ft.expression_part = ftn1.aliase )
)
SELECT it.name item_name
,LISTAGG(readable_expression,' ') WITHIN GROUP(ORDER BY lvl) readable_expression
FROM item_table it
JOIN readable_tbl rt ON ( it.formula_id = rt.id )
GROUP BY it.name
With sample data create CTE (calc_data) for modeling
WITH
items (ITEM_ID, ITEM_NAME, FORMULA_ID) AS
(
Select 1, 'Item name 1', 'f_3' From Dual Union All
Select 2, 'Item name 2', 'f_26' From Dual
),
formulas (FORMULA_ID, EXPRESSION, ALIAS, ELEMENT_NAME) AS
(
Select 'f_1', null, 'var_100', 'Ticket' From Dual Union All
Select 'f_2', null, 'var_200', 'Amount' From Dual Union All
Select 'f_3', 'var_100 * var_200', 'var_300', 'Some description' From Dual
),
calc_data AS
( SELECT e.ITEM_NAME, e.FORMULA_ID, e.FORMULA, e.X, e.OPERAND, e.Y,
ROW_NUMBER() OVER(Partition By e.ITEM_NAME Order By e.FORMULA_ID) "RN", f.ELEMENT_NAME
FROM( Select CAST('.' as VARCHAR2(32)) "FORMULA", i.ITEM_NAME, f.FORMULA_ID,
SubStr(Replace(f.EXPRESSION, ' ', ''), 1, InStr(Replace(f.EXPRESSION, ' ', ''), '*') - 1) "X",
CASE
WHEN InStr(f.EXPRESSION, '+') > 0 THEN '+'
WHEN InStr(f.EXPRESSION, '-') > 0 THEN '-'
WHEN InStr(f.EXPRESSION, '*') > 0 THEN '*'
WHEN InStr(f.EXPRESSION, '/') > 0 THEN '/'
END "OPERAND",
--
SubStr(Replace(f.EXPRESSION, ' ', ''), InStr(Replace(f.EXPRESSION, ' ', ''), '*') + 1) "Y"
From formulas f
Inner Join items i ON(f.FORMULA_ID = i.FORMULA_ID)
) e
Inner Join formulas f ON(f.FORMULA_ID <> e.FORMULA_ID)
)
Main SQL with MODEL clause
SELECT ITEM_NAME, FORMULA
FROM ( SELECT *
FROM calc_data
MODEL
PARTITION BY (ITEM_NAME)
DIMENSION BY (RN)
MEASURES (X, OPERAND, Y, FORMULA, ELEMENT_NAME)
RULES ( FORMULA[1] = ELEMENT_NAME[1] || ' ' || OPERAND[1] || ' ' || ELEMENT_NAME[2] )
)
WHERE RN = 1
R e s u l t :
ITEM_NAME
FORMULA
Item name 1
Amount * Ticket
Just as an option...
The same result without any analytic functions, pseudo columns, unions, etc... - just selecting over and over and over. Not readable, though...
Select
i.ITEM_NAME,
REPLACE( REPLACE( (Select EXPRESSION From formulas Where FORMULA_ID = f.FORMULA_ID),
(Select Min(ALIAS) From formulas Where FORMULA_ID <> f.FORMULA_ID),
(Select ELEMENT_NAME From formulas Where FORMULA_ID <> f.FORMULA_ID And ALIAS = (Select Min(ALIAS) From formulas Where FORMULA_ID <> f.FORMULA_ID) )
) ||
REPLACE( (Select EXPRESSION From formulas Where FORMULA_ID = f.FORMULA_ID),
(Select Max(ALIAS) From formulas Where FORMULA_ID <> f.FORMULA_ID),
(Select ELEMENT_NAME From formulas Where FORMULA_ID <> f.FORMULA_ID And ALIAS = (Select Max(ALIAS) From formulas Where FORMULA_ID <> f.FORMULA_ID) )
),
(SELECT Max(ALIAS) From formulas Where FORMULA_ID <> f.FORMULA_ID ) || (Select Min(ALIAS) From formulas Where FORMULA_ID <> f.FORMULA_ID) ||
SubStr(f.EXPRESSION, InStr(f.EXPRESSION, ' ', 1, 1), (InStr(f.EXPRESSION, ' ', 1, 2) - InStr(f.EXPRESSION, ' ', 1, 1)) + 1 ), ''
) "FORMULA"
From
formulas f
Left Join
items i ON(i.FORMULA_ID = f.FORMULA_ID)
Where i.ITEM_NAME Is Not Null
Thank you all for your answers!
I've decided to create a pl/sql function, just to modify a formula to readable row. So the function just looks for variables using regex, and uses indexes to replace every variable with a name.
CREATE OR REPLACE FUNCTION READABLE_EXPRESSION(inExpression IN VARCHAR2)
RETURN VARCHAR2
IS
matchesCount INTEGER;
toReplace VARCHAR2(32767);
readableExpression VARCHAR2(32767);
selectString VARCHAR2(32767);
BEGIN
matchesCount := REGEXP_COUNT(inExpression, '(var_)(.*?)');
IF matchesCount IS NOT NULL AND matchesCount > 0 THEN
readableExpression := inExpression;
FOR i in 1..matchesCount
LOOP
toReplace := substr(inExpression, REGEXP_INSTR(inExpression, '(var_)(.*?)', 1, i, 0),
REGEXP_INSTR(inExpression, '(var_)(.*?)', 1, i, 1) -
REGEXP_INSTR(inExpression, '(var_)(.*?)', 1, i, 0)
);
SELECT DISTINCT F.NAME
INTO selectString
FROM FORMULA F
WHERE F.ALIASE = toReplace FETCH FIRST 1 ROW ONLY;
readableExpression := REPLACE(readableExpression,
toReplace,
selectString
);
end loop;
end if;
return readableExpression;
END;
So such function returns 1 result row with replaced values for 1 input row with FORMULA. All you need to do is join the ITEM and FORMULA tables in the SELECT.
SELECT item.name, READABLE_EXPRESSION(formula.expression)
FROM item
JOIN formula ON item.formula_id = formula.id;
Please note that the tables are fictitious so as not to reveal the actual data structure, so there might be some inaccuracies. But the general idea should be clear.

merge CTE query in Oracle

I am looking for an optimized way to merge CTE query as mentioned below. In the below query, the same table is getting hit twice, I am looking for an option to hit this table once and it will only be possible if I remove CTE and put everything into single query. I am using CTE as I need to derive column value and then compare that value..
Query
with cte as
(
select distinct
'UP' search_update_code,
'G' search_prov_entity_type,
prpr.prpr_id search_prov_id,
prpr.mctn_id search_tax_id,
'UP' group_provdata_upd_cd,
'G' group_provdata_entity,
case
when enroll_type = 'AH' THEN 'A'
when enroll_type = 'VP' THEN 'A'
when enroll_type = 'CK' THEN ' '
end group_provdata_clm_eft_pay_ind <-- deriving values here
from
fsg_prcb_pe_enroll_stream prcb
inner join
cmc_prpr_prov prpr on prpr.mctn_id = prcb.prov_tin_number <-- 1st hit
where
prpr.prpr_entity ='G'
and prcb.prov_tin_number = ?
and prcb.error_flag is null
)
select distinct
cte.*
from cte
inner join cmc_prpr_prov prpr on cte.search_prov_id = prpr.prpr_id <-- 2nd hit
where cte.group_provdata_clm_eft_pay_ind <> prpr.prpr_cl_eft_ind <--- comparing derived values here
and prpr_term_dt >= systimestamp
You can rewrite your query like below:
with cte as
(
select distinct
'UP' search_update_code,
'G' search_prov_entity_type,
prpr.prpr_id search_prov_id,
prpr.mctn_id search_tax_id,
'UP' group_provdata_upd_cd,
'G' group_provdata_entity,
case
when enroll_type = 'AH' THEN 'A'
when enroll_type = 'VP' THEN 'A'
when enroll_type = 'CK' THEN ' '
end group_provdata_clm_eft_pay_ind, <-- deriving values here
prpr.prpr_cl_eft_ind prpr_cl_eft_ind_2,
prpr_term_dt
from
fsg_prcb_pe_enroll_stream prcb
inner join
cmc_prpr_prov prpr on prpr.mctn_id = prcb.prov_tin_number <-- 1st hit
where
prpr.prpr_entity ='G'
and prcb.prov_tin_number = ?
and prcb.error_flag is null
)
select distinct
cte.*
from cte
where group_provdata_clm_eft_pay_ind <> prpr_cl_eft_ind_2 <--- comparing derived values here
and prpr_term_dt >= systimestamp

How to create a view from existing table records, but also adding new records that do not exist

I am trying to create a view from an existing views data, but also if there are certain lines that do not exist per part/date combo, then have those lines be created. I have the below query that is showing what I currently have for the particular s_date/part_no combos:
SELECT
s_date,
part_no,
issue_group,
s_level,
qty_filled
FROM
current_view
WHERE
part_no = 'xxxxx'
AND s_date IN (
'201802',
'201803'
)
ORDER BY
s_date,
part_no,
issue_group,
DECODE(s_level, '80', 1, '100', 2, 'Late', 3)
Which produces the below:
I know how to create a view with that data, that's the easy part. But what I'm needing is a line for each issue_group and s_level combo, and if it's a created line, to put 0 as the qty_filled.
Every part_no / s_date combo should have 6 rows that go with it
- issue_group = '1' / s_level = '80'
- issue_group = '1' / s_level = '100'
- issue_group = '1' / s_level = 'Late'
- issue_group = '2/3 ' / s_level = '80'
- issue_group = '2/3 ' / s_level = '100'
- issue_group = '2/3 ' / s_level = 'Late'
So if one of the above combinations already exists for the current s_date/part_no, then it obviously takes the qty_filled info from the current view. If not, a new line is created, and qty_filled = 0. So I'm trying to get it to look like this:
I've only shown 1 part, with a couple dates, just to get the point across. There are 10k+ parts within the table and there will never be more than 1 part/date combo for each of the 6 issue_group/s_level combos.
The idea is to generate the rows using CROSS JOIN and then bring in the extra information with a LEFT JOIN. In Oracle syntax, this looks like:
WITH v as (
SELECT v.*
FROM current_view v
WHERE part_no = 'xxxxx' AND
s_date IN ('201802', '201803')
)
SELECT d.s_date, ig.part_no, ig.issue_group, l.s_level,
COALESCE(v.qty_filled, 0) as qty_filled
FROM (SELECT DISTINCT s_date FROM v) d CROSS JOIN
(SELECT DISTINCT part_no, ISSUE_GROUP FROM v) ig CROSS JOIN
(SELECT '80' as s_level FROM DUAL UNION ALL
SELECT '100' FROM DUAL UNION ALL
SELECT 'LATE' FROM DUAL
) l LEFT JOIN
v
ON v.s_date = d.s_date AND v.part_no = ig.part_no AND
v.issue_group = ig.issue_group AND v.s_level = l.s_level
ORDER BY s_date, part_no, issue_group,
(CASE s_level WHEN '80' THEN 1 WHEN '100' THEN 2 WHEN 'Late' THEN 3 END)
One solution could be to generate a cartesian product of all expected rows using a cartesian product between the (fixed) list of values, and then LEFT JOIN it with current_view.
The following query guarantees that you will get a record for each given s_date/part_no/issue_group/s_level tuple. If no record matches in current_view, the query will display a 0 quantity.
SELECT
sd.s_date,
pn.part_no,
ig.issue_group,
sl.s_level,
COALESCE(cv.qty_filled, 0) qty_filled
FROM
(SELECT '201802' AS s_date UNION SELECT '201803') AS sd
CROSS JOIN (SELECT 'xxxxx' AS part_no) AS pn
CROSS JOIN (SELECT '1' AS issue_group UNION SELECT '2') AS ig
CROSS JOIN (SELECT '80' AS s_level UNION SELECT '100' UNION SELECT 'Late') AS sl
LEFT JOIN current_view cv
ON cv.s_date = sd.s_date
AND cv.part_no = pn.part_no
AND cv.issue_group = ig.issue_group
AND cv.s_level = ig.s_level
ORDER BY
sd.s_date,
pn.part_no,
ig.issue_group,
DECODE(sl.s_level, '80', 1, '100', 2, 'Late', 3)
NB : you did not tag your RDBMS. This should work on most of them, excepted Oracle, where you need to add FROM DUAL to each select in the queries that list the allowed values, like :
(SELECT '201802' AS s_date FROM DUAL UNION SELECT '201803' FROM DUAL) AS sd

Union ALL not giving the column\ names for second group

I am using the tool SQLDBx. which i highly like btw. I have 5 groups of series of Select statements and then I made a UNION ALL after the first group. Now it does run correctly
and displays the output but it does not assign the second group'
s field names.
For example. I am greatly abridging
Completed the SQL's needed to mimic a IBM i display screen. There are 5 product groups making up the screen. I was hoping to have one large Command SQL having the 5 SQLs using UNION ALL. This does 'compile' as a Command. However, it does only brings in the first part fields not the second. So this field is not included in the list of fields tree for COMMAND. PROGR2R2PST,
Is there something not correct how doing the UNION ALL? OUTPUT assigns the column name
of the first group to the second group.
HLDGR1PUN
21454
87273
so if i wanted to have one large SQL with union ALLs, it wont work. is there something other than UNION ALL I should use?
SELECT
count(*) as PROGR1PST,
(
SELECT COALESCE(SUM(OdQty#),0)
FROM ASTCCDTA.OEORH48,ASTCCDTA.TRNSTAT2,ASTDTA.OEORD1
WHERE OHCOM# = TSCOM# AND OHORD# = TSORD# AND OHCOM# = ODCOM# AND OHORD# = ODORD#
AND TSSTAT IN('AEP','BGE')
AND OHORDT IN('RTR','INT','SAM')
AND OHREQD < replace(char(current date, iso), '-', '')
AND OHHLDC = ' '
AND ODPRLC = 'ENG'
AND substr(odprt#,1,5) <> 'NOENG' AND OHORD# in(SELECT a.TSORD# FROM ASTCCDTA.TRNSTAT2 a
WHERE a.tsstat IN('AEP','BGE','EAS','REL','STP'))
) AS PROGR1PUN,
(
SELECT count(*)
FROM ASTCCDTA.OEORH48,ASTCCDTA.TRNSTAT2,ASTCCDTA.OETRA99
WHERE OHCOM# = TSCOM# AND OHORD# = TSORD#
AND (otCOM# = OHCOM# AND OTORD#= OHORD# AND ottrnc = 'AQC')
AND TSSTAT IN('AEP','BGE')
AND OHORDT IN('RTR','INT','SAM')
AND OHREQD = replace(char(current date, iso), '-', '') AND OHHLDC = ' ' AND OHORD# in(SELECT a.TSORD# FROM ASTCCDTA.TRNSTAT2 a
WHERE a.tsstat IN('AEP','BGE','EAS','REL','STP'))
) AS PROGR1TOD,
(
etc..
UNION ALL
SELECT
count(*) as PROGR2R2PST,
(SELECT COALESCE(SUM(OdQty#),0) FROM ASTCCDTA.OEORH48,ASTCCDTA.TRNSTAT2,ASTDTA.OEORD1
WHERE OHCOM# = TSCOM# AND OHORD# = TSORD# AND OHCOM# = ODCOM# AND OHORD# = ODORD#
AND TSSTAT IN('AEP','BGE')
AND OHORDT IN('CUS','CIN','SMC','COC','DON')
AND OHREQD < replace(char(current date, iso), '-', '')
AND OHHLDC = ' '
AND ODPRLC = 'ENG'
AND substr(odprt#,1,5) <> 'NOENG' AND OHORD# in(SELECT a.TSORD# FROM ASTCCDTA.TRNSTAT2 a
WHERE a.tsstat IN('AEP','BGE','EAS','REL','STP'))
) AS PROGR2PUN,
I don't think you can get the result you want by "nesting" the SQL statements and joining them with UNION.
This structure may work, depending upon your requirements:
SELECT 'Name1' AS Label , COUNT(*) AS The_Count
FROM table1
WHERE ...
UNION ALL
SELECT 'PROGR2R2PST', COUNT(*)
FROM table2
WHERE ...
UNION ALL
SELECT 'Name3', COUNT(*)
FROM table3
WHERE ...
This will give you back one row per select:
Label The_Count
--------------------
Name1 45867
PROGR2R2PST 22
Name3 1234
Note that the column names come from the first select. If this format doesn't match your requirements, please be more explicit in the question or in comments and I will try to help.

How to collapse similar rows in SQL query

Using an MS SQL query, I would like to collapse redundant data in sequential rows to make a report easier to read. Suppose I have the following data:
ID | Department | Name | Task
-------------------------------------------------
1 Sales Mike Call customer
2 Sales Mike Create quote
3 Sales Sarah Create order
4 Engineering Sam Design prototype
5 Engineering Sam Calculate loads
6 Production Team1 Build parts
7 Production Team2 Build parts
8 Production Team1 Assemble parts
9 Accounting Amy Invoice job
10 Sales Mike Call customer
11 Sales Mike Follow up after 30 days
How can I get the following summarized rows from my query:
Sales: Mike (Call customer, Create quote) Sarah (Create order)
Engineering: Sam (Design prototype, Calculate loads)
Production: Team1 (Build parts) Team2 (Build parts) Team1 (Assemble parts)
Accounting: Amy (Invoice job)
Sales: Mike (Call customer, Follow up after 30 days)
Essentially if the previous department and name are the same, add the task to a comma separated sub-list. If only the department is the same, start a new named sub-list and if the department is new then start a new row. Notice that items 10 and 11 should not be included in the first row so that the order of tasks is not lost.
In C# this task is easy using loops and/or Linq, however, I now need to produce the same result from a SQL query.
Use recursive Common Table Expressions (working SQLFiddle example)
;with RecursivePersonTask( Id, Department, Name, Tasks )
as
(
select
a.Id
, a.Department
, a.Name
, a.Task
from
dbo.Task a
left outer join dbo.Task b
on a.Department = b.Department
and a.Name = b.Name
and a.Id = b.Id + 1
where
b.Id is null
union all
select
t.Id
, t.Department
, t.Name
, rpt.Tasks + ', ' + t.Task
from
RecursivePersonTask rpt
inner join dbo.Task t
on rpt.Department = t.Department
and rpt.Name = t.Name
and rpt.Id = t.Id - 1
)
, CombinedPersonTasks( Id, Department, Name, Tasks )
as
(
select
ROW_NUMBER() over ( order by a.Id )
, a.Department
, a.Name
, '(' + a.Tasks + ')'
from
RecursivePersonTask a
left outer join RecursivePersonTask b
on a.Department = b.Department
and a.Name = b.Name
and a.Id = b.Id - 1
where
b.Id is null
)
, RecursiveDepartmentTasks( Id, Department, Tasks )
as
(
select
a.Id
, a.Department
, a.Name + ' ' + a.Tasks
from
CombinedPersonTasks a
left outer join CombinedPersonTasks b
on a.Department = b.Department
and a.Id = b.Id + 1
where
b.Id is null
union all
select
cpt.Id
, cpt.Department
, rdt.Tasks + ' ' + cpt.Name + ' ' + cpt.Tasks
from
RecursiveDepartmentTasks rdt
inner join CombinedPersonTasks cpt
on rdt.Department = cpt.Department
and rdt.Id = cpt.Id - 1
)
, CombinedDepartmentTasks( Id, Department, Tasks )
as
(
select
ROW_NUMBER() over ( order by a.Id )
, a.Department
, a.Tasks
from
RecursiveDepartmentTasks a
left outer join RecursiveDepartmentTasks b
on a.Department = b.Department
and a.Id = b.Id - 1
where
b.Id is null
)
select
*
from
CombinedDepartmentTasks
order by
Id
If you have SQL Server 2012, you can use the ROWS parameters of the windowing functions:
with tasks as (
select 1 as id, 'Sales' as dept ,'Mike' as name, 'Call customer' as task union
select 2, 'Sales' ,'Mike', 'Create quote' union
select 3, 'Sales' ,'Sarah', ' Create order' union
select 4, 'Engineering' ,'Sam', ' Design prototype' union
select 5, 'Engineering' ,'Sam', ' Calculate loads' union
select 6, 'Production' ,'Team1', ' Build parts' union
select 7, 'Production' ,'Team2', ' Build parts' union
select 8, 'Production' ,'Team1', ' Assemble parts' union
select 9, 'Accounting' ,'Amy', ' Invoice job' union
select 10, 'Sales' ,'Mike', ' Call customer' union
select 11, 'Sales' ,'Mike', ' Follow up after 30 days'
)
select max(NewDepartmentRollover) over (order by id rows unbounded preceding) as ColumnToGroupOn , * from (
select
case when max(dept) over (order by id rows between 1 preceding and 1 preceding) = dept then NULL else id end as NewDepartmentRollover,
* from tasks
) GroupOn
If you only have SQL Server 2005 or 2008, something like this will do:
with tasks as (
select 1 as id, 'Sales' as dept ,'Mike' as name, 'Call customer' as task union
select 2, 'Sales' ,'Mike', 'Create quote' union
select 3, 'Sales' ,'Sarah', ' Create order' union
select 4, 'Engineering' ,'Sam', ' Design prototype' union
select 5, 'Engineering' ,'Sam', ' Calculate loads' union
select 6, 'Production' ,'Team1', ' Build parts' union
select 7, 'Production' ,'Team2', ' Build parts' union
select 8, 'Production' ,'Team1', ' Assemble parts' union
select 9, 'Accounting' ,'Amy', ' Invoice job' union
select 10, 'Sales' ,'Mike', ' Call customer' union
select 11, 'Sales' ,'Mike', ' Follow up after 30 days'
)
, b as (
select
row_number() over (order by tasks.id) as rn,
tasks.id as lefty
from tasks
left join tasks t3
on t3.id = tasks.id - 1
where tasks.dept <> isnull(t3.dept,'')
)
select tasks.*, lefty as columnToGroupOn from tasks left join (
select b.lefty, isnull(b2.lefty,999)-1 as righty from b
left join
b b2 on b.rn = b2.rn - 1
) c
on tasks.id between lefty and righty