在我们公司,我们的客户执行各种活动,我们登录不同的表格 - 访谈出勤,课程出勤和其他一般活动。 我有一个数据库视图,来自所有这些表的联合数据,为我们提供了这样的ActivityView。 正如您所看到的,某些活动重叠 - 例如,在参加面试时,客户可能正在执行CV更新活动。
+----------------------+---------------+---------------------+-------------------+
| activity_client_id | activity_type | activity_start_date | activity_end_date |
+----------------------+---------------+---------------------+-------------------+
| 112 | Interview | 2015-06-01 09:00 | 2015-06-01 11:00 |
| 112 | CV updating | 2015-06-01 09:30 | 2015-06-01 11:30 |
| 112 | Course | 2015-06-02 09:00 | 2015-06-02 16:00 |
| 112 | Interview | 2015-06-03 09:00 | 2015-06-03 10:00 |
+----------------------+---------------+---------------------+-------------------+
每个客户都有一个"注册日期",记录在客户端表上,即他们加入我们的程序时。这是我们的样本客户端:
+-----------+---------------------+
| client_id | client_sign_up_date |
+-----------+---------------------+
| 112 | 2015-05-20 |
+-----------+---------------------+
我需要创建一个显示以下列的报告:
+-----------+---------------------+--------------------------------------------+
| client_id | client_sign_up_date | date_client_completed_5_hours_of_activity |
+-----------+---------------------+--------------------------------------------+
我们需要此报告才能了解我们的计划是否有效。该计划的一个重要目标是让每个客户尽快完成至少5个小时的活动。 因此,本报告将告诉我们注册需要多长时间才能让每个客户实现这一数字。
这更加棘手的是,当我们计算5个小时的总活动时,我们必须打折重叠活动:
在上面的样本数据中,客户参加了09:00至11:00之间的访谈 同一天,他们还在09:30至11:30进行了CV更新活动。 对于我们的计算,这将为他们提供2.5小时(150分钟)当天的总活动 - 我们只计算30分钟的简历更新,因为面试将其重叠到11:00。
因此,我们的示例客户端的报告将给出以下结果:
+-----------+---------------------+--------------------------------------------+
| client_id | client_sign_up_date | date_client_completed_5_hours_of_activity |
+-----------+---------------------+--------------------------------------------+
| 112 | 2015-05-20 | 2015-06-02 |
+-----------+---------------------+--------------------------------------------+
所以我的问题是如何使用select语句创建报告? 我可以通过编写一个将遍历视图并将结果写入报表的存储过程来解决如何执行此操作的方法。 但是我更希望避免使用存储过程并使用select语句来直接提供报告。
我正在使用SQL Server 2005.
答案 0 :(得分:2)
请参阅SQL Fiddle here。
with tbl as (
-- this will generate daily merged ovelaping time
select distinct
a.id
,(
select min(x.starttime)
from act x
where x.id=a.id and ( x.starttime between a.starttime and a.endtime
or a.starttime between x.starttime and x.endtime )
) start1
,(
select max(x.endtime)
from act x
where x.id=a.id and ( x.endtime between a.starttime and a.endtime
or a.endtime between x.starttime and x.endtime )
) end1
from act a
), tbl2 as
(
-- this will add minute and total minute column
select
*
,datediff(mi,t.start1,t.end1) mi
,(select sum(datediff(mi,x.start1,x.end1)) from tbl x where x.id=t.id and x.end1<=t.end1) totalmi
from tbl t
), tbl3 as
(
-- now final query showing starttime and endtime for 5 hours other wise null in case not completed 5(300 minutes) hours
select
t.id
,min(t.start1) starttime
,min(case when t.totalmi>300 then t.end1 else null end) endtime
from tbl2 t
group by t.id
)
-- final result
select *
from tbl3
where endtime is not null
答案 1 :(得分:1)
这是一种方法:
;WITH CTErn AS (
SELECT activity_client_id, activity_type,
activity_start_date, activity_end_date,
ROW_NUMBER() OVER (PARTITION BY activity_client_id
ORDER BY activity_start_date) AS rn
FROM activities
),
CTEdiff AS (
SELECT c1.activity_client_id, c1.activity_type,
x.activity_start_date, c1.activity_end_date,
DATEDIFF(mi, x.activity_start_date, c1.activity_end_date) AS diff,
ROW_NUMBER() OVER (PARTITION BY c1.activity_client_id
ORDER BY x.activity_start_date) AS seq
FROM CTErn AS c1
LEFT JOIN CTErn AS c2 ON c1.rn = c2.rn + 1
CROSS APPLY (SELECT CASE
WHEN c1.activity_start_date < c2.activity_end_date
THEN c2.activity_end_date
ELSE c1.activity_start_date
END) x(activity_start_date)
)
SELECT TOP 1 client_id, client_sign_up_date, activity_start_date,
hoursOfActivicty
FROM CTEdiff AS c1
INNER JOIN clients AS c2 ON c1.activity_client_id = c2.client_id
CROSS APPLY (SELECT SUM(diff) / 60.0
FROM CTEdiff AS c3
WHERE c3.seq <= c1.seq) x(hoursOfActivicty)
WHERE hoursOfActivicty >= 5
ORDER BY seq
SQL Server 2005引入了 公用表表达式和ROW_NUMBER()
,因此上述查询应该适用于该版本。
第一个CTE
,即CTErn
,会产生以下输出:
client_id activity_type start_date end_date rn
112 Interview 2015-06-01 09:00 2015-06-01 11:00 1
112 CV updating 2015-06-01 09:30 2015-06-01 11:30 2
112 Course 2015-06-02 09:00 2015-06-02 16:00 3
112 Interview 2015-06-03 09:00 2015-06-03 10:00 4
第二个CTE
,即CTEdiff
,使用上面的表格来计算每条记录的时差,同时考虑到与之前记录的任何重叠:
client_id activity_type start_date end_date diff seq
112 Interview 2015-06-01 09:00 2015-06-01 11:00 120 1
112 CV updating 2015-06-01 11:00 2015-06-01 11:30 30 2
112 Course 2015-06-02 09:00 2015-06-02 16:00 420 3
112 Interview 2015-06-03 09:00 2015-06-03 10:00 60 4
最终查询计算时间差的累积总和,并选择超过5小时活动的第一条记录。
上述查询适用于简单间隔重叠,即仅当活动的结束日期与下一个活动的开始日期重叠时。
答案 2 :(得分:0)
对于another issue,到目前为止,我采用了几何方法
填料。即,我将日期和时间转换为sql几何
输入并利用geometry::UnionAggregate
合并范围。
我认为这不能在sql-server 2005中使用。但是您的 这个问题真是一个有趣的难题,我想看看 几何方法是否可行。所以任何未来 遇到此问题的用户可以访问以后 版本可以考虑。
在“数字”中:
在“ mergeLines”中:
在“重命名”中:
在外部查询中:
with
numbers as (
select row_number() over (order by (select null)) i
from @activities -- where I put your data
),
mergeLines as (
select activity_client_id,
lines = geometry::UnionAggregate(line)
from @activities
cross apply (select
startP = geometry::Point(convert(float,activity_start_date), 0, 0),
stopP = geometry::Point(convert(float,activity_end_date), 0, 0)
) pointify
cross apply (select line = startP.STUnion(stopP).STEnvelope()) lineify
group by activity_client_id
),
redate as (
select client_id = activity_client_id,
activities_start_date,
activities_end_date,
minutes,
rollingMinutes = sum(minutes) over(
partition by activity_client_id
order by activities_start_date
rows between unbounded preceding and current row
)
from mergeLines ml
join numbers n on n.i between 1 and ml.lines.STNumGeometries()
cross apply (select line = ml.lines.STGeometryN(i).STEnvelope()) l
cross apply (select
activities_start_date = convert(datetime, l.line.STPointN(1).STX),
activities_end_date = convert(datetime, l.line.STPointN(3).STX)
) unprepare
cross apply (select minutes =
round(datediff(s, activities_start_date, activities_end_date) / 60.0,0)
) duration
)
select client_id,
activities_start_date,
activities_end_date,
met_5hr_goal = dateadd(minute, (60 * 5) - prevRoll, activities_start_date)
from (
select *,
prevRoll = lag(rollingMinutes) over (
partition by client_id
order by rollingMinutes
)
from redate
) ranker
where rollingMinutes >= 60 * 5
and prevRoll < 60 * 5;