2

通話評価システムの場合、通話時間を異なる料金期間のサブ期間に分割しようとしています。呼び出しは SQL Server データベースに保存され、開始時刻と合計期間があります。夜間(0000~0800)、ピーク(0800~1900)、オフピーク(1900~235959)で料金が異なります。

例: 通話は 18:50:00 に開始され、継続時間は 1000 秒です。これにより、コールは 19:06:40 に終了し、ピーク料金で 10 分 / 600 秒、オフピーク料金で 400 秒になります。

明らかに、通話は無制限の期間にわたってラップできます (最大通話時間は強制しません)。24 時間以上続く通話は、ピーク時からオフピーク時、夜間、ピーク料金に戻るまでの 3 つの期間すべてをラップできます。

現在、VB で再帰を使用してさまざまな関税期間を計算しています。通話が開始されるのと同じタリフ期間にどれだけの通話が行われるかを計算し、それに応じて通話の開始時刻と期間を変更し、通話の全期間に達するまでこのプロセスを繰り返します (peakDuration + offpeakDuration + nightDuration == callDuration)。

この問題に関して、2 つの質問があります。

  • これを SQL Server ステートメントで効果的に行うことは可能ですか? (ストアド プロシージャでのサブクエリや大量のコーディングを考えることができますが、それではパフォーマンスが向上しません)

  • SQL Server は、現在の VB スクリプトよりもリソース効率の高い方法でこのような計算を行うことができますか?

4

8 に答える 8

2

これは2段階の操作のように思えます。

  1. 通話のどの部分がどの時間にどの料金を使用するかを決定します。
  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 で再帰が必要であるとは確信していません。単純な反復で十分です。

于 2009-03-01T10:14:04.233 に答える
1
kar_vasile(id,vid,datein,timein,timeout,bikari,tozihat)
{
--- the bikari field is unemployment time  you can delete any where
select
            id,
            vid,
            datein,
            timein,
            timeout,
            bikari,
            hourwork =
            case when 
            timein <= timeout
            then
                SUM 
            (abs(DATEDIFF(mi, timein, timeout)) - bikari)/60 --
            calculate Hour 
        else
            SUM(abs(DATEDIFF(mi, timein, '23:59:00:00') + DATEDIFF(mi, '00:00:00', timeout) + 1) - bikari)/60 --
            calculate
            minute
                end
                ,
                minwork =
            case when 
            timein <= timeout
            then
                SUM 
            (abs(DATEDIFF(MI, timein, timeout)) - bikari)%60  --
            calclate Hour 
            starttime is later
            than endtime 
        else
            SUM(abs(DATEDIFF(mi, timein, '23:59:00:00') + DATEDIFF(mi, '00:00:00', timeout) + 1) - bikari)%60--
            calculate minute 
            starttime is later
            than
            endtime
                end, tozihat 

            from kar_vasile 
            group
            by id, vid, datein, timein, timeout, tozihat, bikari
}
于 2011-04-12T19:10:14.877 に答える
0

この種の計算をデータベース レベルで実行する場合の大きな問題は、実行中にデータベースからリソースを奪うことです。これは、CPU と、ロックによる行とテーブルの可用性の両方の観点からです。バッチ操作の一部として 1,000,000 関税を計算していた場合、それはデータベース上で長時間実行される可能性があり、その間はデータベースを他の目的に使用することはできません。

リソースがある場合は、1 つのトランザクションで必要なすべてのデータを取得し、選択した言語でデータベースの外部ですべての論理計算を実行します。次に、すべての結果を挿入します。データベースはデータを格納および取得するためのものであり、データベースが実行するビジネス ロジックは常に最小限に抑える必要があります。SQL はいくつかの点で優れていますが、日付や文字列の操作には最適な言語ではありません。

あなたの VBA の作業は既に正しい方向に進んでいると思いますが、詳細を知らなくても、再帰的、または少なくとも反復的な問題のように感じます。再帰を正しく行うと、問題に対する強力で洗練された解決策になります。データベースのリソースを占有することはほとんどありません。

于 2009-03-08T13:51:58.830 に答える
0

マイク・ウッドハウスの答えに続いて、これはあなたのために働くかもしれません:

SELECT id, SUM(DATEDIFF(ss, started, ended) * rate)
FROM rates 
JOIN calls ON 
     CASE WHEN started < from_date_time 
          THEN DATEADD(ss, 1, from_date_time) 
          ELSE started > from_date_time
   AND 
     CASE WHEN ended > to_date_time 
          THEN DATEADD(ss, -1, to_date_time) 
          ELSE ended END 
     < ended
