『SQLパズル 第2版』の別解など その4

『SQLパズル 第2版』の別解など その3 - 不思議の備忘録

の続き

パズル62

SQLメモ - 不思議の備忘録の「二人組作って~」のN列に一般化 ――3列以上に拡張したい場合もWindow関数ならスッキリ

--4列の場合
SELECT MAX(CASE col WHEN 1 THEN name END) AS name1
      ,MAX(CASE col WHEN 2 THEN name END) AS name2
      ,MAX(CASE col WHEN 3 THEN name END) AS name3
      ,MAX(CASE col WHEN 4 THEN name END) AS name4
  FROM (SELECT name
              ,(ROW_NUMBER() OVER(ORDER BY name) - 1) / 4 + 1 AS row
              ,(ROW_NUMBER() OVER(ORDER BY name) - 1) % 4 + 1 AS col
          FROM Names) AS TMP
 GROUP BY row
 ORDER BY name1, name2, name3, name4;

パズル63

■Window関数を用いた別解

SELECT MIN(num) AS low, MAX(num) AS high, MAX(data) AS data
  FROM (SELECT num
              ,data
              ,SUM(changed) OVER(ORDER BY num) AS group_n
          FROM (SELECT num
                      ,data
                      ,CASE WHEN MAX(data) OVER(ORDER BY num ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) = data THEN 0 
                            ELSE 1 END AS changed
                  FROM T
               ) AS TMP
       ) AS TMP2
 GROUP BY group_n
 ORDER BY group_n;

GROUP BY句でWindow関数が使えればネストが1段減るんだけどなあ… Window関数の中に同じか一部を含むフレーム定義のWindow関数を入れられるようになったらいいなあ…

パズル65

ミック氏のサポートページhttp://mickindex.sakura.ne.jp/database/db_support_sqlpuzzle.htmlの演習問題のSQL

SELECT product_id
  FROM PriceByAge
 WHERE NOT (low_age > 55 OR high_age < 10)
 GROUP BY product_id
HAVING SUM(CASE WHEN high_age < 55 THEN high_age ELSE 55 END - CASE WHEN low_age > 10 THEN low_age ELSE 10 END + 1) = 55 - 10 + 1;

パズル69

■Window関数を使った答え2の別解(LIFOのほう)

SELECT SUM(picked_qty * unit_price) AS total_cost
  FROM (SELECT CASE WHEN SUM(qty_on_hand) OVER(ORDER BY purchase_date DESC) <= 100
                    THEN qty_on_hand
                    WHEN SUM(qty_on_hand) OVER(ORDER BY purchase_date DESC) - qty_on_hand <= 100
                    THEN SUM(qty_on_hand) OVER(ORDER BY purchase_date DESC) - 100
                    ELSE 0
                    END AS picked_qty
              ,unit_price
          FROM WidgetInventory) AS TMP
 WHERE picked_qty > 0;

(演習問題)要求された個数以下で最も近い部品数の組み合わせ

WHERE total_pick = (SELECT MAX(total_pick)
                      FROM PickCombos
                     WHERE total_pick <= goal_qty)

パズル71

答え2や3をみるにSELECT句でSUMを使ってるあたりpin_nbrは重複を認めているんじゃないかなと思いました。 つまり主キーが一切ないテーブルになります

CREATE TABLE Foobaz
(empl_id CHAR(6) NOT NULL, pin_nbr CHAR(5) NOT NULL,
 empl_rcd INTEGER NOT NULL, calc_rslt_val INTEGER NOT NULL,
 calc_adj_val INTEGER NOT NULL, unit_rslt_val INTEGER NOT NULL,
 unit_adj_val INTEGER NOT NULL);

