5

ご覧のとおり、これには時間がかかります。代替案はありますか?group by 句で列エイリアスを使用しようとしましたが、役に立ちませんでした。

select count(callid) ,
case
        when callDuration > 0 and callDuration < 30 then 1
        when callDuration >= 30 and callDuration < 60 then 2
        when callDuration >= 60 and callDuration < 120 then 3
        when callDuration >= 120 and callDuration < 180 then 4
        when callDuration >= 180 and callDuration < 240 then 5
        when callDuration >= 240 and callDuration < 300 then 6
        when callDuration >= 300 and callDuration < 360 then 7
        when callDuration >= 360 and callDuration < 420 then 8
        when callDuration >= 420 and callDuration < 480 then 9
        when callDuration >= 480 and callDuration < 540 then 10
        when callDuration >= 540 and callDuration < 600 then 11
        when callDuration >= 600 then 12
end as duration
from callmetatbl
where programid = 1001 and callDuration > 0
group by case
        when callDuration > 0 and callDuration < 30 then 1
        when callDuration >= 30 and callDuration < 60 then 2
        when callDuration >= 60 and callDuration < 120 then 3
        when callDuration >= 120 and callDuration < 180 then 4
        when callDuration >= 180 and callDuration < 240 then 5
        when callDuration >= 240 and callDuration < 300 then 6
        when callDuration >= 300 and callDuration < 360 then 7
        when callDuration >= 360 and callDuration < 420 then 8
        when callDuration >= 420 and callDuration < 480 then 9
        when callDuration >= 480 and callDuration < 540 then 10
        when callDuration >= 540 and callDuration < 600 then 11
        when callDuration >= 600 then 12
end

編集:私は本当に単一のケースソースを持つ方法を尋ねるつもりでしたが、とにかくケースの変更は大歓迎です(ただし、間隔はおそらく変更され、自動的に生成される可能性があるためあまり役に立ちません)。

一部の人々が考えているように、 callDuration は実際にはフロートであるため、リストされているソリューションの一部は、間隔から値を除外することにより、私のユース ケースには有効ではありません。

教訓:

  • ケース式のパターンを探して、可能で価値がある場合はそれを減らします

     case
        when callDuration > 0 AND callDuration < 30 then 1
        when callDuration > 600 then 12
        else floor(callDuration/60) + 2  end
     end as duration
    
  • インライン ビューを使用してケースのソースを 1 つにする

    select count(d.callid), d.duration
    from (   
       select callid
            , case
               when callDuration > 0 AND callDuration < 30 then 1
               when callDuration > 600 then 12
               else floor(callDuration/60) + 2  end
              end as duration
        from callmetatbl
        where programid = 1001
              and callDuration > 0
    ) d
    group by d.duration
    
  • または、一般的なテーブル式を使用します

       with duration_case as (
          select callid ,
          case
            when callDuration > 0 AND callDuration < 30 then 1
            when callDuration > 600 then 12
            else floor(callDuration/60) + 2  end
          end as duration
       from callmetatbl
       where programid = 1001 and callDuration > 0 )
        select count(callid), duration
        from duration_case
        group by duration
    
  • または、ユーザー定義関数を使用します(これまでの例はありません:-))

  • または、ルックアップ テーブルと結合を使用します。

    DECLARE @t TABLE(durationFrom float, durationTo float, result INT)
    --populate table with values so the query works
    select count(callid) , COALESCE(t.result, 12)
    from callmetatbl JOIN @t AS t ON callDuration >= t.durationFrom 
    AND callDuration < t.durationTo 
    where programid = 1001 and callDuration > 0
    

多くの人が質問のさまざまな部分をカバーしているため、受け入れられた回答を選択するのに非常に苦労しています(そして、私はそれが単純な回答の単純な質問だと思っていました:-)、混乱して申し訳ありません)。

4

11 に答える 11

9

使っていない理由はありますbetweenか?case ステートメント自体はそれほど悪くはありません。本当に嫌なら、これらすべてをテーブルに投げ込んでマッピングできます。

Durations
------------------
low   high   value
0     30     1
31    60     2

等...

