sql server: peak active users by date range - sql

I needs to get count of max active users by date range.
Acrive users - it is a maximum number of not removed users.
I have UsersHistory Table:
HistoryID UserID IsRemoved OperationID ModificationDate
----------------------------------------------------------------------
1 1 0 'Add' 2012-07-24 04:27:48
2 2 0 'Add' 2012-07-26 04:18:48
3 3 0 'Add' 2012-07-27 04:29:48
4 1 0 'Update' 2012-07-28 04:47:48
5 2 0 'Update' 2012-07-29 04:01:48
6 1 1 'Remove' 2012-08-28 04:34:48
7 2 1 'Remove' 2012-08-28 04:18:48
8 3 1 'Remove' 2012-08-28 04:29:48
9 4 0 'Add' 2012-09-24 04:27:48
10 5 0 'Add' 2012-09-26 04:18:48
11 6 0 'Add' 2012-09-27 04:29:48
12 7 0 'Add' 2012-09-27 04:29:48
Expected result: Max active users by this period: 4 (HistoryID: 9, 10, 11, 12)
Update1:
HistoryID UserID IsRemoved OperationID ModificationDate
----------------------------------------------------------------------
1 1 0 'Add' 2012-07-24 04:27:48
2 2 0 'Add' 2012-07-26 04:18:48
3 3 0 'Add' 2012-07-27 04:29:48
4 1 1 'Remove' 2012-07-28 04:47:48
5 2 1 'Remove' 2012-07-28 04:47:48
6 3 1 'Remove' 2012-07-28 04:47:48
Expected result: Maximum of active(not removed) users by this period: 3

This should work for you. It will count total number of operations Add and total operations of Remove. After It subract total count of Remove from total count of Add.
DECLARE #counter1 INT,
#counter2 INT,
#result INT
SELECT #counter1 = c1
FROM(
SELECT COUNT(HistoryID) AS c1
FROM UsersHistory
WHERE OperationId = 'Add'
) x
SELECT #counter2 = c2
FROM(
SELECT COUNT(HistoryID) AS c2
FROM UsersHistory
WHERE OperationId = 'Remove'
) a
SET #result = #counter1 - #counter2
SELECT #result
EDIT
Here you go (It should count maximum as you need):
SELECT TOP 1 COUNT(*) AS maxRes
FROM (
SELECT DENSE_RANK() OVER (ORDER BY UserID ASC) AS [MaximumCount], *
FROM UsersHistory
WHERE OperationId = 'Add'
) a
WHERE
[MaximumCount] = UserID
GROUP BY MaximumCount
ORDER BY maxRes DESC

If I understand you correctly you need to make islands by OperationID and count maximum count of rows in 'Add' islands. If so then:
DECLARE #History TABLE
(
HistoryID INT ,
UserID INT ,
IsRemoved BIT ,
OperationID NVARCHAR(20)
)
INSERT INTO #History
VALUES ( 1, 1, 0, 'Add' ),
( 2, 2, 0, 'Add' ),
( 3, 3, 0, 'Add' ),
( 4, 1, 1, 'Remove' ),
( 5, 2, 1, 'Remove' ),
( 6, 3, 1, 'Remove' ),
( 7, 3, 0, 'Add' );
WITH cte1
AS ( SELECT OperationID ,
ROW_NUMBER() OVER ( ORDER BY HistoryID ) AS ID ,
1 AS Dummy ,
IIF(ISNULL(LAG(OperationID) OVER ( ORDER BY HistoryID ),
OperationID) = OperationID, 0, 1) AS ChangeMark
FROM #History
),
cte2
AS ( SELECT * ,
SUM(Dummy) OVER ( ORDER BY ID )
+ SUM(ChangeMark) OVER ( ORDER BY ID ) AS InervalID
FROM cte1
),
cte3
AS ( SELECT StartSeqNo = MIN(InervalID) ,
EndSeqNo = MAX(InervalID)
FROM ( SELECT InervalID ,
rn = InervalID
- ROW_NUMBER() OVER ( ORDER BY InervalID )
FROM cte2
) a
GROUP BY rn
),
cte4
AS ( SELECT COUNT(*) AS C
FROM cte2 c2
JOIN cte3 c3 ON c2.InervalID BETWEEN c3.StartSeqNo AND c3.EndSeqNo
WHERE c2.OperationID = 'Add'
GROUP BY c3.EndSeqNo
)
SELECT MAX(c) AS UserCount
FROM cte4