GROUP BY id
于 2009-02-25T12:11:45.950 に答える
0

T-SQLで効果的に?現在説明されているスキーマでは、そうではないと思います。

ただし、レート テーブルに日付ごとに 3 つの料金表が格納されている場合は、可能かもしれません。目前の問題とは別に、これを行う理由が少なくとも 1 つあります。ある時点で、ある期間または別の期間のレートが変更される可能性があり、過去のレートを入手する必要がある場合があります。

これらのテーブルがあるとします。

CREATE TABLE rates (
    from_date_time DATETIME
,   to_date_time DATETIME
,   rate MONEY
)

CREATE TABLE calls (
    id INT
,   started DATETIME
,   ended DATETIME
)

考慮すべき3つのケースがあると思います(さらに多くの場合があります。これは、私が行っているときに作成しています)。

  1. コールは 1 つのレート期間内に完全に発生します
  2. コールは、あるレート期間で開始され (a)、次のレート期間で終了します (b)
  3. コールが少なくとも 1 つの完全なレート期間にまたがっている

レートが 1 秒あたりであると仮定すると、次のような (完全にテストされていない) クエリを作成できると思います

SELECT id, DATEDIFF(ss, started, ended) * rate /* case 1 */
FROM rates JOIN calls ON started > from_date_time AND ended < to_date_time
UNION
SELECT id, DATEDIFF(ss, started, to_date_time) * rate /* case 2a and the start of case 3 */
FROM rates JOIN calls ON started > from_date_time AND ended > to_date_time
UNION
SELECT id, DATEDIFF(ss, from_date_time, ended) * rate /* case 2b and the last part of case 3 */
FROM rates JOIN calls ON started < from_date_time AND ended < to_date_time
UNION
SELECT id, DATEDIFF(ss, from_date_time, to_date_time) * rate /* case 3 for entire rate periods, should pick up all complete periods */
FROM rates JOIN calls ON started < from_date_time AND ended > to_date_time

SUM..GROUP BY を SQL で適用するか、コードで処理することができます。あるいは、慎重に構築されたロジックを使用して、UNION 化された部分を多数の AND と OR を持つ単一の WHERE 句にマージすることもできます。UNION の方が意図がわかりやすいと思いました。

HTH & HIW (うまくいくといいのですが...)

于 2009-02-25T11:53:41.890 に答える
0

これは、 sqlteam.com で発生した問題に関するスレッドです。いくつかのかなり洗練されたソリューションが含まれているので、見てください。

于 2009-02-25T12:00:51.170 に答える
0

データベース内の関連するテーブルの実際のスキーマは非常に役に立ちます。私は最善の推測をします。Rates テーブルには、午前 0 時からの分数として start_time と end_time があると想定しています。

カレンダー テーブルを使用する (ほとんどのデータベースにある非常に便利なテーブル):

SELECT
     C.id,
     R.rate,
     SUM(DATEDIFF(ss,
          CASE
               WHEN C.start_time < R.rate_start_time THEN R.rate_start_time
               ELSE C.start_time
          END,
          CASE
               WHEN C.end_time > R.rate_end_time THEN R.rate_end_time
               ELSE C.end_time
          END)) AS 
FROM
     Calls C
INNER JOIN
     (
     SELECT
          DATEADD(mi, Rates.start_time, CAL.calendar_date) AS rate_start_time,
          DATEADD(mi, Rates.end_time, CAL.calendar_date) AS rate_end_time,
          Rates.rate
     FROM
          Calendar CAL
     INNER JOIN Rates ON
          1 = 1
     WHERE
          CAL.calendar_date >= DATEADD(dy, -1, C.start_time) AND
          CAL.calendar_date <= C.start_time
     ) AS R ON
          R.rate_start_time < C.end_time AND
          R.rate_end_time > C.start_time
GROUP BY
     C.id,
     R.rate

入力中にこれを思いついたので、テストされておらず、微調整が必​​要になる可能性が非常に高いですが、一般的なアイデアを確認できることを願っています.

また、呼び出しに start_time と duration を使用していることにも気付きました。期間が秒単位であると仮定すると、 C.end_time を DATEADD(ss, C.start_time, C.duration) に置き換えることができます。

これは、適切なインデックスなどを前提として、適切な RDBMS でかなり迅速に実行されるはずです。

于 2009-02-25T12:30:07.607 に答える