sql split row value before and after substring - sql-server

I have a table that contains a list of names. However, some rows contain the name and the alias separated by , f/k/a—, , f/k/a or , n/k/a . I'm trying to split the names and aliases into separate rows. Can someone please help?
Sample data below:
|---------------------------------------------------------|
| ID | Name |
|---------------------------------------------------------|
| 1 | Evil Empire, f/k/a - Starbucks |
| 2 | Aubrey Drake Graham, n/k/a Drake |
| 3 | Thomas Johnson Bridge, f/k/a Solomans Bridge |
|---------------------------------------------------------|
Desired output below:
|---------------------------------------------------------|
| ID | Name |
|---------------------------------------------------------|
| 1 | Evil Empire |
| 1.1 | Starbucks |
| 2 | Aubrey Drake Graham |
| 2.1 | Drake |
| 3 | Thomas Johnson Bridge |
| 3.1 | Solomans Bridge |
|---------------------------------------------------------|

No need for ordinal splitter. It's a simple unpivot using CROSS APPLY
[EDIT] Changed method of splitting to look for '%/%/%' in cases where there's an alias
select cast(unpvt.id as varchar(9))+iif(unpvt.seq=1, '', '.1') ID,
trim(replace(replace(replace(unpvt.[Name],'f/k/a - ',''),'f/k/a ',''),'n/k/a ','')) [Name]
from (values (1, 'Evil Empire, f/k/a - Starbucks'),
(2, 'Aubrey Drake Graham, n/k/a Drake'),
(3, 'Thomas Johnson Bridge, f/k/a Solomans Bridge'),
(4, 'Thomas, J, Cat, f/k/a Solomans,,,Bridge'),
(5, 'Thomas')) v(id, [Name])
cross apply (values (v.id, substring(v.[Name], 1, patindex('%/%/%', v.Name)-4), 1),
(v.id, substring(v.[Name], patindex('%/%/%', v.Name)-1, len(v.[Name])), 2)) unpvt(id, [Name], seq)
where patindex('%/%/%', v.[Name])>1;
Results
ID Name
1 Evil Empire
1.1 Starbucks
2 Aubrey Drake Graham
2.1 Drake
3 Thomas Johnson Bridge
3.1 Solomans Bridge
4 Thomas, J, Cat
4.1 Solomans,,,Bridge

