Sub query in the where clause - sql

The code below is working now in the VIEW based on Windows Authentication, users should able to see all the data that they own and data of those reports to them direct or indirect. Now another WHERE clause needed to handle the additional result of data that giving to the user in the Authorize column.
SAMPLE DATA: Table TORGANIZATION_HIERARCHY
ManagerID | ManagerEmail | Email | EmployeeID | Authorize | Level
---------------------------------------------------------------------------------
NULL | NULL | user0##abc.com | 1 | NULL | 0
1 | user0##abc.com | user1##abc.com | 273 | NULL | 1
273 | user1##abc.com | user2##abc.com | 16 | NULL | 2
273 | user1##abc.com | SJiang##abc.com | 274 | NULL | 2
273 | user1##abc.com | SAbbas#abc.com | 285 | user2##abc.com; user3#abc.com | 2
285 | SAbbas#abc.com | LTsoflias#abc.com | 286 | NULL | 3
274 | SJiang##abc.com | MBlythe#abc.com | 275 | NULL | 3
274 | SJiang##abc.com | LMitchell#abc.com | 276 | NULL | 3
16 | JWhite#abc.com | user3#abc.com | 23 | NULL | 3
SAMPLE DATA: Table TRANS
Email | Destination_account | Customer_service_rep_code
-----------------------------------------------------------
SAbbas#abc.com | Philippines | 12646
Junerk#abc.com | Canada | 95862
LTsoflias#abc.com | Italy | 98524
user2##abc.com | Italy | 29185
user3##abc.com | Brazil | 58722
The bottom query is working when user SAbbas#abc.com (285) log in. It can see all the data of EmployeeID 285 and 286. I need add another where statement that user (SAbbas#abc.com) authorized to see to see in column Authorize. So the result user SAbbas#abc.com should see EmployeeID 285, 286, 16, 23.
WITH CTE
AS (SELECT OH.employeeid,
OH.managerid,
OH.email AS EMPEMAIL,
1 AS level
FROM TORGANIZATION_HIERARCHY OH
WHERE OH.[email] = (SELECT SYSTEM_USER) --Example SAbbas#abc.com
UNION ALL
SELECT CHIL.employeeid,
CHIL.managerid,
CHIL.email,
level + 1
FROM TORGANIZATION_HIERARCHY CHIL
JOIN CTE PARENT
ON CHIL.[managerid] = PARENT.[employeeid]),
ANOTHERCTE
AS (SELECT
T.[email],
T.[destination_account],
T.[customer_service_rep_code]
FROM [KGFGJK].[DBO].[TRANS] AS T)
SELECT *
FROM ANOTHERCTE
INNER JOIN CTE
ON CTE.empemail = ANOTHERCTE.[email];

