在我看来,这是一个分两个阶段的行动。
- 确定电话呼叫的哪些部分在何时使用哪些费率。
- 将每个费率的时间相加。
第 1 阶段比第 2 阶段更棘手。我在 IBM Informix Dynamic Server (IDS) 中运行了该示例,因为我没有 MS SQL Server。这些想法应该很容易翻译。 INTO TEMP 子句创建一个具有适当模式的临时表;该表是会话私有的,并在会话结束时消失(或者您明确删除它)。在 IDS 中,您还可以使用显式 CREATE TEMP TABLE 语句,然后使用 INSERT INTO temp-table SELECT ... 作为执行与 INTO TEMP 相同工作的更详细方法。
正如在 SO 的 SQL 问题中经常出现的那样,您没有向我们提供模式,因此每个人都必须发明一个可能与您所描述的内容匹配或不匹配的模式。
假设您的数据位于两个表中。第一个表是通话记录记录,通话的基本信息,例如拨打电话的电话、被叫号码、通话开始时间和通话时长:
CREATE TABLE clr -- call log record
(
phone_id VARCHAR(24) NOT NULL, -- billing plan
called_number VARCHAR(24) NOT NULL, -- needed to validate call
start_time TIMESTAMP NOT NULL, -- date and time when call started
duration INTEGER NOT NULL -- duration of call in seconds
CHECK(duration > 0),
PRIMARY KEY(phone_id, start_time)
-- other complicated range-based constraints omitted!
-- foreign keys omitted
-- there would probably be an auto-generated number here too.
);
INSERT INTO clr(phone_id, called_number, start_time, duration)
VALUES('650-656-3180', '650-794-3714', '2009-02-26 15:17:19', 186234);
为了方便起见(主要是为了节省多次写入加法),我想要一份包含实际结束时间的 clr 表的副本:
SELECT phone_id, called_number, start_time AS call_start, duration,
start_time + duration UNITS SECOND AS call_end
FROM clr
INTO TEMP clr_end;
关税数据存储在一个简单的表中:
CREATE TABLE tariff
(
tariff_code CHAR(1) NOT NULL -- code for the tariff
CHECK(tariff_code IN ('P','N','O'))
PRIMARY KEY,
rate_start TIME NOT NULL, -- time when rate starts
rate_end TIME NOT NULL, -- time when rate ends
rate_charged DECIMAL(7,4) NOT NULL -- rate charged (cents per second)
);
INSERT INTO tariff(tariff_code, rate_start, rate_end, rate_charged)
VALUES('N', '00:00:00', '08:00:00', 0.9876);
INSERT INTO tariff(tariff_code, rate_start, rate_end, rate_charged)
VALUES('P', '08:00:00', '19:00:00', 2.3456);
INSERT INTO tariff(tariff_code, rate_start, rate_end, rate_charged)
VALUES('O', '19:00:00', '23:59:59', 1.2345);
我争论了费率表应该使用 TIME 还是 INTERVAL 值;在这种情况下,时间与相对于午夜的间隔非常相似,但间隔可以添加到时间戳,而时间则不能。我坚持使用《时间》,但这让事情变得一团糟。
此查询的棘手部分是在没有循环的情况下生成每个费率的相关日期和时间范围。事实上,我最终使用嵌入在存储过程中的循环来生成整数列表。 (我还使用了 IBM Informix Dynamic Server IDS 特有的技术,使用系统目录中的表 ID 号作为 1..N 范围内的连续整数源,适用于版本中从 1 到 60 的数字11.50。)
CREATE PROCEDURE integers(lo INTEGER DEFAULT 0, hi INTEGER DEFAULT 0)
RETURNING INT AS number;
DEFINE i INTEGER;
FOR i = lo TO hi STEP 1
RETURN i WITH RESUME;
END FOR;
END PROCEDURE;
在简单的情况下(也是最常见的情况),呼叫属于单一资费周期;多时段的通话增添了兴奋感。
假设我们可以创建一个与该模式匹配的表表达式,并涵盖我们可能需要的所有时间戳值:
CREATE TEMP TABLE tariff_date_time
(
tariff_code CHAR(1) NOT NULL,
rate_start TIMESTAMP NOT NULL,
rate_end TIMESTAMP NOT NULL,
rate_charged DECIMAL(7,4) NOT NULL
);
幸运的是,您没有提到周末费率,因此您向客户收取相同的费用
周末的价格与一周内的价格相同。然而,答案应该适应这样的
如果可能的话。如果你要变得像提供周末费率一样复杂
公共假期,但在圣诞节或新年期间,您将收取高峰费率,而不是
由于需求量很大,周末费率很高,那么您最好将费率存储在永久的 rates_date_time 表中。
填充关税日期时间的第一步是生成与呼叫相关的日期列表:
SELECT DISTINCT EXTEND(DATE(call_start) + number, YEAR TO SECOND) AS call_date
FROM clr_end,
TABLE(integers(0, (SELECT DATE(call_end) - DATE(call_start) FROM clr_end)))
AS date_list(number)
INTO TEMP call_dates;
两个日期值之间的差异是整数天(在 IDS 中)。
步骤integers生成从 0 到调用涵盖的天数的值,并将结果存储在临时表中。对于多条记录的更一般情况,最好计算最小和最大日期并生成中间的日期,而不是多次生成日期,然后使用 DISTINCT 子句消除它们。
现在使用费率表与 call_dates 表的笛卡尔积来生成每天的费率信息。这是关税时间作为间隔更整齐的地方。
SELECT r.tariff_code,
d.call_date + (r.rate_start - TIME '00:00:00') AS rate_start,
d.call_date + (r.rate_end - TIME '00:00:00') AS rate_end,
r.rate_charged
FROM call_dates AS d, tariff AS r
INTO TEMP tariff_date_time;
现在我们需要将通话记录记录与适用的资费进行匹配。该条件是处理重叠的标准方法 - 如果第一个时间段的结束晚于第二个时间段的开始并且第一个时间段的开始早于第二个时间段的结束,则两个时间段重叠:
SELECT tdt.*, clr_end.*
FROM tariff_date_time tdt, clr_end
WHERE tdt.rate_end > clr_end.call_start
AND tdt.rate_start < clr_end.call_end
INTO TEMP call_time_tariff;
然后我们需要确定费率的开始和结束时间。费率的开始时间是资费开始时间和通话开始时间中较晚的一个。资费结束时间为资费结束时间和通话结束时间中较早的一个:
SELECT phone_id, called_number, tariff_code, rate_charged,
call_start, duration,
CASE WHEN rate_start < call_start THEN call_start
ELSE rate_start END AS rate_start,
CASE WHEN rate_end >= call_end THEN call_end
ELSE rate_end END AS rate_end
FROM call_time_tariff
INTO TEMP call_time_tariff_times;
最后,我们需要将每个关税费率所花费的时间相加,并将该时间(以秒为单位)乘以收取的费率。由于 SUM(rate_end -rate_start) 的结果是 INTERVAL,而不是数字,因此我必须调用转换函数将 INTERVAL 转换为十进制秒数,而该(非标准)函数是 iv_seconds:
SELECT phone_id, called_number, tariff_code, rate_charged,
call_start, duration,
SUM(rate_end - rate_start) AS tariff_time,
rate_charged * iv_seconds(SUM(rate_end - rate_start)) AS tariff_cost
FROM call_time_tariff_times
GROUP BY phone_id, called_number, tariff_code, rate_charged,
call_start, duration;
对于示例数据,这产生了数据(为了简洁起见,我没有打印电话号码和被叫号码):
N 0.9876 2009-02-26 15:17:19 186234 0 16:00:00 56885.760000000
O 1.2345 2009-02-26 15:17:19 186234 0 10:01:11 44529.649500000
P 2.3456 2009-02-26 15:17:19 186234 1 01:42:41 217111.081600000
这是一个非常昂贵的电话,但电信公司会对此感到满意。您可以查看任何中间结果来查看答案是如何得出的。您可以使用更少的临时表,但会牺牲一些清晰度。
对于单个调用,这与在客户端中运行 VB 中的代码没有太大区别。对于很多调用来说,这有可能提高效率。我远不相信递归在 VB 中是必要的 - 直接迭代就足够了。