これは2段階の操作のように思えます。
- 通話のどの部分がどの時間にどの料金を使用するかを決定します。
- 各レートの時間を合計します。
フェーズ 1 はフェーズ 2 よりも複雑です。私は MS SQL Server を持っていないので、IBM Informix Dynamic Server (IDS) で例を実行しました。アイデアは簡単に翻訳できるはずです。INTO TEMP 句は、適切なスキーマで一時テーブルを作成します。テーブルはセッションに対してプライベートであり、セッションが終了すると (または明示的に削除すると) 消えます。IDS では、明示的な CREATE TEMP TABLE ステートメントを使用してから、INSERT INTO temp-table SELECT ... を使用して、INTO TEMP と同じジョブをより詳細に実行することもできます。
SO に関する SQL の質問でよくあることですが、スキーマが提供されていません。
データが 2 つのテーブルにあるとします。最初のテーブルには、コール ログ レコード、コールを発信した電話、コールされた番号、コールの開始時間、コールの継続時間など、行われたコールに関する基本情報があります。
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 値のどちらを使用するかを議論しました。このコンテキストでは、時刻は午前 0 時を基準とした間隔に非常に似ていますが、時刻ができないタイムスタンプに間隔を追加できます。私はTIMEに固執しましたが、それは物事を混乱させました.
このクエリのトリッキーな部分は、各料金表に関連する日付と時刻の範囲をループなしで生成することです。実際、ストアド プロシージャに埋め込まれたループを使用して、整数のリストを生成することになりました。(また、システム カタログのテーブル ID 番号を 1..N の範囲の連続した整数のソースとして使用する、IBM Informix Dynamic Server IDS に固有の手法も使用しました。 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
);
幸いなことに、週末の料金について言及していないため、顧客に同じ料金を請求します。
週末と平日の料金。ただし、答えはそのようなものに適応する必要があります
可能であれば状況。週末のレートを指定するのと同じくらい複雑になる場合
ただし、クリスマスまたは正月は通常料金ではなくピーク料金を請求します。
需要が高いために週末料金がかかる場合は、料金を永続的な tax_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;
2 つの日付値の差は整数の日数 (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;
ここで、コール ログ レコードと適用される関税を照合する必要があります。この条件は、オーバーラップを処理する標準的な方法です。最初の期間の終了が 2 番目の期間の開始よりも遅く、最初の期間の開始が 2 番目の期間の終了よりも前である場合、2 つの期間が重複します。
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
これは非常に高額な通話ですが、電話会社はそれで満足するでしょう。中間結果のいずれかを調べて、答えがどのように導き出されるかを確認できます。いくつかの明確さを犠牲にして、使用する一時テーブルを減らすことができます。
1 回の呼び出しの場合、クライアントで VB のコードを実行する場合と大差ありません。多くの呼び出しの場合、これはより効率的になる可能性があります。私は、VB で再帰が必要であるとは確信していません。単純な反復で十分です。