This will give you what you need based on column Authorize. The result should be 16 and 23
Select b.employeeid from TORGANIZATION_HIERARCHY a inner join TORGANIZATION_HIERARCHY b
on a.Authorize like '%' + b.Email + '%'
where a.Email = 'SAbbas#abc.com'
Let me know
Complete Solution:
For you to be able to see user3#abc.com, I had to correct the email in 6the table #TRANS. You worte in there user3##abc.com instead of user3#abc.com. # and not ##.
the code is below for your tests. After you can replace with you table names
IF OBJECT_ID('tempdb..#TORGANIZATION_HIERARCHY') IS NOT NULL DROP TABLE #TORGANIZATION_HIERARCHY;
select NULL as ManagerID ,NULL as ManagerEmail ,'user0##abc.com' as Email ,1 as EmployeeID ,NULL as Authorize , 0 as Level into #TORGANIZATION_HIERARCHY
union select 1 ,'user0##abc.com', 'user1##abc.com' ,273 ,NULL , 1
union select 273 ,'user1##abc.com', 'user2##abc.com' ,16 ,NULL , 2
union select 273 ,'user1##abc.com', 'SJiang##abc.com' ,274 ,NULL , 2
union select 273 ,'user1##abc.com', 'SAbbas#abc.com' ,285 ,'user2##abc.com; user3#abc.com' , 2
union select 285 ,'SAbbas#abc.com', 'LTsoflias#abc.com' ,286 ,NULL , 3
union select 274 ,'SJiang##abc.com', 'MBlythe#abc.com' ,275 ,NULL , 3
union select 274 ,'SJiang##abc.com', 'LMitchell#abc.com' ,276 ,NULL , 3
union select 16 ,'JWhite#abc.com', 'user3#abc.com' ,23 ,NULL , 3
--select * from #TORGANIZATION_HIERARCHY
IF OBJECT_ID('tempdb..#TRANS') IS NOT NULL DROP TABLE #TRANS;
select 'SAbbas#abc.com' as Email , 'Philippines' as Destination_account , 12646 as Customer_service_rep_code into #TRANS
union select 'Junerk#abc.com' , 'Canada' , 95862
union select 'LTsoflias#abc.com', 'Italy' , 98524
union select 'user2##abc.com' , 'Italy' , 29185
union select 'user3#abc.com' , 'Brazil' , 58722
;WITH CTE
AS (SELECT OH.employeeid,
OH.managerid,
OH.Authorize,
OH.email AS EMPEMAIL,
1 AS [level]
FROM #TORGANIZATION_HIERARCHY OH
WHERE OH.[email] = (SELECT 'SAbbas#abc.com') --Example
UNION ALL
SELECT CHIL.employeeid,
CHIL.managerid,
CHIL.Authorize,
CHIL.email,
CHIL.[level] + 1
FROM #TORGANIZATION_HIERARCHY CHIL
JOIN CTE PARENT
ON CHIL.[managerid] = PARENT.[employeeid]),
ANOTHERCTE
AS (SELECT
T.[email],
T.[destination_account],
T.[customer_service_rep_code]
FROM #TRANS AS T)
SELECT *
FROM ANOTHERCTE
RIGHT JOIN
(
select a.EmployeeID, a.ManagerID, a.Authorize, a.Email as empemail, a.[level] From CTE INNER JOIN #TORGANIZATION_HIERARCHY a on lower(CTE.Authorize) like '%' + lower(a.Email) + '%'
union
select * From CTE
) CTE
ON CTE.empemail = ANOTHERCTE.[email]
order by [level]
Output:

Related

How to create a query with all of dependencies in hierarchical organization?

I've been trying hard to create a query to see all dependencies in a hierarchical organization. But the only I have accuaried is to retrieve the parent dependency. I have attached an image to show what I need.
Thanks for any clue you can give me.
This is the code I have tried with the production table.
WITH CTE AS
(SELECT
H1.systemuserid,
H1.pes_aprobadorid,
H1.yomifullname,
H1.internalemailaddress
FROM [dbo].[ext_systemuser] H1
WHERE H1.pes_aprobadorid is null
UNION ALL
SELECT
H2.systemuserid,
H2.pes_aprobadorid,
H2.yomifullname,
H2.internalemailaddress
FROM [dbo].[ext_systemuser] H2
INNER JOIN CTE c ON h2.pes_aprobadorid=c.systemuserid)
SELECT *
FROM CTE
OPTION (MAXRECURSION 1000)
You are almost there with your query. You just have to include all rows as a starting point. Also the join should be cte.parent_id = ext.user_id and not the other way round. I've done an example query in postgres, but you shall easily adapt it to your DBMS.
with recursive st_units as (
select 0 as id, NULL as pid, 'Director' as nm
union all select 1, 0, 'Department 1'
union all select 2, 0, 'Department 2'
union all select 3, 1, 'Unit 1'
union all select 4, 3, 'Unit 1.1'
),
cte AS
(
SELECT id, pid, cast(nm as text) as path, 1 as lvl
FROM st_units
UNION ALL
SELECT c.id, u.pid, cast(path || '->' || u.nm as text), lvl + 1
FROM st_units as u
INNER JOIN cte as c on c.pid = u.id
)
SELECT id, pid, path, lvl
FROM cte
ORDER BY lvl, id
id | pid | path | lvl
-: | ---: | :--------------------------------------- | --:
0 | null | Director | 1
1 | 0 | Department 1 | 1
2 | 0 | Department 2 | 1
3 | 1 | Unit 1 | 1
4 | 3 | Unit 1.1 | 1
1 | null | Department 1->Director | 2
2 | null | Department 2->Director | 2
3 | 0 | Unit 1->Department 1 | 2
4 | 1 | Unit 1.1->Unit 1 | 2
3 | null | Unit 1->Department 1->Director | 3
4 | 0 | Unit 1.1->Unit 1->Department 1 | 3
4 | null | Unit 1.1->Unit 1->Department 1->Director | 4
db<>fiddle here
I've reached this code that it is working but when I include a hierarchy table of more than 1800 the query is endless.
With cte AS
(select systemuserid, systemuserid as pes_aprobadorid, internalemailaddress, yomifullname
from #TestTable
union all
SELECT c.systemuserid, u.pes_aprobadorid, u.internalemailaddress, u.yomifullname
FROM #TestTable as u
INNER JOIN cte as c on c.pes_aprobadorid = u.systemuserid
)
select distinct * from cte
where pes_aprobadorid is not null
OPTION (MAXRECURSION 0)

Hierarchical structure, new columns, denormalization

Suppose I have table like this:
id parent_id name
11 NULL Company
33 11 Department 1
44 33 Department 2
I would like to transform it into:
id parent_id name Level1 Level2 Level3
11 NULL Company NULL NULL NULL
22 11 Company Department 1 NULL NULL
33 22 Company Department 1 Department 2 NULL
I am able to create a CTE and come up with Levels column showing a value in hierarchy, but I don't know how to make new columns for departments as presented.
with myCTE as (
select c.id, c."name", c.parent_id, 1 as Level
from table1 c
where c.parent_id IS NULL
UNION ALL
Select c1.id, c1."name", c1.parent_id, Level +1
from table1 c1
inner join myCTE on c1.parent_id = myCTE.id
where c1.parent_id IS NOT NULL
)
select * from myCTE
showing:
id parent_id name level
1 11 NULL Company 1
2 22 11 Department 2 2
3 33 22 Department 3 3
An (almost) fully generic approach:
DECLARE #tbl TABLE(id INT,parent_id INT,name VARCHAR(100));
INSERT INTO #tbl VALUES
(11,NULL,'Company')
,(33,11,'Department 1')
,(44,33,'Department 2a')
,(55,33,'Department 2b')
,(66,44,'SubDep 2a');
--The recursive CTE will build an XML fragment on a row-by-row level
--The SELECT will use XML method .nodes() and ROW_NUMBER to generate column names for PIVOT
WITH recCTE AS
(
SELECT id, parent_id,name,(SELECT name AS [*] FOR XML PATH('')) AS NameConcat
FROM #tbl WHERE parent_id IS NULL
UNION ALL
SELECT t.id,t.parent_id,t.name,recCTE.NameConcat + '</lvl><lvl>' + (SELECT t.name AS [*] FOR XML PATH(''))
FROM #tbl AS t
INNER JOIN recCTE ON recCTE.id=t.parent_id
)
SELECT p.*
FROM
(
SELECT id
,parent_id
,name
,'Level' + REPLACE(STR(ROW_NUMBER() OVER(PARTITION BY id ORDER BY (SELECT NULL)),2),' ','0') AS HierarchyRank
,lvl.value(N'(./text())[1]','nvarchar(max)') AS HierarchyName
FROM recCTE
CROSS APPLY (SELECT CAST('<lvl>' + NameConcat + '</lvl>' AS XML) AS PreLevels ) AS Casted
CROSS APPLY Casted.PreLevels.nodes(N'/lvl') AS A(lvl)
) AS tbl
PIVOT
(
MAX(HierarchyName) FOR HierarchyRank IN(Level01,Level02,Level03,Level04,Level05,Level06,Level07,Level08,Level09)
) AS p;
The result
+----+-----------+----------------+---------+----------------+---------------+-----------+---------+
| id | parent_id | name | Level01 | Level02 | Level03 | Level04 | Level05 |
+----+-----------+----------------+---------+----------------+---------------+-----------+---------+
| 11 | NULL | Company | Company | NULL | NULL | NULL | NULL |
+----+-----------+----------------+---------+----------------+---------------+-----------+---------+
| 33 | 11 | Department 1 | Company | Department 1 | NULL | NULL | NULL |
+----+-----------+----------------+---------+----------------+---------------+-----------+---------+
| 44 | 33 | Department 2a | Company | Department 1 | Department 2a | NULL | NULL |
+----+-----------+----------------+---------+----------------+---------------+-----------+---------+
| 55 | 33 | Department 2b | Company | Department 1 | Department 2b | NULL | NULL |
+----+-----------+----------------+---------+----------------+---------------+-----------+---------+
| 66 | 44 | SubDep 2a | Company | Department 1 | Department 2a | SubDep 2a | NULL |
+----+-----------+----------------+---------+----------------+---------------+-----------+---------+
If you need more levels, the only need was to add more column names into the PIVOT part...
You can calculate the rows for every level, and unite them :
with MyCTE as (
select id, parent_id, name, null as level1, null as level2, null as level3
from table1 as root
where root.parent_id is null
union
select level1.id, level1.parent_id, root.name, level1.name as level1, null as level2, null as level3
from table1 as level1
inner join table1 as root on root.id = level1.parent_id
where root.parent_id is null
union
select level2.id, level2.parent_id, root.name, level1.name as level1, level2.name as level2, null as level3
from table1 as level2
inner join table1 as level1 on level1.id = level2.parent_id
inner join table1 as root on root.id = level1.parent_id
where root.parent_id is null
union
select level3.id, level3.parent_id, root.name, level1.name as level1, level2.name as level2, level3.name as level3
from table1 as level3
inner join table1 as level2 on level2.id = level3.parent_id
inner join table1 as level1 on level1.id = level2.parent_id
inner join table1 as root on root.id = level1.parent_id
where root.parent_id is null
)
select * from MyCTE
If you need more levels, you will need to add more selects with additional joins

How to get detail table records as string in SQL Server

I have a table in database which contains hospital names as follow:
+------------+--------------+
| HospitalID | HospitalName |
+------------+--------------+
| 1 | Hosp1 |
| 2 | Hosp2 |
| 3 | Hosp3 |
| 4 | Hosp4 |
+------------+--------------+
Another table also exist which contains activity names as follows:
+------------+--------------+
| ActivityID | ActivityName |
+------------+--------------+
| 1 | Act1 |
| 2 | Act2 |
| 3 | Act3 |
| 4 | Act4 |
| 5 | Act5 |
+------------+--------------+
There's a N*M relation between these tables, i.e. each hospital can operate different activities. Therefore another table is required as follows:
+----+------------+------------+
| ID | HospitalID | ActivityID |
+----+------------+------------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 5 |
| 4 | 2 | 1 |
| 5 | 2 | 3 |
| 6 | 3 | 2 |
+----+------------+------------+
I want to write a select statement which selects hospital names and their related activities in a string field as follows:
+--------------+------------------+
| HospitalName | ActivityNames |
+--------------+------------------+
| Hosp1 | Act1, Act2, Act5 |
| Hosp2 | Act1, Act3 |
| Hosp3 | Act2 |
| Hosp4 | |
+--------------+------------------+
I have written the select statement using a function for ActivityNames field using a cursor but it is not optimized and the system performance decreases as the number of records increases.
Any solution or suggestion on how to solve this problem?
You can do this just with a select. No need of looping or Cursor for this. Looping will make performance degrade.
So the Schema will be
CREATE TABLE #HOSPITAL( HOSPITALID INT, HOSPITALNAME VARCHAR(20))
INSERT INTO #HOSPITAL
SELECT 1, 'HOSP1'
UNION ALL
SELECT 2 , 'HOSP2'
UNION ALL
SELECT 3 ,'HOSP3'
UNION ALL
SELECT 4 , 'HOSP4'
CREATE TABLE #ACTIVITY( ActivityID INT, ActivityName VARCHAR(50) )
INSERT INTO #ACTIVITY
SELECT 1, 'Act1'
UNION ALL
SELECT 2, 'Act2'
UNION ALL
SELECT 3, 'Act3'
UNION ALL
SELECT 4, 'Act4'
UNION ALL
SELECT 5, 'Act5'
CREATE TABLE #HOSPITAL_ACT_MAP(ID INT, HospitalID INT, ActivityID INT)
INSERT INTO #HOSPITAL_ACT_MAP
SELECT 1, 1, 1
UNION ALL
SELECT 2, 1, 2
UNION ALL
SELECT 3, 1, 5
UNION ALL
SELECT 4, 2, 1
UNION ALL
SELECT 5, 2, 3
UNION ALL
SELECT 6, 3, 2
And do Select like below with CTE
;WITH CTE AS (
SELECT DISTINCT H.HOSPITALNAME, A.ActivityName FROM #HOSPITAL_ACT_MAP HA
INNER JOIN #HOSPITAL H ON HA.HospitalID = H.HOSPITALID
INNER JOIN #ACTIVITY A ON HA.ActivityID = A.ActivityID
)
SELECT HOSPITALNAME
, (SELECT STUFF((SELECT ','+ActivityName FROM CTE C1
WHERE C1.HOSPITALNAME = C.HOSPITALNAME
FOR XML PATH('')),1,1,''))
FROM CTE C
GROUP BY HOSPITALNAME
Edit from Comments
If you can't use CTE and Stuff go for Method 2
DECLARE #TAB TABLE (HOSPITALNAME VARCHAR(20),ActivityName VARCHAR(20) )
INSERT INTO #TAB
SELECT DISTINCT H.HOSPITALNAME, A.ActivityName FROM #HOSPITAL_ACT_MAP HA
INNER JOIN #HOSPITAL H ON HA.HospitalID = H.HOSPITALID
INNER JOIN #ACTIVITY A ON HA.ActivityID = A.ActivityID
SELECT HOSPITALNAME, SUBSTRING(ACTIVITIES,1, LEN(ACTIVITIES)-1) FROM(
SELECT DISTINCT HOSPITALNAME,(SELECT ActivityName+',' FROM #TAB T1
WHERE T1.HOSPITALNAME = T.HOSPITALNAME
FOR XML PATH('') ) AS ACTIVITIES FROM #TAB T
)A
Note: For the performance purpose I have stored the Intermediate result on #TAB (Table variable). If you want you can directly Query it with Sub Query.
using STUFF function to achieve your result :
CREATE TABLE #Hospital(HospitalID INT,HospitalName VARCHAR(100))
CREATE TABLE #Activity(ActivityID INT,ActivityName VARCHAR(100))
CREATE TABLE #RelationShip(Id INT,HospId INT,ActId INT)
CREATE TABLE #ConCat(HospitalID INT ,HospName VARCHAR(100), ActName
VARCHAR(100),UpFlag TINYINT DEFAULT(0))
DECLARE #HospId INT = 0,#String VARCHAR(200) = ''
INSERT INTO #Hospital(HospitalID ,HospitalName )
SELECT 1,'Hosp1' UNION ALL
SELECT 2,'Hosp2' UNION ALL
SELECT 3,'Hosp3' UNION ALL
SELECT 4,'Hosp4'
INSERT INTO #Activity(ActivityID ,ActivityName )
SELECT 1,'Act1' UNION ALL
SELECT 2,'Act2' UNION ALL
SELECT 3,'Act3' UNION ALL
SELECT 4,'Act4' UNION ALL
SELECT 5,'Act5'
INSERT INTO #RelationShip(ID,HospId,ActId)
SELECT 1 , 1 , 1 UNION ALL
SELECT 2 , 1 , 2 UNION ALL
SELECT 3 , 1 , 5 UNION ALL
SELECT 4 , 2 , 1 UNION ALL
SELECT 5 , 2 , 3 UNION ALL
SELECT 6 , 3 , 2
SELECT HospitalName , STUFF( ( SELECT ',' + ActivityName FROM #Activity
JOIN #RelationShip ON ActId = ActivityID WHERE HospId = HospitalID FOR XML
PATH('') ),1,1,'')
FROM #Hospital
GROUP BY HospitalID,HospitalName
***FOR SQLServer2005 Use below code***
INSERT INTO #ConCat (HospitalID ,HospName)
SELECT DISTINCT HospitalID ,HospitalName
FROM #Hospital
WHILE EXISTS(SELECT 1 FROM #ConCat WHERE UpFlag = 0)
BEGIN
SELECT #HospId = HospitalID FROM #ConCat WHERE UpFlag = 0 ORDER BY
HospitalID
SET #String = ''
SELECT #String = ISNULL(#String,'') + CAST(A.ActivityName AS VARCHAR) +
',' FROM
(
SELECT ActivityName
FROM #RelationShip
JOIN #Activity ON ActId = ActivityID
WHERE HospId = #HospId
) A
UPDATE #ConCat SET UpFlag = 1,ActName = CASE WHEN #String = '' THEN
#String ELSE SUBSTRING(#String,0,LEN(#String) ) END WHERE HospitalID
= #HospId
END
SELECT * FROM #ConCat
You can use json to increase the performance of front end.
There is no particular solution if you are using open source databases.
Try to use IBM db2 or ORACLE database to ensure performance of your app.
then generate json data . You will find the improvement in speed

How to create a condition for this case?

Sample Table:
Id |Acc_Code|Description |Balance | Acclevel| Acctype| Exttype|
--- -------- ----------------- |-------- |-------- | -------| -------|
1 |SA |Sales | 0.00 | 1 | SA | |
2 |CS |Cost of Sales | 0.00 | 1 | CS | |
3 |5000/001|Revenue | 94.34 | 2 | SA | |
4 |5000/090|Sales(Local) | 62.83 | 2 | SA | |
5 |7000/000|Manufacturing Acc |-250.80 | 2 | CS | MA |
6 |7000/200|Manufacturing Acc | 178.00 | 2 | CS | |
This is a sample data of a temporary table which would be used to be inserted into another temporary table that would calculate the data for Profit and Loss Statement (For Manufacturing related Accounts only).
In this case, the acc_code for Manufacturing accounts start from 7000/000 and separated/partitioned for each following Exttype.
Eg: We start from the exttype of MA and based on its acclevel (could be 2 or more) until the next exttype.
The idea is we get the manufacturing accounts by SELECT FROM tmp_acc_list WHERE acc_code BETWEEN #start_acc_code (7000/000 in this case) AND #end_acc_code (the data before the next exttype)
I don't know what the exttype is, I'm still learning the tables.
How do we create the #end_acc_code part out from this sample table?
So here is a all in one script.
I created Your table for test:
create table #tmp_acc_list(
Id numeric,
Acc_Code nvarchar(100),
Acclevel numeric,
Acctype nvarchar(100),
Exttype nvarchar(100));
GO
insert into #tmp_acc_list(Id, Acc_Code, Acclevel, Acctype, Exttype)
select 1 , 'SA', 1,'SA', null union all
select 2 , 'CS', 1,'CS', null union all
select 3 , '5000/001', 2,'SA', null union all
select 4 , '5000/090', 2,'SA', null union all
select 5 , '7000/000', 2,'CS', 'MA' union all
select 6 , '7000/200', 2,'CS', null
;
Then comes the query:
with OrderedTable as -- to order the table is Id is not an order
(
select
t.*, ROW_NUMBER() over (
order by id asc --use any ordering You need here
)
as RowNum
from
#tmp_acc_list as t
),
MarkedTable as -- mark with common number
(
select
t.*,
Max(case when t.Exttype is null then null else t.RowNum end)
over (order by t.RowNum) as GroupRownum
from OrderedTable as t
),
GroupedTable as -- add group Exttype
(
select
t.Id, t.Acc_Code, t.Acclevel, t.Acctype, t.Exttype,
max(t.Exttype) over (partition by t.GroupRownum) as GroupExttype
from MarkedTable as t
)
select * from GroupedTable where GroupExttype = 'MA'
Is this what You need?
select *
from
(
select Id, Acc_Code
from tmp_acc_list
where Acc_Code = '7000/000'
) s
cross join tmp_acc_list a
cross apply
(
select top 1 x.Id, x.Acc_Code
from tmp_acc_list x
where x.Id >= a.Id
and x.AccLevel = a.AccLevel
and x.Acctype = a.Acctype
and x.Exttype = ''
order by Id desc
) e
where a.Id between s.Id and e.Id