Related

Recursive CTE - recalculate the tree after exclusions

Lets say I have a table called #OrgList
CREATE TABLE #OrgList (
OrgUnitId int,
ParentOrgUnitId int,
PersonId int,
isExcluded bit
);
INSERT INTO #OrgList(OrgUnitId, ParentOrgUnitId, PersonId, isExcluded) VALUES
(1, NULL, 100, 0), (2,1, 101, 0), (3,1,102,0), (4,2,103,1), (5,2,104,0), (6,3,105,0), (7,4,106,0), (8,4,107,0), (9,4,108,0), (10,4,109,0), (11,4,110,1), (12,11,111,0)
My hierarchy tree structure looks like this
and my cte like this:
;
with cte as (
select OrgUnitId, ParentOrgUnitId, PersonId, isExcluded , 0 as level_num
from #OrgList
where ParentOrgUnitId is null
UNION ALL
select o.OrgUnitId, o.ParentOrgUnitId, o.PersonId, o.isExcluded , level_num+1 as level_num
from #OrgList o
join cte on o.ParentOrgUnitId=cte.OrgUnitId
)
select * from cte
I exclude OrgUnitId=4 and =11, then I want to update my recursive query that will recalculate the tree and show the new tree details, including level moves (there can be more levels and more consecutive exclusions, of course except the root node):
You should just add a second UNION ALL in your cte:
with cte as (
select OrgUnitId, ParentOrgUnitId, PersonId, isExcluded , 0 as level_num
from #OrgList
where ParentOrgUnitId is null
UNION ALL
select o.OrgUnitId, o.ParentOrgUnitId, o.PersonId, o.isExcluded , level_num+1 as level_num
from #OrgList o
join cte on o.ParentOrgUnitId=cte.OrgUnitId
where cte.isExcluded = 0
UNION ALL
select o.OrgUnitId, cte.ParentOrgUnitId, o.PersonId, o.isExcluded , level_num as level_num
from #OrgList o
join cte on o.ParentOrgUnitId=cte.OrgUnitId
where cte.isExcluded = 1
)
select * from cte
My approach:
Extend your initial CTE with an exclusion counter (ExclusionCount), counting the number of excluded nodes going from root to leaf nodes.
Add another recursive CTE to construct the upward path (cte_upwards) for each leaf node. Now decrement the counter added in the initial CTE.
Use a cross apply to select the first node where the upward path reaches an exlusion count of zero.
Solution:
with cte as -- initial CTE
(
select OrgUnitId,
ParentOrgUnitId,
PersonId,
IsExcluded,
convert(int, IsExcluded) as 'ExclusionCount', -- new counter
0 as 'level_num'
from #OrgList
where ParentOrgUnitId is null
union all
select o.OrgUnitId,
o.ParentOrgUnitId,
o.PersonId,
o.IsExcluded,
cte.ExclusionCount + convert(int, o.isExcluded), -- increment counter
cte.level_num + 1
from #OrgList o
join cte on o.ParentOrgUnitId = cte.OrgUnitId
),
cte_upwards as
(
select cte.OrgUnitId,
cte.ParentOrgUnitId as 'NewParentOrgUnitId',
cte.IsExcluded,
cte.ExclusionCount,
cte.level_num
from cte
where cte.ParentOrgUnitId is not null -- only leaf nodes (not a root)
and not exists ( select top 1 'x' -- only leaf nodes (not an intermediate node)
from cte cp
where cp.ParentOrgUnitId = cte.OrgUnitId )
union all
select cte_upwards.OrgUnitId,
cte.ParentOrgUnitId,
cte.IsExcluded,
cte_upwards.ExclusionCount - cte.IsExcluded, -- decrement counter
cte.level_num
from cte_upwards
join cte
on cte.OrgUnitId = cte_upwards.NewParentOrgUnitId
)
select cte.OrgUnitId,
cte.ParentOrgUnitId,
cte.IsExcluded,
x.NewParentOrgUnitId,
coalesce(x.NewParentOrgUnitId, cte.ParentOrgUnitId) as 'Recalculated'
from cte
outer apply ( select top 1 cu.NewParentOrgUnitId
from cte_upwards cu
where cu.OrgUnitId = cte.OrgUnitId
and cu.ExclusionCount = 0 -- node without excluded parent nodes
order by cu.level_num desc ) x -- select lowest node in upwards path
order by cte.OrgUnitId;
Result:
OrgUnitId ParentOrgUnitId IsExcluded NewParentOrgUnitId Recalculated
----------- --------------- ---------- ------------------ ------------
1 NULL 0 NULL NULL
2 1 0 NULL 1
3 1 0 NULL 1
4 2 1 NULL 2
5 2 0 2 2
6 3 0 3 3
7 4 0 2 2
8 4 0 2 2
9 4 0 2 2
10 4 0 2 2
11 4 1 NULL 4
12 11 0 2 2
I've added a VirtualParentOrgUnitId, which contains the parent with excluded nodes taken into account. I've also added a counter, VirtualDistance, which will report how many real hops there are between this node and it's virtual parent.
VirtualParentOrgUnitId will use the parent's ID if is not excluded, otherwise it will use it's parents's VirtualParentOrgUnitId, which allows chaining of multiple levels.
DROP TABLE IF EXISTS #OrgList
CREATE TABLE #OrgList (
OrgUnitId int,
ParentOrgUnitId int,
PersonId int,
isExcluded bit
);
INSERT INTO #OrgList(OrgUnitId, ParentOrgUnitId, PersonId, isExcluded) VALUES
(1, NULL, 100, 0), (2,1, 101, 0), (3,1,102,0), (4,2,103,1), (5,2,104,0), (6,3,105,0), (7,4,106,0), (8,4,107,0), (9,4,108,0), (10,4,109,0), (11,4,110,1), (12,11,111,0)
DROP TABLE IF EXISTS #Excludes
CREATE Table #Excludes (
OrgUnitId int
);
INSERT INTO #Excludes VALUES (4), (11);
with cte as (
select OrgUnitId, ParentOrgUnitId, ParentOrgUnitId VirtualParentOrgUnitId, 1 as VirtualDistance , PersonId, isExcluded , 0 as level_num
from #OrgList
where ParentOrgUnitId is null
UNION ALL
select o.OrgUnitId, o.ParentOrgUnitId, IIF(o.ParentOrgUnitId IN (SELECT OrgUnitId FROM #Excludes),cte.VirtualParentOrgUnitId, o.ParentOrgUnitId ), IIF(o.ParentOrgUnitId IN (SELECT OrgUnitId FROM #Excludes),VirtualDistance + 1, 1 ), o.PersonId, o.isExcluded , level_num+1 as level_num
from #OrgList o
join cte on o.ParentOrgUnitId=cte.OrgUnitId
)
select * from cte
Here are the results:
OrgUnitId ParentOrgUnitId VirtualParentOrgUnitId VirtualDistance PersonId isExcluded level_num
----------- --------------- ---------------------- --------------- ----------- ---------- -----------
1 NULL NULL 0 100 0 0
2 1 1 0 101 0 1
3 1 1 0 102 0 1
6 3 3 0 105 0 2
4 2 2 0 103 1 2
5 2 2 0 104 0 2
7 4 2 1 106 0 3
8 4 2 1 107 0 3
9 4 2 1 108 0 3
10 4 2 1 109 0 3
11 4 2 1 110 1 3
12 11 2 2 111 0 4
;
with cte as (
select OrgUnitId, ParentOrgUnitId, PersonId, isExcluded , 0 as level_num, 0 as level_after_exclusions,
cast(',' as varchar(max)) + case isExcluded when 1 then cast(OrgUnitId as varchar(20)) else '' end as excludedmembers,
case isExcluded when 1 then ParentOrgUnitId end as newParentId
from #OrgList
where ParentOrgUnitId is null
UNION ALL
select o.OrgUnitId, o.ParentOrgUnitId, o.PersonId, o.isExcluded , level_num + 1, level_after_exclusions + case o.isExcluded when 1 then 0 else 1 end,
excludedmembers + case o.isExcluded when 1 then cast(o.OrgUnitId as varchar(20))+',' else '' end,
case when excludedmembers like '%,'+cast(o.ParentOrgUnitId as varchar(20))+',%' then newParentId else o.ParentOrgUnitId end
from #OrgList o
join cte on o.ParentOrgUnitId=cte.OrgUnitId
)
select *, level_num - level_after_exclusions as shiftbylevels
from cte

Issue with Row_Number() Over Partition

I've been trying to reset the row_number when the value changes on Column Value and I have no idea on how should i do this.
This is my SQL snippet:
WITH Sch(SubjectID, VisitID, Scheduled,Actual,UserId,RLev,SubjectTransactionID,SubjectTransactionTypeID,TransactionDateUTC,MissedVisit,FieldId,Value) as
(
select
svs.*,
CASE WHEN stdp.FieldID = 'FrequencyRegime' and svs.SubjectTransactionTypeID in (2,3) THEN
stdp.FieldID
WHEN stdp.FieldID is NULL and svs.SubjectTransactionTypeID = 1
THEN NULL
WHEN stdp.FieldID is NULL
THEN 'FrequencyRegime'
ELSE stdp.FieldID
END AS [FieldID],
CASE WHEN stdp.Value is NULL and svs.SubjectTransactionTypeID = 1
THEN NULL
WHEN stdp.Value IS NULL THEN
(SELECT TOP 1 stdp.Value from SubjectTransaction st
JOIN SubjectTransactionDataPoint STDP on stdp.SubjectTransactionID = st.SubjectTransactionID and stdp.FieldID = 'FrequencyRegime'
where st.SubjectID = svs.SubjectID
order by st.ServerDateST desc)
ELSE stdp.Value END AS [Value]
from SubjectVisitSchedule svs
left join SubjectTransactionDataPoint stdp on svs.SubjectTransactionID = stdp.SubjectTransactionID and stdp.FieldID = 'FrequencyRegime'
)
select
Sch.*,
CASE WHEN sch.Value is not NULL THEN
ROW_NUMBER() over(partition by Sch.Value, Sch.SubjectID order by Sch.SubjectID, Sch.VisitID)
ELSE NULL
END as [FrequencyCounter],
CASE WHEN Sch.Value = 1 THEN 1--v.Quantity
WHEN Sch.Value = 2 and (ROW_NUMBER() over(partition by Sch.Value, Sch.SubjectID order by Sch.SubjectID, Sch.VisitID) % 2) <> 0
THEN 0
WHEN Sch.Value = 2 and (ROW_NUMBER() over(partition by Sch.Value, Sch.SubjectID order by Sch.SubjectID, Sch.VisitID) % 2) = 0
THEN 1
ELSE NULL
END AS [DispenseQuantity]
from Sch
--left join VisitDrugAssignment v on v.VisitID = Sch.VisitID
where SubjectID = '4E80718E-D0D8-4250-B5CF-02B7A259CAC4'
order by SubjectID, VisitID
This is my Dataset:
Based on the Dataset, I am trying to reset the FrequencyCounter to 1 every time the value changes for each subject, Right now it does 50% of what I want, It is counting when the value 1 or 2 is found, but when value 1 comes again after value 2 it continues the count from where it left. I want every time the value is changes the count to also start from the beginning.
It's difficult to reproduce and test without sample data, but if you want to know how to number rows based on change in column value, next approach may help. It's probably not the best one, but at least will give you a good start. Of course, I hope I understand your question correctly.
Data:
CREATE TABLE #Data (
[Id] int,
[Subject] varchar(3),
[Value] int
)
INSERT INTO #Data
([Id], [Subject], [Value])
VALUES
(1, '801', 1),
(2, '801', 2),
(3, '801', 2),
(4, '801', 2),
(5, '801', 1),
(6, '801', 2),
(7, '801', 2),
(8, '801', 2)
Statement:
;WITH ChangesCTE AS (
SELECT
*,
CASE
WHEN LAG([Value]) OVER (PARTITION BY [Subject] ORDER BY [Id]) <> [Value] THEN 1
ELSE 0
END AS [Change]
FROM #Data
), GroupsCTE AS (
SELECT
*,
SUM([Change]) OVER (PARTITION BY [Subject] ORDER BY [Id]) AS [GroupID]
FROM ChangesCTE
)
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY [GroupID] ORDER BY [Id]) AS Rn
FROM GroupsCTE
Result:
--------------------------------------
Id Subject Value Change GroupID Rn
--------------------------------------
1 801 1 0 0 1
2 801 2 1 1 1
3 801 2 0 1 2
4 801 2 0 1 3
5 801 1 1 2 1
6 801 2 1 3 1
7 801 2 0 3 2
8 801 2 0 3 3
As per my understanding, you need DENSE_RANK as you are looking for the row number will only change when value changed. The syntax will be as below-
WITH your_table(your_column)
AS
(
SELECT 2 UNION ALL
SELECT 10 UNION ALL
SELECT 2 UNION ALL
SELECT 11
)
SELECT *,DENSE_RANK() OVER (ORDER BY your_column)
FROM your_table