(SELECT value FROM Durations WHERE callDuration BETWEEN low AND high) as Duration

編集:または、フロートが使用されていてbetween面倒になる場合。

(SELECT value FROM Durations WHERE callDuration >= low AND callDuration <= high) as Duration
于 2009-06-04T17:05:30.387 に答える
9

Q: GROUP BY 句で使用するエイリアスを取得する方法

1 つの方法は、インライン ビューを使用することです。[編集] Remus Rusanu (+1!) からの回答は、同じことを達成するための Common Table Expression の例を示しています。[/編集]

インライン ビューは、複雑な式の単純な「エイリアス」を取得します。これを、外側のクエリの GROUP BY 句で参照できます。

select count(d.callid)
     , d.duration
  from (select callid
             , case
               when callDuration >= 600 then 12
               when callDuration >= 540 then 11
               when callDuration >= 480 then 10
               when callDuration >= 420 then 9
               when callDuration >= 360 then 8
               when callDuration >= 300 then 7
               when callDuration >= 240 then 6
               when callDuration >= 180 then 5
               when callDuration >= 120 then 4
               when callDuration >=  60 then 3
               when callDuration >=  30 then 2
               when callDuration >    0 then 1
               --else null
               end as duration
             from callmetatbl
            where programid = 1001
              and callDuration > 0
       ) d
group by d.duration

それを開梱しましょう。

  • 内側の (インデントされた) クエリが呼び出され、インライン ビューが表示されます(エイリアスを指定しますd) 。
  • 外側のクエリでは、エイリアスを参照できdurationますd

それはあなたの質問に答えるのに十分なはずです。同等の置換式を探している場合は、tekBluesのもの( +1 ! ) が正しい答えです (境界および非整数に対して機能します)。

tekBlues の置換式 (+1!) を使用:

select count(d.callid)
     , d.duration
  from (select callid
             , case 
               when callduration >=30 and callduration<600
                    then floor(callduration/60)+2
               when callduration>0 and callduration< 30
                    then 1 
               when callduration>=600
                    then 12
               end as duration
          from callmetatbl
         where programid = 1001
           and callDuration > 0
       ) d
 group by d.duration

(これはあなたの質問に答えるのに十分なはずです。)


[UPDATE:]ユーザー定義関数のサンプル(インライン CASE 式の代替)

CREATE FUNCTION [dev].[udf_duration](@cd FLOAT)
RETURNS SMALLINT
AS
BEGIN
  DECLARE @bucket SMALLINT
  SET @bucket = 
  CASE
  WHEN @cd >= 600 THEN 12
  WHEN @cd >= 540 THEN 11
  WHEN @cd >= 480 THEN 10
  WHEN @cd >= 420 THEN 9
  WHEN @cd >= 360 THEN 8
  WHEN @cd >= 300 THEN 7
  WHEN @cd >= 240 THEN 6
  WHEN @cd >= 180 THEN 5
  WHEN @cd >= 120 THEN 4
  WHEN @cd >=  60 THEN 3
  WHEN @cd >=  30 THEN 2
  WHEN @cd >    0 THEN 1
  --ELSE NULL
  END
  RETURN @bucket
END

select count(callid)
     , [dev].[udf_duration](callDuration)
  from callmetatbl
 where programid = 1001
   and callDuration > 0
 group by [dev].[udf_duration](callDuration)

注:ユーザー定義関数はオーバーヘッドを追加し、(もちろん) 別のデータベース オブジェクトへの依存関係を追加することに注意してください。