INSERT INTO Foobaz VALUES('xxxxxx', '52636', 0, 10, 20, 30, 40);
INSERT INTO Foobaz VALUES('xxxxxx', '52751', 0,  5,  6,  7,  8);
INSERT INTO Foobaz VALUES('xxxxxx', '52768', 0, 20, 40, 60, 80);
INSERT INTO Foobaz VALUES('xxxxxx', '52636', 0, 10, 20, 30, 40);
INSERT INTO Foobaz VALUES('xxxxxx', '52751', 0,  5,  6,  7,  8);
INSERT INTO Foobaz VALUES('xxxxxx', '52768', 0, 20, 40, 60, 80);

この場合答えその2とその3は違うクエリ結果になります(列持ち行もちの違いではなく)

SELECT DISTINCT
         SUM(F1.calc_rslt_val + F1.calc_adj_val) AS calc_1,
         SUM(F1.unit_rslt_val + F1.unit_adj_val) AS unit_1,
         SUM(f2.calc_rslt_val + f2.calc_adj_val) AS calc_2,
         SUM(f2.unit_rslt_val + f2.unit_adj_val) AS unit_2,
         SUM(f3.calc_rslt_val + f3.calc_adj_val) AS calc_3,
         SUM(f3.unit_rslt_val + f3.unit_adj_val) AS unit_3
  FROM Foobaz F1, Foobaz f2, Foobaz f3
 WHERE F1.empl_id = 'xxxxxx'
   AND f2.empl_id = 'xxxxxx'
   AND f3.empl_id = 'xxxxxx'
   AND F1.empl_rcd = 0
   AND F1.pin_nbr = '52636'
   AND f2.pin_nbr = '52751'
   AND f3.pin_nbr = '52768';
SELECT F1.pin_nbr,
       SUM(F1.calc_rslt_val + F1.calc_adj_val) AS calc_val,
       SUM(F1.unit_rslt_val + F1.unit_adj_val) AS unit_val
  FROM Foobaz F1
 WHERE F1.empl_id = 'xxxxxx'
   AND F1.empl_rcd = 0
   AND F1.pin_nbr IN ('52636', '52751', '52768')
 GROUP BY F1.pin_nbr;

結合条件で'52636'は2つあるのでAA,AB,BA,BBの重複が発生します

パズル73

演習問題:ORでつながずに「少なくとも1列は9を含む」行を選択するコード

SELECT *
  FROM Staging
 WHERE 1 = ANY(ARRAY[col1, col2, col3, col4, col5, col6, col7, col8, col9, col10]);

SELECT *
  FROM Staging
 WHERE NOT 1 <> ALL(ARRAY[col1, col2, col3, col4, col5, col6, col7, col8, col9, col10]);

パズル74

ネタ別解。可読性もパフォーマンスも悪い。最後の項目がこんなんでいいんですかね・・・?

多対多の結合の場合相手方の結合対象の数だけ重複行が発生するため、SUMするときに重複して計上してしまう。 なので、最終的に重複数で割ることで無理やり帳尻を合わせます。CASE WHEN してるのは0除算を防ぐため。 こうすれば中間テーブルは作られませんね(効率がいいとは言っていない)

SELECT A.acct_nbr,
       COALESCE(SUM(F.foo_qty), 0) / CASE WHEN EXISTS (SELECT * FROM Bar WHERE Bar.acct_nbr = A.acct_nbr) THEN (SELECT COUNT(*) FROM Bar WHERE Bar.acct_nbr = A.acct_nbr) ELSE 1 END AS foo_qty_tot,
       COALESCE(SUM(B.bar_qty), 0) / CASE WHEN EXISTS (SELECT * FROM Foo WHERE Foo.acct_nbr = A.acct_nbr) THEN (SELECT COUNT(*) FROM Foo WHERE Foo.acct_nbr = A.acct_nbr) ELSE 1 END AS bar_qty_tot
  FROM Accounts AS A
       LEFT OUTER JOIN 
       Foo AS F
       ON A.acct_nbr = F.acct_nbr
       LEFT OUTER JOIN
       Bar AS B
       ON A.acct_nbr = B.acct_nbr
 GROUP BY A.acct_nbr
 ORDER BY A.acct_nbr;