SQL Rank() function excluding rows

Consider I have the following table.
ID value
1 100
2 200
3 200
5 250
6 1
I have the following query which gives the result as follows. I want to exclude the value 200 from rank function, but still that row has to be returned.
SELECT
CASE WHEN Value = 200 THEN 0
ELSE DENSE_RANK() OVER ( ORDER BY VALUE DESC)
END AS RANK,
ID,
VALUE
FROM #table
RANK ID VALUE
1 5 250
0 2 200
0 3 200
4 1 100
5 6 1
But I want the result as follows. How to achieve it?
RANK ID VALUE
1 5 250
0 2 200
0 3 200
2 1 100
3 6 1
If VAL column is not nullable, taking into account NULL is the last value in ORDER BY .. DESC
select *, dense_rank() over (order by nullif(val,200) desc) * case val when 200 then 0 else 1 end
from myTable
order by val desc;
There is no way to exclude Val in Dense Rank currently ,unless you filter in where clause..that is the reason ,you get below result
RANK ID VALUE
1 5 250
0 2 200
0 3 200
4 1 100
5 6 1
You will need to filter once and then do a union all
;with cte(id,val)
as
(
select 1, 100 union all
select 2, 200 union all
select 3, 200 union all
select 5, 250 union all
select 6, 1 )
select *, dense_rank() over (order by val desc)
from cte
where val<>200
union all
select 0,id,val from cte where val=200
You could split the ranking in to separate queries for the values you want to include/exclude from the ranking and UNION ALL the results like so:
Standalone executable example:
CREATE TABLE #temp ( [ID] INT, [value] INT );
INSERT INTO #temp
( [ID], [value] )
VALUES ( 1, 100 ),
( 2, 200 ),
( 3, 200 ),
( 5, 250 ),
( 6, 1 );
SELECT *
FROM ( SELECT 0 RANK ,
ID ,
value
FROM #temp
WHERE value = 200 -- set rank to 0 for value = 200
UNION ALL
SELECT DENSE_RANK() OVER ( ORDER BY value DESC ) AS RANK ,
ID ,
value
FROM #temp
WHERE value != 200 -- perform ranking on records != 200
) t
ORDER BY value DESC ,
t.ID
DROP TABLE #temp
Produces:
RANK ID value
1 5 250
0 2 200
0 3 200
2 1 100
3 6 1
You can modify the ordering at the end of the statement if required, I set it to produce your desired results.
You can also try this, too:
SELECT ISNULL(R, 0) AS Rank ,t.id ,t.value
FROM tbl1 AS t
LEFT JOIN ( SELECT id ,DENSE_RANK() OVER ( ORDER BY value DESC ) AS R
FROM dbo.tbl1 WHERE value <> 200
) AS K
ON t.id = K.id
ORDER BY t.value DESC
The solution in the original question was actually pretty close. Just adding a partition clause to the dense_rank can do the trick.
SELECT CASE
WHEN VALUE = 200 THEN 0
ELSE DENSE_RANK() OVER(
PARTITION BY CASE WHEN VALUE = 200 THEN 0 ELSE 1 END
ORDER BY VALUE DESC
)
END AS RANK
,ID
,VALUE
FROM #table
ORDER BY VALUE DESC;
The 'partition by' creates separate groups for the dense_rank such that the order is performed on these groups individually. This essentially means you create two ranks at the same time, one for the group without the 200 value and one for the group with only the 200 value. The latter one to be set to 0 in the 'case when'.
Standalone executable example:
DECLARE #table TABLE
(
ID INT NOT NULL PRIMARY KEY
,VALUE INT NULL
)
INSERT INTO #table
(
ID
,VALUE
)
SELECT 1, 100
UNION SELECT 2, 200
UNION SELECT 3, 200
UNION SELECT 5, 250
UNION SELECT 6, 1;
SELECT CASE
WHEN VALUE = 200 THEN 0
ELSE DENSE_RANK() OVER(
PARTITION BY CASE WHEN VALUE = 200 THEN 0 ELSE 1 END
ORDER BY VALUE DESC
)
END AS RANK
,ID
,VALUE
FROM #table
ORDER BY VALUE DESC;
RANK ID VALUE
1 5 250
0 2 200
0 3 200
2 1 100
3 6 1