この例の関数は、元の式と同等です。OP CASE 式にはギャップはありませんが、各「ブレークポイント」を 2 回参照します。私は下限のみをテストすることを好みます。(条件が満たされると CASE が返されます。逆にテストを実行すると、未処理のケース (<=0 または NULL) がテストなしで失敗します。anELSE NULLは必要ありませんが、完全を期すために追加できます。

追加の詳細

(パフォーマンスとオプティマイザの計画を確認して、元の計画と同じ (または大幅に悪化していない) ことを確認してください。以前、述語をインライン ビューにプッシュする際に問題が発生しました。あなたの場合は問題になるように。)

保存されたビュー

インラインビューは、ビュー定義としてデータベースに格納することもできます。しかし、ステートメントから複雑な式を「隠す」以外に、そうする理由はありません。

複雑な式を単純化する

複雑な式を「より単純」にするもう 1 つの方法は、ユーザー定義関数を使用することです。しかし、ユーザー定義関数には独自の問題があります (パフォーマンスの低下を含む)。

データベースの「ルックアップ」テーブルを追加

データベースに「ルックアップ」テーブルを追加することを推奨する回答もあります。これが本当に必要だとは思いません。これはもちろん実行できます。また、クエリを変更したり、DDL ステートメントを実行したりせずに、その場で from のさまざまな値を導出できるようにしたい場合 (たとえば、ビュー定義を変更するため) に意味ありdurationます。またはユーザー定義関数を変更します)。 callDuration

「ルックアップ」テーブルへの結合の利点の 1 つは、「ルックアップ」テーブルで DML 操作を実行するだけで、クエリが異なる結果セットを返すようにできることです。

しかし、同じ利点が実際には欠点でもある可能性があります。

メリットが実際にデメリットを上回るかどうかを慎重に検討してください。新しいテーブルが単体テストに与える影響、ルックアップ テーブルの内容が有効で変更されていないことを確認する方法 (オーバーラップやギャップ)、コードの継続的なメンテナンスへの影響 (複雑さが増すため) を考慮してください。

いくつかの大きな仮定

ここで与えられた答えの多くは、それcallDurationが INTEGER データ型であると仮定しているようです。彼らはそれが整数ではない可能性を見落としているようですが、質問でそのナゲットを逃したのかもしれません。

それを実証するのは、かなり単純なテスト ケースです。

callDuration BETWEEN 0 AND 30

と同等ではありません

callDuration > 0 AND callDuration < 30
于 2009-06-04T17:06:00.260 に答える
5

ケースは次のように記述できます。

case 
when callduration >=30 and callduration<600 then floor(callduration/60)+2
when callduration>0 and callduration< 30 then 1 
when callduration>=600 then 12
end

持つ必要はありません。「where callduration>0」に置き換えてください。

前に与えられた変換表の回答が気に入っています! それが最善の解決策です

于 2009-06-04T17:10:28.757 に答える
4

CASEをクエリツリーのさらに下にプッシュして、その投影がGROUPBYに表示されるようにする必要があります。これは、次の2つの方法で実現できます。

  1. 派生テーブルを使用します(すでにSpencer、Adam、およびJeremyがその方法を示しています)
  2. 一般的なテーブル式を使用する

    with duration_case as (
    select callid ,
    case
        when callDuration > 0 and callDuration < 30 then 1
        when callDuration >= 30 and callDuration < 60 then 2
        when callDuration >= 60 and callDuration < 120 then 3
        when callDuration >= 120 and callDuration < 180 then 4
        when callDuration >= 180 and callDuration < 240 then 5
        when callDuration >= 240 and callDuration < 300 then 6
        when callDuration >= 300 and callDuration < 360 then 7
        when callDuration >= 360 and callDuration < 420 then 8
        when callDuration >= 420 and callDuration < 480 then 9
        when callDuration >= 480 and callDuration < 540 then 10
        when callDuration >= 540 and callDuration < 600 then 11
        when callDuration >= 600 then 12
    end as duration
    from callmetatbl
    where programid = 1001 and callDuration > 0 )
       select count(callid), duration
       from duration_case
       group by duration
    

どちらのソリューションも、あらゆる点で同等です。CTEの方が読みやすく、移植性の高い派生テーブルを好む人もいます。

于 2009-06-04T17:24:55.277 に答える
2

callDuration60 で割ります。

case
        when callDuration between 1 AND 29 then 1
        when callDuration > 600 then 12
        else (callDuration /60) + 2  end
end as duration

境界が含まれていることに注意してくださいbetween。 callDuration は整数として扱われると想定しています。


更新:
これを他のいくつかの回答と組み合わせると、クエリ全体を次のようにまとめることができます。

