web-dev-qa-db-ja.com

Oracle:範囲を「グループ化」する方法

このようなテーブルがある場合:

pkey   age
----   ---
   1     8
   2     5
   3    12
   4    12
   5    22

「グループ化」して、各年齢のカウントを取得できます。

select age,count(*) n from tbl group by age;
age  n
---  -
  5  1
  8  1
 12  2
 22  1

年齢範囲でグループ化するには、どのクエリを使用できますか?

  age  n
-----  -
 1-10  2
11-20  2
20+    1

私は10gR2を使用していますが、11g固有のアプローチにも興味があります。

30
Mark Harrison
SELECT CASE 
         WHEN age <= 10 THEN '1-10' 
         WHEN age <= 20 THEN '11-20' 
         ELSE '21+' 
       END AS age, 
       COUNT(*) AS n
FROM age
GROUP BY CASE 
           WHEN age <= 10 THEN '1-10' 
           WHEN age <= 20 THEN '11-20' 
           ELSE '21+' 
         END
56
Einstein

試してください:

select to_char(floor(age/10) * 10) || '-' 
|| to_char(ceil(age/10) * 10 - 1)) as age, 
count(*) as n from tbl group by floor(age/10);
25

探しているのは、基本的に histogram のデータです。

X軸に年齢(または年齢範囲)があり、y軸にカウントn(または頻度)があります。

最も簡単な形式では、すでに説明したように、各個別の年齢値の数を単純にカウントできます。

SELECT age, count(*)
FROM tbl
GROUP BY age

ただし、x軸の値が多すぎる場合は、グループ(またはクラスターやバケット)を作成する必要があります。あなたの場合、10の一定範囲でグループ化します。

各範囲にWHEN ... THEN行を書くことを避けることができます-年齢に関するものではない場合は数百になります。代わりに、@ NitinMidhaが述べている理由から、@ MatthewFlaschenによるアプローチが望ましいです。

では、SQLをビルドしましょう...

まず、次のように年齢を10の範囲グループに分割する必要があります。

  • 0-9
  • 10-19
  • 20〜29
  • 等.

これは、年齢列を10で除算し、結果のFLOORを計算することで実現できます。

FLOOR(age/10)

"FLOORはn以下の最大整数を返します" http://docs.Oracle.com/cd/E11882_01/server.112/e26088/functions067.htm#SQLRF0064

次に、元のSQLを使用して、ageをその式に置き換えます。

SELECT FLOOR(age/10), count(*)
FROM tbl
GROUP BY FLOOR(age/10)

これは問題ありませんが、範囲はまだ見えません。代わりに、0, 1, 2 ... nである計算されたフロア値のみが表示されます。

実際の下限を取得するには、再度10を掛けて0, 10, 20 ... nを取得する必要があります。

FLOOR(age/10) * 10

下限bound + 10-1または

FLOOR(age/10) * 10 + 10 - 1

最後に、両方を次のような文字列に連結します。

TO_CHAR(FLOOR(age/10) * 10) || '-' || TO_CHAR(FLOOR(age/10) * 10 + 10 - 1)

これにより、'0-9', '10-19', '20-29'などが作成されます。

これで、SQLは次のようになります。

SELECT 
TO_CHAR(FLOOR(age/10) * 10) || ' - ' || TO_CHAR(FLOOR(age/10) * 10 + 10 - 1),
COUNT(*)
FROM tbl
GROUP BY FLOOR(age/10)

最後に、順序とNice列のエイリアスを適用します。

SELECT 
TO_CHAR(FLOOR(age/10) * 10) || ' - ' || TO_CHAR(FLOOR(age/10) * 10 + 10 - 1) AS range,
COUNT(*) AS frequency
FROM tbl
GROUP BY FLOOR(age/10)
ORDER BY FLOOR(age/10)

ただし、より複雑なシナリオでは、これらの範囲はサイズ10の一定のチャンクにグループ化されない場合がありますが、動的なクラスタリングが必要です。 Oracleには、より高度なヒストグラム関数が含まれています。 http://docs.Oracle.com/cd/E16655_01/server.121/e15858/tgsql_histo.htm#TGSQL366 を参照してください。

彼のアプローチに対する@MatthewFlaschenの功績。詳細のみを説明しました。

10
Wintermute

サブクエリで「範囲」テーブルを作成し、これを使用してメインテーブルからデータをパーティション分割するソリューションを次に示します。

SELECT DISTINCT descr
  , COUNT(*) OVER (PARTITION BY descr) n
FROM age_table INNER JOIN (
  select '1-10' descr, 1 rng_start, 10 rng_stop from dual
  union (
  select '11-20', 11, 20 from dual
  ) union (
  select '20+', 21, null from dual
)) ON age BETWEEN nvl(rng_start, age) AND nvl(rng_stop, age)
ORDER BY descr;
3
Dan