Recursive CTE and Count Info from recursive table

I have this table called tblLandAreas:
Level 1 = Bloq
Level 2 = Ships
Level 3 = Sides
Level 4 = Beds
Ship is a child of Bloq, Sides is a child of Ship and Bed is a child of Side.
And I need to show a report this way:
Only when _parentid is null
I have tried this but it is not working:
Try the following:
DECLARE #t TABLE
(
ID INT ,
ParentID INT ,
Level INT
)
INSERT INTO #t
VALUES ( 1, NULL, 1 ),
( 29, NULL, 1 ),
( 38, 29, 2 ),
( 32, 1, 2 ),
( 18, 1, 2 ),
( 41, 29, 2 ),
( 42, 41, 3 ),
( 43, 41, 3 ),
( 44, 41, 3 ),
( 45, 44, 4 )
;WITH cte AS(SELECT *, id AS baseid FROM #t WHERE ParentID IS NULL
UNION ALL
SELECT t.*, c.baseid FROM cte c JOIN #t t ON c.ID = t.ParentID)
SELECT baseid,
SUM(CASE WHEN Level = 2 THEN 1 ELSE 0 END) ships,
SUM(CASE WHEN Level = 3 THEN 1 ELSE 0 END) sides,
SUM(CASE WHEN Level = 4 THEN 1 ELSE 0 END) beds
FROM cte
WHERE ParentID IS NOT NULL
GROUP BY baseid
Output:
baseid ships sides beds
1 2 0 0
29 2 3 1
To project on your structure:
;WITH Bloque AS(SELECT LandAreaId, _ParentId, _Level, LandAreaId AS baseLandAreaId
FROM tblLandAreas WHERE _ParentId IS NULL
UNION ALL
SELECT a.LandAreaId, a._ParentId, a._Level, B.baseLandAreaId
FROM Bloque B
JOIN tblLandAreas a ON B.LandAreaId = a._ParentId)
SELECT baseLandAreaId AS LandAreaId,
SUM(CASE WHEN _Level = 2 THEN 1 ELSE 0 END) [#Ships],
SUM(CASE WHEN _Level = 3 THEN 1 ELSE 0 END) [#Sides],
SUM(CASE WHEN _Level = 4 THEN 1 ELSE 0 END) [#Beds]
FROM Bloque
WHERE _ParentId IS NOT NULL
GROUP BY baseLandAreaId

SQL: get latest (un)subscribeaction from table

I have the following table:
ID (int)
EMAIL (varchar(50))
CAMPAIGNID (int)
isSubscribe (bit)
isActionByUser (bit)
This table stores all subscribe and unsubscribe actions on campaigns for a user. These actions can be done by the user itself (isActionByUser = true) or by the administration desk (isActionByUser = false).
I need to get the last action to determine if a user is subscribed or unsubscribed. But keeping in mind that when a user did an unsubscribe action from a campaign, it will have priority on other subscribe actions by the administration desk.
I have found a nice solution to get the lastest record grouped by EMAIL and CAMPAIGNID. But I can't figure out how I do incorporate the requirement that an isActionByUser = true, has absolute priority over records with isActionByUser = false.
Also: when the administration desk does an unsubscribe action, it will have priority over a record with (isSubscribe = true and isActionByUser).
Example data:
ID EMAIL CAMPAIGNID ISSUBSCRIBE ISACTIONBYUSER
-----------------------------------------------------------
1 a#aa.com 1 1 0
2 b#bb.com 1 1 0
3 c#cc.com 1 1 0
4 a#aa.com 1 0 1
5 a#aa.com 1 1 0
6 c#cc.com 1 1 1
7 c#cc.com 1 0 0
The expected result would be:
ID EMAIL CAMPAIGNID ISSUBSCRIBE ISACTIONBYUSER
-----------------------------------------------------------
2 b#bb.com 1 1 0
4 a#aa.com 1 0 1
7 c#cc.com 1 0 0
With the following query
select cs1.*
from
[TABLE] cs1
left join
[TABLE] cs2
on
cs1.EM_EMAIL = cs2.EM_EMAIL
and
cs1.EM_CAMPAIGNID = cs2.EM_CAMPAIGNID
and
cs1.id < cs2.id
where cs2.id is null
I' m having the following result:
ID EMAIL CAMPAIGNID ISSUBSCRIBE ISACTIONBYUSER
-----------------------------------------------------------
2 b#bb.com 1 1 0
5 a#aa.com 1 1 0
7 c#cc.com 1 0 0
Another approach:
SELECT *
FROM [TABLE] cs
WHERE id in
(
SELECT top 1 id
FROM [TABLE] ss
WHERE
cs.EMAIL = ss.EMAIL
and
cs.CAMPAIGNID = ss.CAMPAIGNID
and ISSUBSCRIBE = (
select top 1 min(convert(int, ISSUBSCRIBE))
FROM [TABLE] sss
WHERE
cs.EMAIL = sss.EMAIL
and
cs.CAMPAIGNID = sss.CAMPAIGNID
)
and ISACTIONBYUSER= (
select top 1 max(convert(int, ISACTIONBYUSER))
FROM [TABLE] ssss
WHERE
cs.EMAIL = ssss.EMAIL
and
cs.CAMPAIGNID = ssss.CAMPAIGNID
)
)
This will produce the following result:
ID EMAIL CAMPAIGNID ISSUBSCRIBE ISACTIONBYUSER
-----------------------------------------------------------
2 b#bb.com 1 1 0
4 a#aa.com 1 0 1
6 c#cc.com 1 1 1
Which is also not correct. And I'm afraid performance will be a big rpoblem with this approach.
So any ideas how I can solve this?
Ok, try the following query:
SELECT DISTINCT B.*
FROM YourTable A
OUTER APPLY (SELECT TOP 1 *
FROM YourTable
WHERE Email = A.Email AND CampaignId = A.CampaignId
ORDER BY CASE WHEN ISSUBSCRIBE = 0 THEN 1 ELSE 2 END,
CASE WHEN ISACTIONBYUSER = 1 THEN 1 ELSE 2 END,
ID DESC) B
Try this: [Updated to handle unsubscribe and subscribed users]
declare #test table (id int, email varchar(100), CAMPAIGNID int, ISSUBSCRIBE bit, ISACTIONBYUSER bit)
INSERT INTO #test
SELECT 1,'a#aa.com',1,1,0 UNION
SELECT 2,'b#bb.com',1,1,0 UNION
SELECT 3,'c#cc.com',1,1,0 UNION
SELECT 4,'a#aa.com',1,0,1 UNION
SELECT 5,'a#aa.com',1,1,0 UNION
SELECT 6,'c#cc.com',1,1,1 UNION
SELECT 7,'c#cc.com',1,0,0 UNION
select 8, 'd#dd.com', 1, 1, 1 UNION
select 9, 'd#dd.com', 1, 0, 1 UNION
select 10, 'd#dd.com', 1, 1, 1
;WITh CTE AS
(
select s.*,
ROW_NUMBER() OVER (PARTITION BY email,campaignid
ORDER BY
case
when ISSUBSCRIBE = 0 AND ISACTIONBYUSER = 0 THEN 1
when ISSUBSCRIBE = 0 AND ISACTIONBYUSER = 1 THEN 1
when ISSUBSCRIBE = 1 AND ISACTIONBYUSER = 1 THEN 1 ELSE 2 END, ID DESC) Rn1
from #test s
)
SELECT * FROM CTE WHERE Rn1 = 1
order by id
This is some standard SQL that might get you there, though it's not the prettiest ever:
Updated:
select s.*
from Subscriptions s
join (
-- Apply the user unsubscribe logic to get the proper ID
select case when b.ID is not null and a.ISACTIONBYUSER = 0 then b.ID else a.ID end as ID
from (
-- Latest overall
select ID, EMAIL, CAMPAIGNID,
(select ISACTIONBYUSER from Subscriptions where ID = z.ID) as ISACTIONBYUSER
from (
select max(ID) as ID, EMAIL, CAMPAIGNID
from Subscriptions a
group by EMAIL, CAMPAIGNID
) as z
) as a
left join (
-- Latest user unsubscribe
select max(ID) as ID, EMAIL, CAMPAIGNID, 1 as ISACTIONBYUSER
from Subscriptions
where ISSUBSCRIBE = 0
and ISACTIONBYUSER = 1
group by EMAIL, CAMPAIGNID
) as b on a.EMAIL = b.EMAIL
and a.CAMPAIGNID = b.CAMPAIGNID
) as i on s.ID = i.ID
I've updated this to account for this case:
insert into Subscriptions select 8, 'd#dd.com', 1, 1, 1
insert into Subscriptions select 9, 'd#dd.com', 1, 0, 1
insert into Subscriptions select 10, 'd#dd.com', 1, 1, 1