在我看来,这是一个有两个阶段的操作。
- 确定电话的哪些部分在何时使用哪些费率。
- 将每个费率的时间相加。
第 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 值;在这种情况下,时间与相对于午夜的时间间隔非常相似,但时间间隔可以添加到时间不能添加的时间戳中。我坚持使用 TIME,但它让事情变得一团糟。
此查询的棘手部分是为每个关税生成相关的日期和时间范围,而无需循环。事实上,我最终使用了一个嵌入存储过程的循环来生成一个整数列表。(我还使用了一种特定于 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
);
幸运的是,您没有提到周末费率,所以您向客户收取相同的费用
周末的价格与一周内的价格相同。但是,答案应该适应这样的
如果可能的话。如果您要变得像给出周末价格一样复杂
公共假期,圣诞节或新年除外,您收取高峰费率而不是
由于需求量大,周末费率,那么您最好将费率存储在永久关税日期时间表中。
填充关税_日期_时间的第一步是生成与呼叫相关的日期列表:
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 中)。过程整数生成从 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 转换为 DECIMAL 秒数,并且该(非标准)函数是 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中递归是必要的——直接迭代就足够了。