2

実稼働の MySQL DB を Postgres に変換する必要性を受け継いでいます。これは、テーブル/関数の作成に単純な SQL ステートメントを使用して (Navicat を使用して半自動変換を生成する) 問題なく処理されましたが、やや複雑なビューの変換で問題が発生しています。

調査によると、これは 2 つの DB がサブクエリ (WHERE ステートメント) を処理する方法の違いが原因である可能性があり、おそらくそれは単なる構文の違いです。コードベースは別の開発者から継承されているため、ここでのビジネス ロジックは不明です。

以下を実行します (Laravel 移行 / PHP スクリプトを使用):

SELECT 
parent.is_owner AS is_owner,
parent.brand AS first_name,
parent.id AS id,
(SELECT count(c.id)
 FROM campaigns c
 WHERE((
       (c.user_id = parent.id)
       OR
       (c.user_id = child.id)
       )
       AND
       (c.campaign_status_id = 4)
))
AS current_campaigns,
(SELECT count(c.id)
    FROM campaigns c
    WHERE
        ((
        (c.user_id = parent.id)
        OR (c.user_id = child.id)
        )
        AND (c.campaign_status_id = 5)
))
AS past_campaigns,
(SELECT count(c.id)
    FROM campaigns c
    WHERE
        ((
         (c.user_id = parent.id)
         OR (c.user_id = child.id))
         AND (c.campaign_status_id = 2)
        ))
    AS pending_campaigns,
(SELECT count(c.id)
    FROM    campaigns c
    WHERE ((
            (c.user_id = parent.id)
            OR (c.user_id = child.id)
            )
            AND (c.invoice_status = '1')
        ))
    AS past_invoices
FROM ((users parent LEFT JOIN campaigns mc ON
     ((parent.id = mc.user_id)))
    LEFT JOIN users child ON ((child.parent_owner = parent.id)
    ))
WHERE
(
    (parent.is_owner = 1)
    OR (child.is_retailer = 1)
)
GROUP BY parent.id
ORDER BY parent.brand

...エラーをトリガーします