select count(d.callid), d.duration
from (   
       select callid
            , case
                when callDuration between 1 AND 29 then 1
                when callDuration > 600 then 12
                else (callDuration /60) + 2  end
              end as duration
        from callmetatbl
        where programid = 1001
              and callDuration > 0
    ) d
group by d.duration
于 2009-06-04T17:05:46.080 に答える
1
select count(callid), duration from
(
    select callid ,
    case
            when callDuration > 0 and callDuration < 30 then 1
            when callDuration >= 30 and callDuration < 60 then 2
            when callDuration >= 60 and callDuration < 120 then 3
            when callDuration >= 120 and callDuration < 180 then 4
            when callDuration >= 180 and callDuration < 240 then 5
            when callDuration >= 240 and callDuration < 300 then 6
            when callDuration >= 300 and callDuration < 360 then 7
            when callDuration >= 360 and callDuration < 420 then 8
            when callDuration >= 420 and callDuration < 480 then 9
            when callDuration >= 480 and callDuration < 540 then 10
            when callDuration >= 540 and callDuration < 600 then 11
            when callDuration >= 600 then 12
    end as duration
    from callmetatbl
    where programid = 1001 and callDuration > 0
) source
group by duration
于 2009-06-04T17:11:44.137 に答える
1

未テスト:

select  count(callid) , duracion
from
    (select 
        callid,
        case        
            when callDuration > 0 and callDuration < 30 then 1        
            when callDuration >= 30 and callDuration < 60 then 2        
            when callDuration >= 60 and callDuration < 120 then 3        
            when callDuration >= 120 and callDuration < 180 then 4        
            when callDuration >= 180 and callDuration < 240 then 5        
            when callDuration >= 240 and callDuration < 300 then 6        
            when callDuration >= 300 and callDuration < 360 then 7        
            when callDuration >= 360 and callDuration < 420 then 8        
            when callDuration >= 420 and callDuration < 480 then 9        
            when callDuration >= 480 and callDuration < 540 then 10        
            when callDuration >= 540 and callDuration < 600 then 11        
            when callDuration >= 600 then 12        
            else 0
        end as duracion
    from callmetatbl
    where programid = 1001) GRP
where duracion > 0
group by duracion
于 2009-06-04T17:15:18.690 に答える
1

ここでのユーザー定義関数の何が問題なのですか? コードを視覚的にクリーンアップし、そのように機能を集中化することができます。パフォーマンスに関しては、上記の UDF 内で何か非常に遅いことをしていない限り、ヒットがあまりにもひどいことはわかりません。

于 2009-06-04T20:10:42.350 に答える
1

これが私のショットです。必要なすべてのコンポーネントは、ストレート SQL で実行できます。

select
  count(1) as total
 ,(fixedDuration / divisor) + adder as duration
from
(
    select
      case/*(30s_increments_else_60s)*/when(callDuration<60)then(120)else(60)end as divisor
     ,case/*(increment_by_1_else_2)*/when(callDuration<30)then(1)else(2)end as adder
     ,(/*duration_capped@600*/callDuration+600-ABS(callDuration-600))/2 as fixedDuration
     ,callDuration
    from 
      callmetatbl
    where
      programid = 1001
    and 
      callDuration > 0
) as foo
group by
  (fixedDuration / divisor) + adder

テストに使用したSQLは次のとおりです。(私は自分自身の callmetatbl を持っていません;)

select
  count(1) as total
 ,(fixedDuration / divisor) + adder as duration
