UFOnian
@UFOnian

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Oracle でグループ毎に条件に合致する値の有無を確認できるか?

経緯

  • GROUP BYのグループ毎に、条件に合致するレコードの存在有無を調べたい。
  • 言葉尻だけで考えればEXISTSだが、この用途における記述方法があるのか無いのか含めわからない。
  • 集計関数 COUNT には、「列を渡すとその列のNULL以外の値の個数を返す」性質があることに思い立ち、これに「条件に合致したらNULL以外、合致しなかったらNULL」を渡すことで集計できるのではと考えた。

謎:→ 集計自体は無事できたっぽいものの、COUNTの結果にしては「条件に合致する行の数」にはならない

解決: テストデータをDUAL表から作っていたが、UNION ALL じゃなく UNION で連結したせいで DISTINCTされた表をベースに集計してました。(ドアホウ)

実験

Oracleの集計関数 count は、引数に列を与えるとGroup byのグループ毎に引数に与えられた値がnull以外であるような値の個数を返す(?)

ここで下記のような、IDATRを持ってたらATR毎に行ができるようなテーブルを定義する。

ITEMS

ID ATR
1 1
1 2
1 2
2 2
3 1
4 3

これに対し、条件式でNULLNULL以外の値になるように式を組みcountに渡してやると、その属性を持っているかいないかの判定になる?

SELECT ID
     , COUNT(CASE ATR WHEN 1 THEN 1 END) AT1 -- ATRが1の行以外は NULL を返す(CASE式のELSE省略時の返り値)ので、1個も無ければ0になる(?)
     , COUNT(DECODE(ATR, 2, 1)) AT2 -- 当然、DECODEでNULLを返させてもよい
     , COUNT(DECODE(ATR, 1, 1, 2, 1)) AT1_OR_2 -- OR条件を再現
FROM ITEMS
ORDER BY ID;

結果

ID AT1 AT2 AT1_OR_2
1 1 1 2
2 0 1 1
3 1 0 1
4 0 0 0

予想してた絵

ID AT1 AT2 AT1_OR_2
1 1 2 3
2 0 1 1
3 1 0 1
4 0 0 0

※再現用のクエリをおいておく

WITH ITEMS AS (
    SELECT 1 ID, 1 ATR FROM DUAL
    UNION SELECT 1, 2 FROM DUAL
    UNION SELECT 1, 2 FROM DUAL
    UNION SELECT 2, 2 FROM DUAL
    UNION SELECT 3, 1 FROM DUAL
    UNION SELECT 4, 3 FROM DUAL
)
SELECT ID
     , COUNT(CASE ATR WHEN 1 THEN 1 END) AT1
     , COUNT(DECODE(ATR, 2, 1))          AT2
     , COUNT(DECODE(ATR, 1, 1, 2, 1))    AT1_OR_2
FROM ITEMS
GROUP BY ID
ORDER BY ID
;

疑問点

(id,atr)=(1,2)な列を2行用意しているため、DECODE(ATR, 2, 1)の結果が1になる行は2行あるはずであり、COUNT(DECODE(ATR, 2, 1)) AT2の結果が2になると予想されたが、こうならない。
また、id=1に対するCOUNT(DECODE(ATR, 1, 1, 2, 1)) AT1_OR_2の結果は2になった。
(3行あるのに!)

ATRでもグループ化されている...?(なわけないよな...)
という謎

疑問点サマリ

  • COUNT(条件式)条件式がNULL以外である行数が複数あっても1が返る理由(Oracle技術ページ?)
  • 謎が残るので、もともとの目的(グループ毎に、条件に合致するレコードの有無を抽出する)が達せられているのかも若干不安。
    この方法が適切であるのか? よりよい方法があるか?
0

3Answer

尚、サブクエリになる汚さがありますが、 GROUP BY 後に列展開しつつ集計するのでなく、列展開してからこれを集計する方法にするという単純な方法があるなー、と「where句で...」と言われて思いついたので、そっちの記述も掲載します。

SELECT ID
     , SUM(AT1)       AT1
     , SUM(AT2)       AT2
     , SUM(AT1 + AT2) AT1_OR_2
FROM (
         SELECT ID
              , DECODE(ATR, 1, 1, 0) AT1
              , DECODE(ATR, 2, 1, 0) AT2
         FROM ITEMS
     )
GROUP BY ID
ORDER BY ID

どっちのが性能いいんじゃろう。これは別枠で議論で立てようかしらん

1Like

nullとはなにか?私はこの哲学的な問いに、最近、次の様な結論に至りました。
[nullとは情報入力を忘れた悲しい存在]
その為、情報として扱われなくなった存在
但し、count(*)や IS NULL 判定は可能
where句で is not null 判定して利用するのが常套手段ではないでしょうか。

0Like

Comments

  1. @UFOnian

    Questioner

    ここではNULLデータの扱い云々はあんまり関係なくて、「グループ毎に条件に一致するレコードがあるかないか」を知りたいだけです。
    その過程でNULLは出てきましたが、ここではNULLとCOUNT関数との関連を利用している(利用できているか謎)だけで、データ自体がNULLというわけではないです。

    WHEREで対象レコードだけに絞ってから`Group BY`しろ、ということかもしれませんが、記述の通り複数同時に判定したいです。
    ここまで書いて、サブクエリにして`case when 条件 then 1 else 0 end`のようなカラムを作成して、sum関数を呼び出す、という手段に思い至りましたので、まあこっちのが妥当なのでしょうか。

    NULLデータ云々は完全に蛇足なんですけど、敢えて応えるならば、
    「実際のデータ値が不定または値に意味がない場合に、NULLを仕様してください。」とOracleさんは宣っているので、これに可能な限り従うのが正しいあり方じゃないっすかね。(既存コードがそうとは言っていない)

Your answer might help someone💌