1) 種別
- 画面:
種別(M2O / O2M / M2M バッジ) - SQL:
rel_kind_raw- 値:
'many2one' | 'one2many' | 'many2many'
- 値:
- 画面の「日本語分類」見出し(多対一…など)
- SQL:
classification
2) こちらのフィールド
- 画面:
こちらのフィールド(例: partner_id) - SQL:
local_field
3) 相手モデル
- 画面:
相手モデル(例: res.partner) - SQL:
remote_model- 元:
ir_field_src.relation
- 元:
4) 相手側キー
- 画面:
相手側キー(スクショでは id が多い) - SQL:
remote_key- many2one: 常に
'id' - one2many:
inverse_fieldがあればそれ(= 子テーブル側の外部キー) - many2many:
'id'(本質は junction 側で結ぶので、表示はid固定でOK)
- many2one: 常に
画面の「相手側キー」は “JOINで使う相手側の結合キー” という意味で表示しているはずなので、O2Mだけは
inverse_fieldを見せるのが自然です。
5) 用途/要約
- 画面:
用途/要約(スクショでは日本語テキスト) - SQL:
usage_summary_ja- 元:
ir_field_src.notes(JSON/テキストどちらでもそのまま) - ※
notesが i18n JSON の場合は UI 側でja_JPを優先表示にするのが良いです
- 元:
6) SQL結合ヒント
- 画面:
SQL結合ヒント(例:JOIN res_partner p ON so.partner_id = p.id) - SQL:
sql_join_hint- many2one:
local_alias.local_field = remote_alias.id - one2many:
local_alias.id = remote_alias.inverse_field(inverseが取れた時だけ) - many2many:
local_alias.id = j.column1→remote.id = j.column2(junctionが取れた時だけ)
- many2one:
ここは「ある時だけ出る」でOK(無いものは画面で埋める方針)。
画面には出てないが、UI/次工程で使える列(推奨)
A) モデル/テーブル情報(ページ文脈)
local_model(例: sale.order)local_table(例: sale_order)remote_table(例: res_partner など)- 元:
ir_model_src.model_table(remote_model を引いて解決)
- 元:
→ 画面で SQL結合ヒント を生成し直したい時や、JOINプランに流す時に必須。
B) 相手の表示フィールド(UIでリンク/サジェストに使える)
remote_display_field(name / display_name / id)remote_display_label_ja(そのラベル)
→ スクショには無いですが、「相手側の代表表示」用途で便利。
C) O2M / M2M の追加情報(詳細に出すなら)
inverse_field,inverse_field_label_ja(O2M)junction_table,junction_col_local,junction_col_remote(M2M)
D) 一意キー
natural_key(例:relation::sale.order::partner_id)
→ フロント側で行IDに使える、差分更新にも強い。
まとめ:画面列を表示する最小セット
画面の表を成立させる最小はこれです:
rel_kind_raw(種別バッジ)local_field(こちらのフィールド)remote_model(相手モデル)remote_key(相手側キー)usage_summary_ja(用途/要約)sql_join_hint(SQL結合ヒント)
CREATE OR REPLACE VIEW public.v_ir_model_relations AS
WITH local_fields AS (
SELECT
f.id,
f.model AS local_model,
f.model_table AS local_table,
f.field_name AS local_field,
lower(f.ttype) AS rel_kind_raw,
f.label_i18n,
f.notes,
-- notes の中のカスタム(UI上書き)を参照
CASE
WHEN jsonb_typeof(f.notes) = 'object' THEN (f.notes->'__relation_custom')
ELSE NULL
END AS rel_custom,
NULLIF(f.relation,'') AS base_remote_model,
NULLIF(f.relation_field,'') AS base_inverse_field,
NULLIF(f.relation_table,'') AS base_junction_table,
NULLIF(f.column1,'') AS base_junction_col_local,
NULLIF(f.column2,'') AS base_junction_col_remote
FROM public.ir_field_src f
WHERE lower(f.ttype) IN ('many2one','one2many','many2many')
),
local_fields_resolved AS (
SELECT
lf.*,
-- ★カスタムがあれば上書き、なければ src を採用(空文字は無視)
COALESCE(NULLIF(lf.rel_custom->>'remote_model',''), lf.base_remote_model) AS remote_model,
COALESCE(NULLIF(lf.rel_custom->>'inverse_field',''), lf.base_inverse_field) AS inverse_field,
COALESCE(NULLIF(lf.rel_custom->>'junction_table',''), lf.base_junction_table) AS junction_table,
COALESCE(NULLIF(lf.rel_custom->>'junction_col_local',''), lf.base_junction_col_local) AS junction_col_local,
COALESCE(NULLIF(lf.rel_custom->>'junction_col_remote',''), lf.base_junction_col_remote) AS junction_col_remote
FROM local_fields lf
),
local_field_label AS (
SELECT
lfr.id,
COALESCE(
NULLIF(lfr.label_i18n->>'ja_JP',''),
NULLIF(lfr.label_i18n->>'en_US',''),
lfr.local_field
) AS local_field_label_ja
FROM local_fields_resolved lfr
),
remote_model_table AS (
SELECT
lfr.id,
lfr.remote_model,
COALESCE(NULLIF(rm.model_table,''), replace(lfr.remote_model,'.','_')) AS remote_table
FROM local_fields_resolved lfr
LEFT JOIN public.ir_model_src rm
ON rm.model = lfr.remote_model
),
remote_display_pick AS (
SELECT
lfr.id,
COALESCE(
NULLIF(lfr.rel_custom->>'remote_display_field',''),
CASE
WHEN lfr.remote_model IS NULL THEN NULL
WHEN EXISTS (
SELECT 1 FROM public.ir_field_src rf
WHERE rf.model = lfr.remote_model AND rf.field_name = 'name'
) THEN 'name'
WHEN EXISTS (
SELECT 1 FROM public.ir_field_src rf
WHERE rf.model = lfr.remote_model AND rf.field_name = 'display_name'
) THEN 'display_name'
ELSE 'id'
END
) AS remote_display_field
FROM local_fields_resolved lfr
),
remote_display_label AS (
SELECT
rdp.id,
rdp.remote_display_field,
COALESCE(
NULLIF(rf.label_i18n->>'ja_JP',''),
NULLIF(rf.label_i18n->>'en_US',''),
rdp.remote_display_field
) AS remote_display_label_ja
FROM remote_display_pick rdp
LEFT JOIN local_fields_resolved lfr ON lfr.id = rdp.id
LEFT JOIN public.ir_field_src rf
ON rf.model = lfr.remote_model
AND rf.field_name = rdp.remote_display_field
),
inverse_field_label AS (
SELECT
lfr.id,
COALESCE(
NULLIF(rf.label_i18n->>'ja_JP',''),
NULLIF(rf.label_i18n->>'en_US',''),
lfr.inverse_field
) AS inverse_field_label_ja
FROM local_fields_resolved lfr
LEFT JOIN public.ir_field_src rf
ON rf.model = lfr.remote_model
AND rf.field_name = lfr.inverse_field
)
SELECT
CASE rel_kind_raw
WHEN 'many2one' THEN '多対一(参照)'
WHEN 'one2many' THEN '一対多(子テーブル)'
WHEN 'many2many' THEN '多対多(中間テーブル)'
END AS classification,
rel_kind_raw,
local_model,
local_table,
local_field,
lfl.local_field_label_ja,
lfr.remote_model,
rmt.remote_table,
CASE
WHEN rel_kind_raw = 'many2one' THEN 'id'
WHEN rel_kind_raw = 'one2many' THEN COALESCE(lfr.inverse_field, 'id')
WHEN rel_kind_raw = 'many2many' THEN 'id'
END AS remote_key,
rdl.remote_display_field,
rdl.remote_display_label_ja,
-- ★usage_summary_ja は jsonb のまま(互換維持)
-- 優先: custom.usage_summary_ja(文字列)→ notes.ja_JP → notes.en_US → local_field
COALESCE(
to_jsonb(NULLIF(lfr.rel_custom->>'usage_summary_ja','')),
CASE WHEN jsonb_typeof(lfr.notes) = 'object' THEN to_jsonb(NULLIF(lfr.notes->>'ja_JP','')) END,
CASE WHEN jsonb_typeof(lfr.notes) = 'object' THEN to_jsonb(NULLIF(lfr.notes->>'en_US','')) END,
to_jsonb(lfr.local_field)
) AS usage_summary_ja,
-- joinヒント:custom があればそれ、なければ計算値(text)
COALESCE(
NULLIF(lfr.rel_custom->>'sql_join_hint',''),
CASE
WHEN rel_kind_raw = 'many2one' AND rmt.remote_table IS NOT NULL THEN
'JOIN ' || rmt.remote_table || ' r ON l.' || lfr.local_field || ' = r.id'
WHEN rel_kind_raw = 'one2many' AND rmt.remote_table IS NOT NULL AND lfr.inverse_field IS NOT NULL THEN
'JOIN ' || rmt.remote_table || ' r ON l.id = r.' || lfr.inverse_field
WHEN rel_kind_raw = 'many2many'
AND lfr.junction_table IS NOT NULL
AND lfr.junction_col_local IS NOT NULL
AND lfr.junction_col_remote IS NOT NULL
AND rmt.remote_table IS NOT NULL THEN
'JOIN ' || lfr.junction_table || ' j ON l.id = j.' || lfr.junction_col_local
|| ' JOIN ' || rmt.remote_table || ' r ON r.id = j.' || lfr.junction_col_remote
ELSE NULL
END
) AS sql_join_hint,
lfr.inverse_field,
ifl.inverse_field_label_ja,
lfr.junction_table,
lfr.junction_col_local,
lfr.junction_col_remote,
('relation::' || lfr.local_model || '::' || lfr.local_field) AS natural_key
FROM local_fields_resolved lfr
LEFT JOIN local_field_label lfl ON lfl.id = lfr.id
LEFT JOIN remote_model_table rmt ON rmt.id = lfr.id
LEFT JOIN remote_display_label rdl ON rdl.id = lfr.id
LEFT JOIN inverse_field_label ifl ON ifl.id = lfr.id
;
コメントを残す