As you can't use the built in string_split you will need to add a Table Valued Function to do that for you. Using one of these allows you to split your data like this:
Query
declare #t table(ID int,[Name] varchar(100));
insert into #t values
(1,'Evil Empire, f/k/a - Starbucks')
,(2,'Aubrey Drake Graham, n/k/a Drake')
,(3,'Thomas Johnson Bridge, f/k/a Solomans Bridge')
;
select case when s.rn = 1
then t.ID
else t.ID + ((s.rn - 1)/10.)
end as ID
,replace(replace(replace(s.item,' f/k/a - ',''),' f/k/a ',''),' n/k/a ','')
from #t as t
cross apply dbo.fn_StringSplit4k(t.[Name],',',null) as s
order by t.ID
,s.rn;
Output
+----------+-----------------------+
| ID | Name |
+----------+-----------------------+
| 1.000000 | Evil Empire |
| 1.100000 | Starbucks |
| 2.000000 | Aubrey Drake Graham |
| 2.100000 | Drake |
| 3.000000 | Thomas Johnson Bridge |
| 3.100000 | Solomans Bridge |
+----------+-----------------------+
Function
create function [dbo].[fn_StringSplit4k]
(
#str nvarchar(4000) = ' ' -- String to split.
,#delimiter as nvarchar(20) = ',' -- Delimiting value to split on.
,#num as int = null -- Which value to return.
)
returns table
as
return
-- Start tally table with 10 rows.
with n(n) as (select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1)
-- Select the same number of rows as characters in #str as incremental row numbers.
-- Cross joins increase exponentially to a max possible 10,000 rows to cover largest #str length.
,t(t) as (select top (select len(isnull(#str,'')) a) row_number() over (order by (select null)) from n n1,n n2,n n3,n n4)
-- Return the position of every value that follows the specified delimiter.
,s(s) as (select 1 union all select t+1 from t where case when #delimiter = '' and t < len(#str) then 1 else case when substring(isnull(#str,''),t,1) = #delimiter then 1 else 0 end end = 1)
-- Return the start and length of every value, to use in the SUBSTRING function.
-- ISNULL/NULLIF combo handles the last value where there is no delimiter at the end of the string.
,l(s,l) as (select s,case when #delimiter = '' then 1 else isnull(nullif(charindex(#delimiter,isnull(#str,''),s),0)-s,4000) end from s)
select rn
,item
from(select row_number() over(order by s) as rn
,substring(#str,s,l) as item
from l
) a
where rn = #num
or #num is null;

Related

SQL Server split field and create addition rows with values from main row

I have a table with rows and in one field there are values like this A,B,C
Table 'Mytable':
|ID | Date | MyValue | SplitID |
|1 | 2019-12-17 | A | |
|2 | 2019-12-15 | A,B | |
|3 | 2019-12-16 | B,C | |
Result should be:
|1 | 2019-12-17 | A | 1 |
|2 | 2019-12-15 | A | 2 |
|4 | 2019-12-15 | B | 2 |
|3 | 2019-12-16 | B | 3 |
|5 | 2019-12-16 | C | 3 |
(Sorry, I could not find HOW to format a table in the Stackoverflow help)
I tried a inline table function which splits the Field Myvalue into more lines but could not pass my rows with
charindex(',',[MyValue])>0
from MyTable as input lines.
The code is this:
ALTER function [dbo].[fncSplitString](#input Varchar(max), #Splitter Varchar(99), #ID int)
returns table as
Return
with tmp (DataItem, ix, ID) as
( select LTRIM(#input) , CHARINDEX('',#Input), #ID --Recu. start, ignored val to get the types right
union all
select LTRIM(Substring(#input, ix+1,ix2-ix-1)), ix2, #ID
from (Select *, CHARINDEX(#Splitter,#Input+#Splitter,ix+1) ix2 from tmp) x where ix2<>0
) select DataItem,ID from tmp where ix<>0
Thanks for help
Michael
You can try the following query.
Create table #Temp(
Id int,
DateField Date,
MyValue Varchar(10),
SplitID int
)
CREATE FUNCTION [dbo].[SplitPra] (#Value VARCHAR(MAX), #delimiter CHAR)
RETURNS #DataResult TABLE([Position] TINYINT IDENTITY(1,1),[Value] NVARCHAR(128))
AS
BEGIN
DECLARE #XML xml = N'<r><![CDATA[' + REPLACE(#Value, #delimiter, ']]></r><r><![CDATA[') + ']]></r>'
INSERT INTO #DataResult ([Value])
SELECT RTRIM(LTRIM(T.c.value('.', 'NVARCHAR(128)')))
FROM #xml.nodes('//r') T(c)
RETURN
END
insert into #Temp Values(1, '2019-12-17', 'A', NULL),(2, '2019-12-15', 'A,B', NULL), (3, '2019-12-16', 'B,C', NULL)
Select
#Temp.Id, DateField, b.Value as MyValue, b.Id as SplitValue
from #Temp inner join (
select
Id, f.*
from
#Temp u
cross apply [dbo].[SplitPra](u.MyValue, ',') f
)b on #Temp.Id = b.Id
Drop table #Temp
This will give an output as shown below.
Id DateField MyValue SplitValue
---------------------------------
1 2019-12-17 A 1
2 2019-12-15 A 2
2 2019-12-15 B 2
3 2019-12-16 B 3
3 2019-12-16 C 3
You can find the live demo here.
I found this solution, i hope it will work for you. But i didn't use your function to solve this problem. Instead of that, i used cross apply function.You can find the query below:
-- Creating Test Table
CREATE TABLE #Test
(
ID int,
Date date,
MyValue nvarchar(max),
SplitID int
);
GO
-- Inserting data into test table
INSERT INTO #Test VALUES (1, '2019-12-17', 'A', NULL);
INSERT INTO #Test VALUES (2, '2019-12-15', 'A,B', NULL);
INSERT INTO #Test VALUES (3, '2019-12-16', 'B,C', NULL);
GO
-- Select query
SELECT
*,
(SELECT ID FROM test t1 WHERE t.Date = t1.date) AS SplitID
FROM
(
SELECT
ROW_NUMBER() OVER (ORDER BY ID) AS ID,
Date,
substring(A.value,1,
CASE WHEN charindex(',',rtrim(ltrim(A.value))) = 0 then LEN(A.value)
ELSE charindex(',',rtrim(ltrim(A.value))) -1 end) as MyValue
FROM Test
CROSS APPLY string_split (MyValue, ',') A) AS T
ORDER BY MyValue ASC;
And the result must be like that:
ID Date MyValue SplitID
1 2019-12-17 A 1
2 2019-12-15 A 2
3 2019-12-15 B 2
4 2019-12-16 B 3
5 2019-12-16 C 3

TSQL Manager Hierarchy

I need to be able to show all my managers in a hierarchy in different columns. I don't know how many levels there will be.
Example: Employee – ManagerOfEmployee - TheBigBoss
I have tried the below but cant get it to work the way I want it.
I need the results to look like this:
Level1Column Level2Column Level3Column
------------------------------------------
1 2 3
Code:
CREATE TABLE #tblHRData
(
Emplid INT,
ReportsToEmplid INT
)
INSERT INTO #tblHRData (Emplid, ReportsToEmplid)
VALUES (1, 2), (2, 3)
;WITH CTE AS
(
SELECT
Emplid,
ReportsToEmplid,
1 AS level
FROM
#tblHRData
WHERE
Emplid = 1
UNION ALL
SELECT
child.Emplid,
child.ReportsToEmplid,
level + 1
FROM
#tblHRData child
JOIN
CTE parent ON child.ReportsToEmplid = parent.Emplid
)
SELECT *
FROM CTE;
With an unknown depth you will have to go the dynamic SQL route. But such cases tend to have a maximal depth. As your columns will have computable names, you can try this:
I enhanced your table a bit:
CREATE TABLE #tblHRData
(
Emplid INT,
ReportsToEmplid INT,
Descr VARCHAR(100)
)
INSERT INTO #tblHRData (Emplid, ReportsToEmplid, Descr)
VALUES (1, 2, 'lvl 3.2.1') --boss is 2
,(2, 3, 'lvl 3.2') --boss is 3
,(3,null, 'big boss')--big boss reports to no one
,(4, 3, 'lvl 3.4') --one more 2nd lvl
,(5, 4, 'lvl 3.4.5') --below 4
,(6, 4, 'lvl 3.4.6') --another one below 4
-- And I changed the recursive CTE to start off with the big boss and to build a sort string on the fly. In this case this is limited to 3 digits. You'll have to widen this with Emplids exceeding 999:
;WITH CTE AS
(
SELECT
Emplid,
ReportsToEmplid,
Descr,
0 AS EmpLvl,
CAST(REPLACE(STR(Emplid,3),' ','0') AS VARCHAR(MAX)) AS SortOrder
FROM
#tblHRData
WHERE
ReportsToEmplid IS NULL --start with the big boss
UNION ALL
SELECT
child.Emplid,
child.ReportsToEmplid,
child.Descr,
parent.EmpLvl + 1,
parent.SortOrder + REPLACE(STR(child.Emplid,3),' ','0')
FROM
#tblHRData child
JOIN
CTE parent ON child.ReportsToEmplid = parent.Emplid
)
SELECT Emplid,
SortOrder,
MAX(CASE WHEN EmpLvl=0 THEN Descr END) AS BossDescr,
MAX(CASE WHEN EmpLvl=1 THEN Descr END) AS Lvl1Descr,
MAX(CASE WHEN EmpLvl=2 THEN Descr END) AS Lvl2Descr,
MAX(CASE WHEN EmpLvl=3 THEN Descr END) AS Lvl3Descr,
MAX(CASE WHEN EmpLvl=4 THEN Descr END) AS Lvl4Descr,
MAX(CASE WHEN EmpLvl=5 THEN Descr END) AS Lvl5Descr
--add as many as you need and add some more to be future safe
FROM CTE
GROUP BY EmpLvl,Emplid,SortOrder
ORDER BY SortOrder;
GO
DROP TABLE #tblHRData
The result
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| Emplid | SortOrder | BossDescr | Lvl1Descr | Lvl2Descr | Lvl3Descr | Lvl4Descr | Lvl5Descr |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 3 | 003 | big boss | NULL | NULL | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 2 | 003002 | NULL | lvl 3.2 | NULL | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 1 | 003002001 | NULL | NULL | lvl 3.2.1 | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 4 | 003004 | NULL | lvl 3.4 | NULL | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 5 | 003004005 | NULL | NULL | lvl 3.4.5 | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 6 | 003004006 | NULL | NULL | lvl 3.4.6 | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
Some remarks:
- I use the conditional aggregation as PIVOT approach. With just one column this can be done with PIVOT() too.
- The SortOrder is important to be created in the recursion. It is kind of the path to the entry and will allow you to order your result-set.
- This path must allow alphanumerical sorting. Therefore I concatenate padded strings.

Split String Into Individual Rows

I'm currently tying to figure out how to split an ID into 2 rows at each instance of '/'. The original IDs will still be saved in the main table as well as temp table 2 but I need the new IDs saved to a new table. All of this happens in temporary tables on a pre-import handler before a report is generated.
The tables output is currently as follows:
RWID RWLEN DESCR QTY UNIT
T2/10060 20.0000 SomeInfo 1 pcs
T2/10061 18.5689 SomeInfo 1 pcs
T2/10062 20.0000 SomeInfo 1 pcs
I need the table to out the following:
RWID RWLEN DESCR QTY UNIT
T10060 20.0000 SomeInfo 1 pcs
T20060 20.0000 SomeInfo 1 pcs
T10061 18.5689 SomeInfo 1 pcs
T20061 18.5689 SomeInfo 1 pcs
T10062 20.0000 SomeInfo 1 pcs
T20062 20.0000 SomeInfo 1 pcs
A snippet of my code is below:
-- populate temp table 1 from main table
SELECT *
INTO ##tmp1
FROM main;
-- populate temp table 2 from temp table 1, group and order by RWID
SELECT RWID, MAX(DESCR) as aux
INTO ##tmp2
FROM ##tmp1
group by RWID
ORDER by RWID;
-- populate temp table 3 from temp table 1 then split strings with dividers
SELECT RWID, RWLEN, DESCR, QTY, UNIT
INTO ##tmp3
FROM ##tmp1
UNION ALL
SELECT RWID, NULL RWLEN, NULL DESCR, NULL QTY, NULL UNIT
FROM ##tmp1
GROUP BY RWID
ORDER BY RWID, DESCR desc;
SELECT
RWID = CASE WHEN a.DESCR = b.AUX THEN a.RWID ELSE NULL END,
RWLEN = CASE WHEN a.DESCR = b.AUX THEN a.RWLEN ELSE NULL END,
a.DESCR,
a.QTY,
a.UNIT
INTO ##report
FROM ##tmp3
a
FULL OUTER JOIN ##tmp2
b on a.RWID = b.RWID;
SELECT *
FROM ##report
Thanks in advance for your time and assistance.
UPDATE! Thanks so much for all of your help, it really steered me in the right direction. I've figured out how to split the strings shown above as well as the other types of IDs that I'll encounter that I hadn't included in the example. Thanks again for your time and help, you're all awesome!!
Result: http://www.sqlfiddle.com/#!18/17a09/1
You can simply use a UNION ALL like following to get the desired output.
SELECT 'T1'+ SUBSTRING(RWID,CHARINDEX('/',RWID)+1,
LEN(RWID)- CHARINDEX('/',RWID)) -- + OTHER COLUMN
FROM [TABLE_NAME]
UNION ALL
SELECT 'T2'+ SUBSTRING(RWID,CHARINDEX('/',RWID)+1,
LEN(RWID)- CHARINDEX('/',RWID)) -- + OTHER COLUMN
FROM [TABLE_NAME]
Complete Example
DECLARE #TBL TABLE (RWID VARCHAR(30), RWLEN DECIMAL(15,2),
DESCR VARCHAR(50), QTY INT, UNIT VARCHAR(4))
INSERT INTO #TBL
values
('T2/10060', 20.0000 ,'SomeInfo', 1 ,'pcs'),
('T2/10061', 18.5689 ,'SomeInfo', 1 ,'pcs'),
('T2/10062', 20.0000 ,'SomeInfo', 1 ,'pcs')
SELECT 'T1'+ SUBSTRING(RWID, CHARINDEX('/',RWID)+1,LEN(RWID)- CHARINDEX('/',RWID)) RWID
,RWLEN, DESCR, QTY, UNIT
FROM #TBL
UNION ALL
SELECT 'T2'+ SUBSTRING(RWID, CHARINDEX('/',RWID)+1,LEN(RWID)- CHARINDEX('/',RWID)) RWID
,RWLEN, DESCR, QTY, UNIT
FROM #TBL
Output
+---------+-------+----------+-----+------+
| RWID | RWLEN | DESCR | QTY | UNIT |
+---------+-------+----------+-----+------+
| T110060 | 20.00 | SomeInfo | 1 | pcs |
+---------+-------+----------+-----+------+
| T110061 | 18.57 | SomeInfo | 1 | pcs |
+---------+-------+----------+-----+------+
| T110062 | 20.00 | SomeInfo | 1 | pcs |
+---------+-------+----------+-----+------+
| T210060 | 20.00 | SomeInfo | 1 | pcs |
+---------+-------+----------+-----+------+
| T210061 | 18.57 | SomeInfo | 1 | pcs |
+---------+-------+----------+-----+------+
| T210062 | 20.00 | SomeInfo | 1 | pcs |
+---------+-------+----------+-----+------+
DEMO
SQL-server using CTE
declare #table table (rwid varchar(30), rwlen float, descr varchar(50), qty int, unit varchar(4))
insert into #table
values
('T2/10060', 20.0000 ,'SomeInfo', 1 ,'pcs'),
('T2/10061', 18.5689 ,'SomeInfo', 1 ,'pcs'),
('T2/10062', 20.0000 ,'SomeInfo', 1 ,'pcs')
;with mycte as (
select *, cast(right(left(rwid,charindex('/',rwid)-1),1) as int) [num], 1 [start] from #table
union all
select t.*,c.start + 1, c.num from #table t
inner join mycte c
on c.rwid = t.rwid
and c.start + 1 <= c.num
)
select
concat(left(rwid,1), start,replace(rwid,left(rwid,charindex('/',rwid)+1),'')) ,
rwlen,
descr,
qty,
unit
from mycte
order by rwid, start
Using Cross apply
;WITH CTE( RWID, RWLEN,DESCR,QTY, UNIT)
AS
(
SELECT 'T2/10060',20.0000,'SomeInfo', 1,'pcs' UNION ALL
SELECT 'T2/10061',18.5689,'SomeInfo', 1,'pcs' UNION ALL
SELECT 'T2/10062',20.0000,'SomeInfo', 1,'pcs'
)
SELECT REPLACE(RWID,'2/1',CAST(Rnk AS VARCHAr(2))) AS RWID
,RWLEN
,DESCR
,QTY
,UNIT
FROM
(
SELECT C.*,ROW_NUMBER()OVER(PARTITION BY C.RWID ORDER BY C.RWID) AS Rnk
FROM CTE C
CROSS APPLY CTE C2
)dt WHERE Rnk<3
Result
http://www.sqlfiddle.com/#!18/9eecb/11626

How to generate an External ID based ID's that has negative value on price

I have this Data set
InvoiceID CDamount companyname
1 2500 NASA
1 -2500 NASA
2 1600 Airjet
3 5000 Boeing
4 -600 EXEarth
5 8000 SpaceX
5 -8000 SpaceX
I want to be able to get that as shown below:
External ID CDamount companyname
1 2500 NASA
1-C -2500 NASA
2 1600 Airjet
3 5000 Boeing
4 -600 EXEarth
5 8000 SpaceX
5-C -8000 SpaceX
I cannot use CASE WHEN CDamount < 0 THEN InvoiceID + '-' + 'C' ELSE InvoiceID END AS "External ID" because some of other companies have negative amount as well that do not fall under this category.
I was wondering how can I say IF InvoiceID is Duplicated AND CDAmount is Negative then Create a new External ID?
Is this something possible?
Below you can create the sample data
Create Table #Incident (
InvoiceID int,
CDamount int,
Companyname Nvarchar(255))
insert into #Incident Values (1,2500,'NASA')
insert into #Incident Values (1,-2500,'NASA')
insert into #Incident Values (2,1600,'Airjet')
insert into #Incident Values (3, 5000, 'Boeing')
insert into #Incident Values (4, -600, 'ExEarth')
insert into #Incident Values (5,8000,'SpaceX')
insert into #Incident Values (5, -8000, 'SpaceX')
Here is What I used but as I mentioned since ID number 4 has negative value as well I get "-C" for it which I do not want to.
Select CASE WHEN T1.CDamount < 0
THEN CAST(T1.InvoiceID AS nvarchar (255)) + '-' + 'C'
ELSE CAST(T1.InvoiceID AS nvarchar (255))
END AS ExternalID,
T1.Companyname
from #Incident AS T1
So I got this based on my knowledge of SQL and that works for my case.
Not sure if it is an smart way to go with but can be a good start for someone who is struggling with a Scenario like this:
;With CTE1 AS (
SELECT Count(*) AS Duplicate, T1.InvoiceID
From #Incident AS T1
Group by T1.InvoiceID
),
Main AS (
Select CASE WHEN T1.CDamount < 0 AND T2.Duplicate > 1
THEN CAST(T1.InvoiceID AS nvarchar (255)) + '-' + 'C'
ELSE CAST(T1.InvoiceID AS nvarchar (255))
END AS ExternalID,
T1.InvoiceID AS count,
T1.CDamount,
T1.Companyname
from #Incident AS T1
Join CTE1 AS T2 ON T1.InvoiceID = T2.InvoiceID
)
SELECT * FROM Main
Alternative solution without CTE, using ROW_NUMBER() function.
SELECT
CASE WHEN CDAmount < 0 AND RowID > 1
THEN InvoiceID + '-C'
ELSE InvoiceID
END AS ExternalID
, CDAmount
, CompanyName
FROM
(
SELECT
CAST(InvoiceID AS NVARCHAR(255)) AS InvoiceID
, CDAmount
, CompanyName
, ROW_NUMBER() OVER (PARTITION BY InvoiceID ORDER BY CompanyName) AS RowID
FROM
#Incident
) AS SourceTable
The trick is using ROW_NUMBER() function to generate a sequence which resets when InvoiceID changes. Here's the subquery and its result. Use CASE statement when CDAmount is negative and RowID greater than 1.
SELECT
CAST(InvoiceID AS NVARCHAR(255)) AS InvoiceID
, CDAmount
, CompanyName
, ROW_NUMBER() OVER (PARTITION BY InvoiceID ORDER BY CompanyName) AS RowID
FROM
#Incident
Subquery result:
+-----------+----------+-------------+-------+
| InvoiceID | CDAmount | CompanyName | RowID |
+-----------+----------+-------------+-------+
| 1 | 2500 | NASA | 1 |
| 1 | -2500 | NASA | 2 |
| 2 | 1600 | Airjet | 1 |
| 3 | 5000 | Boeing | 1 |
| 4 | -600 | ExEarth | 1 |
| 5 | 8000 | SpaceX | 1 |
| 5 | -8000 | SpaceX | 2 |
+-----------+----------+-------------+-------+

Date-based multiple group by T-SQL query

First of all, execuse the longer question, but I will try to put it as simply as possible...
I'm trying to write a kind of a reporting query, but I'm having a problem getting the desired results. The problem:
Employee table
Id | Name
---------------
1 | John Smith
2 | Alan Jones
3 | James Jones
Task table
Id | Title | StartDate | EmployeeId | Estimate (integer - ticks)
----------------------------------------------------------------------------
1 | task1 | 21.08.2011 | 1 | 90000000000
2 | task2 | 21.08.2011 | 1 | 150000000
3 | task3 | 22.08.2011 | 2 | 1230000000
Question:
How to get the estimate summary per day, grouped, but to include all the employees?
Like this:
Date | EmployeeId | EmployeeName | SummaryEstimate
-------------------------------------------------------------
19.08.2011 | 1 | John Smith | NULL
19.08.2011 | 2 | Alan Jones | NULL
19.08.2011 | 3 | James Jones | NULL
20.08.2011 | 1 | John Smith | NULL
20.08.2011 | 2 | Alan Jones | NULL
20.08.2011 | 3 | James Jones | NULL
21.08.2011 | 1 | John Smith | 90150000000
21.08.2011 | 2 | Alan Jones | NULL
21.08.2011 | 3 | James Jones | NULL
22.08.2011 | 1 | John Smith | NULL
22.08.2011 | 2 | Alan Jones | 1230000000
22.08.2011 | 3 | James Jones | NULL
What I currently do is I have a "dates" table with 30years of days. I left join and group by that table to get other dates included too. Well, here is the query:
SELECT dates.value, employee.Id, employee.Name, sum(task.Estimate)
FROM TableOfDates as dates
left join Tasks as task on (dates.value = convert(varchar(10), task.StartTime, 101))
left join Employees as employee on (employee.Id = task.EmployeeId)
WHERE dates.value >= '2011-08-19' and dates.value < '2011-08-22'
GROUP BY dates.value, employee.Id, employee.Name
ORDER BY dates.value, employee.Id
The convert call is to get the date part of the DateTime column.
The result that I get is:
Date | EmployeeId | EmployeeName | SummaryEstimate
-------------------------------------------------------------
19.08.2011 | NULL | NULL | NULL
20.08.2011 | NULL | NULL | NULL
21.08.2011 | 1 | John Smith | 90150000000
22.08.2011 | 2 | Alan Jones | 1230000000
I am there half of the way, I get dates that are not in the two base joined tables (Employees and Tasks) but I cannot also have all the employees included as in the table shown before this one.
I've tried cross-joining, then subqueries, but little luck there. Any help would be very much appreciated ! Thank you for having the time to go through all of this, I hope I was clear enough...
SELECT DE.DateValue, DE.EmployeeId, DE.EmployeeName, sum(task.Estimate)
FROM
( SELECT
D.value AS DateValue
, E.Id AS EmployeeId
, E.Name AS EmployeeName
FROM
TableOfDates D
CROSS JOIN Employees E ) DE
left join Tasks as task on DE.DateValue = convert(varchar(10), task.StartTime, 101)
AND DE.EmployeeId = task.EmployeeId
WHERE DE.DateValue >= '2011-08-19' and DE.DateValue < '2011-08-22'
GROUP BY DE.DateValue, DE.EmployeeId, DE.EmployeeName
ORDER BY DE.DateValue, DE.EmployeeId
Note that this solution offers the possibility to drop the day-table as you may use a dynamic recursive CTE instead.
The other CTE:s (Employees and Tasks) can be substituted with the real tables.
DECLARE #startDate DATETIME = '2011-08-01'
DECLARE #endDate DATETIME = '2011-09-01'
;WITH Employees(Id,Name)
AS
(
SELECT 1, 'John Smith'
UNION ALL
SELECT 2, 'Alan Jones'
UNION ALL
SELECT 3, 'James Jones'
)
,Tasks (Id, Title, StartDate, EmployeeId, Estimate)
AS
(
SELECT 1, 'task1', '2011-08-21', 1, 90000000000
UNION ALL
SELECT 2, 'task2', '2011-08-21', 1, 150000000
UNION ALL
SELECT 3, 'task3', '2011-08-22', 2, 1230000000
)
,TableOfDates(value)
AS
(
SELECT DATEADD(DAY, DATEDIFF(DAY, 0, #startDate), 0)
UNION ALL
SELECT DATEADD(DAY, 1, value)
FROM TableOfDates
WHERE value < #endDate
)
SELECT dates.value
,employee.Id
,employee.Name
,SUM(task.Estimate) AS SummaryEstimate
FROM TableOfDates dates
CROSS JOIN Employees employee
LEFT JOIN Tasks task
ON dates.value = task.StartDate
AND (employee.Id = task.EmployeeId)
WHERE dates.value >= '2011-08-19'
AND dates.value < '2011-08-26'
GROUP BY
dates.value
,employee.Id
,employee.Name
ORDER BY
dates.value
,employee.Id
use this query:
create table #T_dates (id_date int identity(1,1),inp_date datetime)
create table #T_tasks (id_task int identity(1,1),key_date int, key_emp int, est int)
create table #T_emp (id_emp int identity(1,1),name varchar(50))
insert #T_dates (inp_date) values ('08.19.2011')
insert #T_dates (inp_date) values ('08.20.2011')
insert #T_dates (inp_date) values ('08.21.2011')
insert #T_dates (inp_date) values ('08.22.2011')
insert #T_dates (inp_date) values ('08.23.2011')
insert #T_dates (inp_date) values ('08.24.2011')
--select * from #T_dates
insert #T_emp (name) values ('John Smith')
insert #T_emp (name) values ('Alan Jones')
insert #T_emp (name) values ('James Jones')
--select * from #T_emp
insert #T_tasks (key_date,key_emp,est) values (4,1,900000)
insert #T_tasks (key_date,key_emp,est) values (4,1,15000)
insert #T_tasks (key_date,key_emp,est) values (5,2,123000)
--select * from #T_tasks
select inp_date,id_emp,name,EST
from #T_emp
cross join #T_dates
left join
(
select key_date,key_emp,SUM(est) 'EST' from #T_tasks group by key_date,key_emp
) Gr
ON Gr.key_emp = id_emp and Gr.key_date = id_date
where inp_date >= '2011-08-19' and inp_date <= '2011-08-22'
order by inp_date,id_emp

Resources