1時間に表示されるトランザクション数でデータをグループ化する必要がありました。タイムスタンプから時間を抽出することでこれを行いました:

select extract(hour from transaction_time) as hour
      ,count(*)
from   table
where  transaction_date='01-jan-2000'
group by
       extract(hour from transaction_time)
order by
       extract(hour from transaction_time) asc
;

出力を与える:

HOUR COUNT(*)
---- --------
   1     9199 
   2     9167 
   3     9997 
   4     7218

ご覧のとおり、これにより、1時間あたりのレコード数を簡単にグループ化できます。

2
Clarkey

age_rangeテーブルとage_range_idフィールドをテーブルに追加し、代わりにそれでグループ化します。

// DDLを言い訳しますが、アイデアを得る必要があります

create table age_range(
age_range_id tinyint unsigned not null primary key,
name varchar(255) not null);

insert into age_range values 
(1, '18-24'),(2, '25-34'),(3, '35-44'),(4, '45-54'),(5, '55-64');

//もう一度DMLを言い訳しますが、アイデアを得る必要があります

select
 count(*) as counter, p.age_range_id, ar.name
from
  person p
inner join age_range ar on p.age_range_id = ar.age_range_id
group by
  p.age_range_id, ar.name order by counter desc;

必要に応じて、このアイデアを改良できます-age_rangeテーブルにfrom_age to_ageカラムを追加します-しかし、それはあなたにお任せします。

お役に立てれば :)

1
Jon Black

日ごとにサンプル数を取得する必要がありました。 @Clarkeyに触発され、TO_CHARを使用してタイムスタンプからISO-8601日付形式にサンプルの日付を抽出し、GROUP BY句とORDER BY句で使用しました。 (さらにインスピレーションを得て、他の人に役立つようにここに投稿します。)

SELECT 
  TO_CHAR(X.TS_TIMESTAMP, 'YYYY-MM-DD') AS TS_DAY, 
  COUNT(*) 
FROM   
  TABLE X
GROUP BY
  TO_CHAR(X.TS_TIMESTAMP, 'YYYY-MM-DD')
ORDER BY
  TO_CHAR(X.TS_TIMESTAMP, 'YYYY-MM-DD') ASC
/
1
Kieron Hardy

Oracle 9i +を使用している場合、mightNTILE分析関数 を使用できます。

_WITH tiles AS (
  SELECT t.age,
         NTILE(3) OVER (ORDER BY t.age) AS tile
    FROM TABLE t)
  SELECT MIN(t.age) AS min_age,
         MAX(t.age) AS max_age,
         COUNT(t.tile) As n
    FROM tiles t
GROUP BY t.tile
_

NTILEの注意点は、パーティションの数のみを指定でき、ブレークポイント自体は指定できないことです。したがって、適切な数値を指定する必要があります。 IE:100行の場合、NTILE(4)は4つのバケット/パーティションのそれぞれに25行を割り当てます。分析関数をネストすることはできないため、サブクエリ/サブクエリファクタリングを使用してそれらを階層化し、必要な粒度を取得する必要があります。それ以外の場合は、次を使用します。

_  SELECT CASE t.age
           WHEN BETWEEN 1 AND 10 THEN '1-10' 
           WHEN BETWEEN 11 AND 20 THEN '11-20' 
           ELSE '21+' 
         END AS age, 
         COUNT(*) AS n
    FROM TABLE t
GROUP BY CASE t.age
           WHEN BETWEEN 1 AND 10 THEN '1-10' 
           WHEN BETWEEN 11 AND 20 THEN '11-20' 
           ELSE '21+' 
         END
_
1
OMG Ponies

私のアプローチ:

select range, count(1) from (
select case 
  when age < 5 then '0-4' 
  when age < 10 then '5-9' 
  when age < 15 then '10-14' 
  when age < 20 then '15-20' 
  when age < 30 then '21-30' 
  when age < 40 then '31-40' 
  when age < 50 then '41-50' 
  else                '51+' 
end 
as range from
(select round(extract(day from feedback_update_time - feedback_time), 1) as age
from txn_history
) ) group by range  
  • 範囲を柔軟に定義できます
  • Select句とgroup句の範囲を繰り返さない
  • しかし、誰かが私に教えてください、大きさでそれらを注文する方法!
0
Ananth N

以下の解決策を試すことができます:

SELECT count (1), '1-10'  where age between 1 and 10
union all 
SELECT count (1), '11-20'  where age between 11 and 20
union all
select count (1), '21+' where age >20
from age 
0
sudarshan vp