from
(
    select
      case/*(30s_increments_else_60s)*/when(callDuration<60)then(120)else(60)end as divisor
     ,case/*(increment_by_1_else_2)*/when(callDuration<30)then(1)else(2)end as adder
     ,(/*duration_capped@600*/callDuration+600-ABS(callDuration-600))/2 as fixedDuration
     ,callDuration
    from -- callmetatbl -- using test view below
      (  
       select 1001 as programid,   0 as callDuration union
       select 1001 as programid,   1 as callDuration union
       select 1001 as programid,  29 as callDuration union
       select 1001 as programid,  30 as callDuration union
       select 1001 as programid,  59 as callDuration union
       select 1001 as programid,  60 as callDuration union
       select 1001 as programid, 119 as callDuration union
       select 1001 as programid, 120 as callDuration union
       select 1001 as programid, 179 as callDuration union
       select 1001 as programid, 180 as callDuration union
       select 1001 as programid, 239 as callDuration union
       select 1001 as programid, 240 as callDuration union
       select 1001 as programid, 299 as callDuration union
       select 1001 as programid, 300 as callDuration union
       select 1001 as programid, 359 as callDuration union
       select 1001 as programid, 360 as callDuration union
       select 1001 as programid, 419 as callDuration union
       select 1001 as programid, 420 as callDuration union
       select 1001 as programid, 479 as callDuration union
       select 1001 as programid, 480 as callDuration union
       select 1001 as programid, 539 as callDuration union
       select 1001 as programid, 540 as callDuration union
       select 1001 as programid, 599 as callDuration union
       select 1001 as programid, 600 as callDuration union
       select 1001 as programid,1000 as callDuration
      ) as callmetatbl
    where
      programid = 1001
    and 
      callDuration > 0
) as foo
group by
  (fixedDuration / divisor) + adder

SQL 出力を以下に示します。期間 (バケット) 1 から 12 ごとに 2 つのレコードがカウントされます。

total  duration
2             1
2             2
2             3
2             4
2             5
2             6
2             7
2             8
2             9
2            10
2            11
2            12

「foo」サブクエリの結果は次のとおりです。

divisor adder   fixedDuration  callDuration
120         1               1             1
120         1              29            29
120         2              30            30
120         2              59            59
60          2              60            60
60          2             119           119
60          2             120           120
60          2             179           179
60          2             180           180
60          2             239           239
60          2             240           240
60          2             299           299
60          2             300           300
60          2             359           359
60          2             360           360
60          2             419           419
60          2             420           420
60          2             479           479
60          2             480           480
60          2             539           539
60          2             540           540
60          2             599           599
60          2             600           600
60          2             600          1000

乾杯。

于 2009-06-04T19:36:34.870 に答える
1

ルックアップ テーブルを作成する ルックアップ テーブルを使用すると、ステートメントもduration
高速化されます。SELECT

これは、ルックアップ テーブルでどのように見えるかの最終結果です。

select  count(a.callid), b.ID as duration
from    callmetatbl a
        inner join DurationMap b 
         on a.callDuration >= b.Minimum
        and a.callDuration < IsNUll(b.Maximum, a.CallDuration + 1)
group by  b.ID

こちらがルックアップテーブルです。

create table DurationMap (
    ID          int identity(1,1) primary key,
    Minimum     int not null,
    Maximum     int 
)

insert  DurationMap(Minimum, Maximum) select 0,30
insert  DurationMap(Minimum, Maximum) select 30,60
insert  DurationMap(Minimum, Maximum) select 60,120
insert  DurationMap(Minimum, Maximum) select 120,180
insert  DurationMap(Minimum, Maximum) select 180,240
insert  DurationMap(Minimum, Maximum) select 240,300
insert  DurationMap(Minimum, Maximum) select 300,360
insert  DurationMap(Minimum, Maximum) select 360,420
insert  DurationMap(Minimum, Maximum) select 420,480
insert  DurationMap(Minimum, Maximum) select 480,540
insert  DurationMap(Minimum, Maximum) select 540,600
insert  DurationMap(Minimum) select 600
于 2009-06-05T16:11:40.933 に答える
1

すべてのケースをテーブル変数に追加し、外部結合を実行します

DECLARE @t TABLE(durationFrom INT, durationTo INT, result INT)
--        when callDuration > 0 and callDuration < 30 then 1
INSERT INTO @t VALUES(1, 30, 1);
--        when callDuration >= 30 and callDuration < 60 then 2
INSERT INTO @t VALUES(30, 60, 2);

select count(callid) , COALESCE(t.result, 12)
from callmetatbl JOIN @t AS t ON callDuration >= t.durationFrom AND callDuration  < t.durationTo 
where programid = 1001 and callDuration > 0
于 2009-06-04T17:31:04.847 に答える