SQLSTATE[42803]: Grouping error: 7 ERROR:  subquery uses ungrouped column "child.id" from outer query
  LINE 1: ...c where (((c.user_id = parent.id) or (c.user_id = child.id)) ...

Postgresがサブクエリを実行するように、これをフォーマットする方法を誰かが提案できますか?

ところで、ここで Laravel 移行スクリプトで使用される PHP コードは次のとおりです。

...

DB::unprepared("CREATE VIEW client AS
select parent.is_owner AS is_owner,parent.brand AS first_name,parent.id AS id
   ,(select count(c.id) from campaigns c where (((c.user_id = parent.id) or (c.user_id = child.id)) and (c.campaign_status_id = 4))) AS current_campaigns
   ,(select count(c.id) from campaigns c where (((c.user_id = parent.id) or (c.user_id = child.id)) and (c.campaign_status_id = 5))) AS past_campaigns
   ,(select count(c.id) from campaigns c where (((c.user_id = parent.id) or (c.user_id = child.id)) and (c.campaign_status_id = 2))) AS pending_campaigns
   ,(select count(c.id) from campaigns c where (((c.user_id = parent.id) or (c.user_id = child.id)) and (c.invoice_status = '1'))) AS past_invoices
from ((users parent
left join campaigns mc on((parent.id = mc.user_id)))
left join users child on((child.parent_owner = parent.id)))
where ((parent.is_owner = 1) or (child.is_retailer = 1))
group by parent.id
order by parent.brand;");

更新、修正済み:

素晴らしい。ここにすべてからの非常に良い入力があります。

@patrick と @ErwinBrandstetter のソリューションはどちらも機能します。ここでの私の役割は、システムを「現状のまま」変換することであるため、ここではパトリックを支持します。将来的にはリファクタリングの余地があるかもしれませんが、この段階では、他の誰かのダクトテープソリューションを台無しにする (または改善する) のは危険だと感じています (つまり、コードベースは、ドキュメントの兆候がなく、場所によっては過度に複雑に見えます。ビジネス ロジックに関する背景情報がなければ、いろいろ調べたり、コアの改善を試みたりすることに消極的です)。いずれにせよ、モデルの一部をオーバーホールする必要があるのではないかと思うので、[原文のまま]-ここでは修正を優先します。

いくつかのクリック操作が元のクエリを生成した可能性があると思いました...元の開発者に疑いの利益を与えようとし、迅速な(つまり、面倒な)ターンアラウンドを必要とするビジネス上の圧力があったと想定しました。複雑な SQL は私の得意分野ではありませんが、私の直感が正しかったことをうれしく思います。クエリはそもそも不要で複雑です。おそらく、ビューは計画外のボルトオンでした-そもそも設計されていません. 賢明であろうとなかろうと、私はおそらく ORM ベースのアプローチで問題を解決しようとしたでしょう。

私は土壇場でこのプロジェクトに参加し、再起動のためにクリーンアップを実行しています (元の開発者は「手放しました」)。いわば空挺部隊を実行しています。ありがたいことに、このビューの問題はパズルの最後のピースに現れます。ありがとうございました :-)

4

2 に答える 2

6

ああ、ああ、ああ。ステートメントには 74 個以上の括弧が含まれていたため、開発者は右手の薬指に目盛りが付いていたことは間違いありません。54 行ではなく、8 つの括弧と 14 行だけで済む方法を次に示します。

SELECT 
  parent.is_owner AS is_owner,
  parent.brand AS first_name,
  parent.id AS id,
  sum(CASE WHEN c.campaign_status_id = 4 THEN 1 ElSE 0 END) AS current_campaigns,
  sum(CASE WHEN c.campaign_status_id = 5 THEN 1 ElSE 0 END) AS past_campaigns,
  sum(CASE WHEN c.campaign_status_id = 2 THEN 1 ElSE 0 END) AS pending_campaigns,
  sum(CASE WHEN c.invoice_status = '1' THEN 1 ElSE 0 END) AS past_invoices,
FROM users parent
LEFT JOIN users child ON child.parent_owner = parent.id
LEFT JOIN campaigns c ON c.user_id = parent.id OR c.user_id = child.id
WHERE parent.is_owner = 1 OR child.is_retailer = 1
GROUP BY parent.is_owner, parent.brand, parent.id
ORDER BY parent.brand;

副選択がないということは、このコードがより高速に起動することを意味します。Wolph がコメントで述べたように、集約関数に含まれていない選択リストのすべての列はGROUP BY、SQL 標準で指定されているように句に表示する必要がありますが、MySQL では幸いにも無視されます。

副選択は、CASE構文を使用することで回避されます: 列リストでの条件式の評価。サブ選択でのフィルタリングの反復句が句として実行されるようになったことに注意してください。メイン クエリの列ごとJOINに、関連する 1 つの列のみが評価されます。ステートメントからorをcampaigns発行し、それを関数でラップすることは、単一のクエリで複数の異なるカウントを行うための巧妙なトリックです。10CASEsum()

ウルフがこの回答の下のコメントで指摘したように、条項

sum(CASE WHEN c.campaign_status_id = 4 THEN 1 ElSE 0 END) AS current_campaigns

のように簡潔に書くこともできます。

sum((c.campaign_status_id = 4)::integer) AS current_campaigns

CASEこれは、PostgreSQL が記述されている C 言語でのブール値から整数へのキャストが操作を必要としない (C ではブール値は 1 または 0 のいずれかである) ことを考えると、ステートメントよりもいくらか高速になる可能性があります。読みやすさは確かに劣ります (2 倍の括弧を使用することは言うまでもありません!)。

于 2015-07-18T09:10:53.800 に答える
2

質問には説明がありませんが、考えられるユースケースは次のとおりです。

各ユーザーが「所有」しているキャンペーンの数を数えます。ユーザーは子ユーザーを持つことができ、そのキャンペーンは親ユーザーに追加されます。

@Patrick が demoで整理した信じられないほどノイズの多い構文は別として、クエリもあいまいです (そしておそらく完全に間違っています)。

仮定できる場合:

  • 参照整合性: 子ユーザーは既存の親ユーザーのみを参照し、FOREIGN KEY制約が適用されます。

  • 親と子は確実にマークされis_owneris_retailerこれらの列は値0とのみを保持します1下記参照。

このクエリは仕事をします:

SELECT CASE WHEN u.is_retailer = 1 THEN u.parent_owner
            WHEN u.is_owner = 1    THEN u.id END        AS user_id
     , max(u.is_owner)                                  AS is_owner
     , max(u.brand) FILTER (WHERE u.is_owner = 1)       AS first_name
     , count(*) FILTER (WHERE c.campaign_status_id = 4) AS current_campaigns
     , count(*) FILTER (WHERE c.campaign_status_id = 5) AS past_campaigns
     , count(*) FILTER (WHERE c.campaign_status_id = 2) AS pending_campaigns
     , count(*) FILTER (WHERE c.invoice_status = '1')   AS past_invoices
FROM   users          u
LEFT   JOIN campaigns c ON u.id = c.user_id
                       AND (c.campaign_status_id IN (4, 5, 2) OR 
                            c.invoice_status = '1')  -- exclude irrelevant early
WHERE  1 IN (u.is_owner, u.is_retailer)  -- parent & child, may be redundant
GROUP  BY 1
ORDER  BY 2;

かなり速いはずです。大きなテーブルには適切なインデックスを用意してください。
他のオプションがない場合、この条件は冗長です。

   WHERE  1 IN (u.is_owner, u.is_retailer)

boolean「そのまま」データモデルを操作しましたが、おそらく列だけが必要です。

  • is_childtrue子供向け、false親向け。
  • is_owner:trueオーナーfalse向け、小売業者向け。

Postgres 9.4 で導入された新しい集計句を使用する:FILTER

于 2015-07-18T15:34:28.947 に答える