Query for missing elements

I have a table with the following structure:
timestamp | name | value
0 | john | 5
1 | NULL | 3
8 | NULL | 12
12 | john | 3
33 | NULL | 4
54 | pete | 1
180 | NULL | 4
400 | john | 3
401 | NULL | 4
592 | anna | 2
Now what I am looking for is a query that will give me the sum of the values for each name, and treats the nulls in between (orderd by the timestamp) as the first non-null name down the list, as if the table were as follows:
timestamp | name | value
0 | john | 5
1 | john | 3
8 | john | 12
12 | john | 3
33 | pete | 4
54 | pete | 1
180 | john | 4
400 | john | 3
401 | anna | 4
592 | anna | 2
and I would query SUM(value), name from this table group by name. I have thought and tried, but I can't come up with a proper solution. I have looked at recursive common table expressions, and think the answer may lie in there, but I haven't been able to properly understand those.
These tables are just examples, and I don't know the timestamp values in advance.
Could someone give me a hand? Help would be very much appreciated.
With Inputs As
(
Select 0 As [timestamp], 'john' As Name, 5 As value
Union All Select 1, NULL, 3
Union All Select 8, NULL, 12
Union All Select 12, 'john', 3
Union All Select 33, NULL, 4
Union All Select 54, 'pete', 1
Union All Select 180, NULL, 4
Union All Select 400, 'john', 3
Union All Select 401, NULL, 4
Union All Select 592, 'anna', 2
)
, NamedInputs As
(
Select I.timestamp
, Coalesce (I.Name
, (
Select I3.Name
From Inputs As I3
Where I3.timestamp = (
Select Max(I2.timestamp)
From Inputs As I2
Where I2.timestamp < I.timestamp
And I2.Name Is not Null
)
)) As name
, I.value
From Inputs As I
)
Select NI.name, Sum(NI.Value) As Total
From NamedInputs As NI
Group By NI.name
Btw, what would be orders of magnitude faster than any query would be to first correct the data. I.e., update the name column to have the proper value, make it non-nullable and then run a simple Group By to get your totals.
Additional Solution
Select Coalesce(I.Name, I2.Name), Sum(I.value) As Total
From Inputs As I
Left Join (
Select I1.timestamp, MAX(I2.Timestamp) As LastNameTimestamp
From Inputs As I1
Left Join Inputs As I2
On I2.timestamp < I1.timestamp
And I2.Name Is Not Null
Group By I1.timestamp
) As Z
On Z.timestamp = I.timestamp
Left Join Inputs As I2
On I2.timestamp = Z.LastNameTimestamp
Group By Coalesce(I.Name, I2.Name)
You don't need CTE, just a simple subquery.
select t.timestamp, ISNULL(t.name, (
select top(1) i.name
from inputs i
where i.timestamp < t.timestamp
and i.name is not null
order by i.timestamp desc
)), t.value
from inputs t
And summing from here
select name, SUM(value) as totalValue
from
(
select t.timestamp, ISNULL(t.name, (
select top(1) i.name
from inputs i
where i.timestamp < t.timestamp
and i.name is not null
order by i.timestamp desc
)) as name, t.value
from inputs t
) N
group by name
I hope I'm not going to be embarassed by offering you this little recursive CTE query of mine as a solution to your problem.
;WITH
numbered_table AS (
SELECT
timestamp, name, value,
rownum = ROW_NUMBER() OVER (ORDER BY timestamp)
FROM your_table
),
filled_table AS (
SELECT
timestamp,
name,
value
FROM numbered_table
WHERE rownum = 1
UNION ALL
SELECT
nt.timestamp,
name = ISNULL(nt.name, ft.name),
nt.value
FROM numbered_table nt
INNER JOIN filled_table ft ON nt.rownum = ft.rownum + 1
)
SELECT *
FROM filled_table
/* or go ahead aggregating instead */