From 9ed7ff88465d1749cb85eb292562aee9b55e4812 Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Wed, 9 Aug 2023 20:17:32 -0500 Subject: [PATCH] clarify FK joins --- docs/_static/premieres.png | Bin 8990 -> 0 bytes docs/references/api/resource_embedding.rst | 305 +++++++++++---------- 2 files changed, 163 insertions(+), 142 deletions(-) delete mode 100644 docs/_static/premieres.png diff --git a/docs/_static/premieres.png b/docs/_static/premieres.png deleted file mode 100644 index 794e96767de072d6414630fc9fbfbeb24d4f9d9b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8990 zcmeAS@N?(olHy`uVBq!ia0y~yV9aM=U^u|R#=yXEn(@(W1_lPUByV>YhW{YAVDIwD z3=9eko-U3d6?5L+tquu!`iuR;`)`MQ6C4~=UHlt6CM8uif0@C(-aRJp_vQP4-@IKpud*`zyqv2Gi;K$=)#z6Y+d7?lCs3}|9oDbT1OUE*2H5ylFQ1R#gz2)_*RFlH7b_~ z@t(1MTH=!v6V<*a7JbaSx5si`^*hVS%!;QEKFZxYWBs&+YF*~f_jGocbR3=C;n5s{ zU}o*D$iKgD?;-D0DU*x_cezTI#qRybW*VnwSx=ENPCqB}Rl@Gq3+3W==ajOkPb+V_ zpJaa|`t;%q!{jSZZ*R{xzpfZ*v$=0icUM=z+gqkN_x4D}?kYKWak2YZNj|}eX%T0( zc>4MA#Z|pjEpBⓈAQwwTGFBNkK_KP()n(@u#P!XIK<2+VyXGe4S%aQBiDLi0 z`r-Y_pPrn2#@Bvg(UfzaKgnHQ?$3U}#%StIw|iZS7F2$I7O^>v_s*R=SDvaC-%owM zci;bi)g|xlfULgw`g#5T-||)8-f+(KV^2>{FL`rAaCP{4x5C1Wal0@5eS3Ge`23nr zoHezziTU~c2M##Axv`O%q2SMt!Y{9`a`*N1Wn5gus^&AJ;menjz5C?Mp2yCek@ZM| zQNh$ybh4VS)9GotiOI>#Ad_ETFYnqd zXSe0vW@%uU(9qCOblc|VlgU}LeP^3>cAR)|aq-pE|Np-4KYH(89D_$f!h&6Q@9ZqT zYL+U0-v0ldJNxV7bE9|dZ)9ed+Oj@;{k%1Cd!z39zjQAs*dQV#m-YpEqfpT70Q;WA5#3 zMbl@`I&3;odl`d*prGKg62Z)GmhYRInEre`F8}KDrNtkFw8PiAs>{q=f$^36@DPuKnab~~SUny9#V@~J7BY`+=h?z}%dgFo1}bER5e;lU=>9d&=J zTHo&f`AquFt*xsMeRVN?cx7dQ=R7)$kflz&Yn1R$|)vBhT*~9@ArbQ@Bi_rdq>@0DKXg`0-=K>GSh!e_wcPsJd*H#jzPP zBuXnwN=!a|{ycHsymiwJmfjY~oC1DW`sCIt^>FX^d(Ep?Ye#L(YE^V@Tk-l-wYmP;E1-D$ z{OQw`f8XBS?Vhaef9UE}(fqw%r~Nu8kZ(yRO8S65i-0F9CESvvDZOK^pZ+GJ1wkpFZb#cG$ z^CxHdm=^Zzt^OW1y|ul4arX6f8Q0cG9_y8E@95yTv$Od4i^qzq)}2~s7P+scviHql zh_%%p%ZiH+zqq(qP+q=&nr`&8+sDqG^LtX)+;@$XXUEDxwp6Z&JB8bcei;{Q`3j9*W;B{k5hu(byD|7sS0_fP3vqZ)1!z327y_5E|L%OCyye&79gpX|d24;ZYitwBY` zj|a{C8>+wO-MDch!)n>8Z|2qCa%#TcEtfFMk=V0m&xH+%&LD^S&Ni!f+-oi%FV8P7 zKK+(OiIVTewwS#~E!SBOzt?z7|e>?o9z_!cQ;M;8Idj z^7(1_LU)#IrLf@NsT(VV1v3S@%>^g2k;$C-#W+J#Q`4gSoy?xSd$YCVj5be_w^ajG zWjdOgnrgY{CxT3$Y4YjY=g-aa=gYt6&vEs7_uThd`_C*(5!c4~^XGfk7CiBFS>mX3 z&&5UR$Ri|XE-R>Tudn~#zRla)yLpjD#IBM|Sr^w7TOGsW;F|5k5^vU%Mlv=P7w#wu zM4Eb^G3c(=K}yt{il!A?(0i_6i`v7n%!Vfk`(F`bBpMT?Y-T|JiMPMeWD$84k1 zY_r@$H#R0KDls(Nxf7!ky^UwOUaV1mf=k#Tp`sq;eC^4zXLqj*Ue3fIrWez3db+-{ zt*z|qYikd0%e{R@Mt0)2NUy%aC%LJ7vQ{l;&iMGuu@LMQ(+!&LKgU9F=FFKhjMMvm zHYsmnnl>p}rZReFx41qN0~^0w&W5}L4UDC-d=sy3oFbF_ahuWCSD}C6qi$L$8TBoF zBC+}AsUJUnFff$8y_LEl@BBPln9|2{woiR|Z?E;8yLUn5SVd)J;-MDKf`Wnr+j4Jj z$hfG~x3AMDMbq+_&o-m7va*P+S)wj1+~RsJpP!w*xT7%HXNCdeVz=I=)vH(UbXK`! z;(gLkEMMEC>PrTwc64-f+^~6bb9*~`+POKMTeGjvvNlm#oxo{%EK^u}L;e4{=G103 z-a`u9qDKmX4A}6#a?TecB^73+J8=F15X6GAbK9Lm>`LbZTZnV$K zHXViyfi8tGXdWM9Eh`2h2gorR4xO`doyxs3L zGZ(M2J|>rc^K;1Mh&_xye*Ich{{G%GzQYdZ=h;4d|33enLB|&r6%_^stMYd}phTvt z%Nx6^#IQJ|bg|a)=wCKh+5Sl|3x;|+IWa|T$!J{WJDX|6Z~c1rIhbGM^D^Hq82Mv#~;WBBS8JHeP7~85tgTcXtbG>*OaVCVF^yIJmjBS&0Ue zWu3o%U3~qXPoR?I!n#;%3kwSceSLn04avv(zP!IL|L)zpj4LYytx8@r^xOZtk)X1> z^mW*mmzUXlrOn$mZ#H&vaw_=r#FL@HZ~5ULA0IO^96WeX!lq)wuDknce;Z_6P^hS= zIB@Nnn5%;! zvNjw!cFZdM$jxPmj9wr`?ukm}y_XucJdm zT>Nwkr|_xV$FE*#+5i8u`QWF_%gZ`DPVA}teCn=WponF`g9FZwjoofOHvMKZmA%2m z#YJN8`y!W372Sy+`?|Y@tE+z({SOw?k6W|t_SWp_CK(qN+$obb&l3@xXj%L$X~@{=Eb`uK6;h7ATUFD`C(+&y8!1h4a~-P^w!nZ-}niBw`xF)#>7N=kZiZ*TRa zS+lef6BC&nUR_nm&mNo3{{HD|zOy>|`};dObmI5LFm%kHKmW~*jn11kZ&p=S zc3$Q?d)an*e}Dg%*VoUNdcUvsH%r5ojEheD{{6~6dFs@pHEVPtcNQ&W2q@mY=tocJ zw~Ag{|EDWEAD!jbIV~W#=f|UN4GoP0*6;T;8zvt+aO@b{?y|RsuB;6H_`d$X^yB0G z?wixj8f0El(dcUW@S&hb&Q_{d%Jk6p`}O^WkB@ z7F^uig%1uewzRb+y}q`VgO~T{^ZE6ChRMek_|Lbyu`M@x=X6C$>FL|In=>RlKR5To zw{LDvPE1TpOakKK?GF#PKfbWg`NZkd&2#6@J^5TqTbuj$x3{Yc_WCV%zQ3<_XRU7j z{cX9?EG#S`H`Zxw+O+Azx7+!L4ub7uzTL`x_36@{J$nj%f6E0`VC&Ycv#9xD zFv}!!(YpE8<$CL4b~dT`&U*0Y=jR<|Z>8M&<<_p7Z&z#e=IvWSY3c6ca@9VucE!(p zYJNVQZc+Tqr}bl~>&wS$PgSnc)w!2?5-nzxKS*waCrQy|_I;e#g$8FSo8< zxkJr=9uLEg;^%zZ^6qx+|NAzdp&@Q>RpOl;g+D$V=FeVJG5Kp}U!R+wAD@_B%nGq3 zLS2g-T&`5!X=!1(apQ)9fk8m+yGM_jrpMPs-c?gmV_Ug$<q5-V`kWre_xK_!)g8f4?zBSyY>1PuJX=DkCH5`t-~ea zL#No?dwYAkbnUz>?(T9)Pft&VhPuDMB-dz1mOVVgdS&(Z2b=Gma&tS-Ebd=ZV^jCz zA-jsYy0WFEAn6E4-ujDgPX`-$ zy}iGG|G`~1ZbXQPi@)yO!pzK^n4Qhd;Nj!r(l2M5ke|Ok@AGAUd&Y`@l-pag!ynd( z>&0*|NLUmkEd3~JS0ll2W#6O6kG1c-`}FkmkFVF`Ki(-m9}02#syWMV9a`;sYJ$D} z&oGgr39+6B#X$XSW(Fyf6+iyIe|B~@2M>?S_Po0v&RM^I@a*jD7uVKGD=8^m*j4&E zs&jSA?%mZ>f2lJy{Qmx)nc>st&tIpBi;5;*T;$3i=Og{(=~G2bO;6o`>3Xq;dZo>I zxWW<>6&W79y1Lr!?e;y_%QM^g<=vXuc&{{`zq`Af(c$dzOQ1Hj;K3UkleK%!N2K;& zzwtR_cEq038kO$-eSKyT+tbg>F&tRjZ?~#1D>*4i>rM9ab8|hsybf(jJzenkSLvHu zTe-Ev);M^0cucKlI(cKqnU{}_cE7m3US38<=E38~&Arm*vrOf(g8~8?)<$oa66v1x z?DcDIhKj#muTPvbsp-$38X5aK8D@4q7bhpCoZQ@t`)aG_SQIj`va+&$z8kx{?CR_F zPkz3+xw-Js5zZ&io;|v=vl!G$iiwH2ur}J9A>jF&^z-w2xyAJsELgw*^7!ri{k+l9 z(Jig5in_YI&(F_ye|>H3)kl#ay~xRdw7}eY=eXY45r^c zJu(gh7uHv`g6-aIIkwujtGjz??(J=t7C16je!p9;s;ld3WMuSYvcH|6ySw|!`2G9- z{QLdB==;0a%sV?a-nen&1iO5Vf_dJZ33u+q{CwChf8_Xa_dR>|q`bbimdQcQf1ZxG zUd)8m;p@G8e0o}1S~?bKTv``vee&Env5EixAE~*!t8~)*`T9z~!e>iJhu?gxnfve0 zPbP=FySqXk?p*Qis@l(=KOen+umAe``svl*-<>=;SzT2@!QtDxyU|bgty=YD6%)g! zzu)h#J{=q!eDvHozgY9^YipwVmzse_E#}pJi!3ZDnUZ~dUFdH4QAqp zl9Q8jV+r_4^+^N~-+za{1}KPODN+ zPn$Gnj*gPj&Ye3i?Wz1+bldjVi^Y?sO;c;`tp*ii(JgQ>nnqo7j6Ii`Sau} zSFU(Hh6ZjZ%bt)8X;XB|?En2(960?+?a!yv7ioCye0%8d;iuo;-hTA-Y3hc&8ygah zN?ru)J2&JnTspxzuioKWx8Fp?%&V!m-qHoPyM~B@6DT>kKevcQ=eDi z#Kq11_1fFp+x_=Wi^%@;`E%-r2M0ero1K4Zf}(TQ-imIU9V+Y6&(8z3Fy!RqjB0)q zbarXLG0MIfk&arEd>C8bM?-T6O$|E|A%zjgV$6H7cNAGv!s7SvwJ zy}b=&rdL(fuDIP#o}@?`Bs5r9S#>oxH*d+j>~^o>G4II}Cj=(4xBi-$KJVi0ebFu| zwzjc{4jp$e?&s&1l%5{` zaC*tUzu%%?URyic!os4%Z~0`s*j+ESdV6_I+E@F#%Wt``zP^6srj$+*F|kt%o!dcf zWMX1+aZyoMcXx7fI&$q=m|XpzLP5>{-d4wsr1m|st@%;l>E&e+wOPzX!X-mQ$b$r>bMg5aa&31{W)7#sCAjIH)AaL_ouQsb*@$E zp?iC)<#)YpU}V$g7AdSAhi@bc19 zP(N_#?Xd8ytgM~o4mp>X@z(wS`~BAZ|GzI^TIzjqXK}jC|39BIuC0lj`)0G***TV% z?HNP3!EvdvdI-tOA0I<&uy0nU@7LGYmCen?_y7B)eXK{anUz~CWXIW6p{o

yy3Q_C#;ZVy&+J-Tv2 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 diff --git a/docs/references/api/resource_embedding.rst b/docs/references/api/resource_embedding.rst index 3870f298a..6e12171c2 100644 --- a/docs/references/api/resource_embedding.rst +++ b/docs/references/api/resource_embedding.rst @@ -10,10 +10,12 @@ PostgREST allows including related resources in a single API call. This reduces Foreign Key Joins ================= -The server uses **Foreign Keys** to determine which tables and views can be joined together. +The server uses **Foreign Keys** to determine which database objects can be joined together. It supports joining tables, views and +table-valued functions. -- For joining tables, it reads foreign keys (respecting composite keys) and generates a join condition based on the foreign key columns. -- For joining views, it reads the base tables of the views' definition, and generates a join condition based on the foreign key columns of the base tables. +- For tables, it generates a join condition using the foreign keys columns (respecting composite keys). +- For views, it generates a join condition using the views' base tables foreign key columns. +- For table-valued functions, it generates a join condition based on the foreign key columns of the function return type. .. important:: @@ -55,6 +57,13 @@ For example, consider a database of films and their awards: language text ); + CREATE TABLE technical_specs( + film_id INT REFERENCES films UNIQUE, + runtime TIME, + camera TEXT, + sound TEXT + ); + create table roles( film_id int references films(id), actor_id int references actors(id), @@ -80,7 +89,7 @@ For example, consider a database of films and their awards: Many-to-one relationships ------------------------- -Since ``films`` has a **foreign key** to ``directors``, this establishes a many-to-one relationship. Thus, we're able to request all the films and the director for each film. +Since ``films`` has a **foreign key** to ``directors``, this establishes a many-to-one relationship. This enables us to request all the films and the director for each film. .. tabs:: @@ -183,9 +192,19 @@ The **foreign key reference** establishes the inverse one-to-many relationship. Many-to-many relationships -------------------------- -The join table determines many-to-many relationships. It must contain foreign keys to other two tables and they must be part of its composite key. +The join table determines many-to-many relationships. It must contain foreign keys to other two tables and they must be part of its composite key. In the :ref:`sample film database `, ``roles`` is taken as a join table. -Thus, it can detect the join table ``roles`` between ``films`` and ``actors``: +The join table is also detected if the composite key has additional columns. + +.. code-block:: postgresql + + create table roles( + id int generated always as identity, + , film_id int references films(id) + , actor_id int references actors(id) + , character text, + , primary key(id, film_id, actor_id) + ); .. tabs:: @@ -209,18 +228,6 @@ Thus, it can detect the join table ``roles`` between ``films`` and ``actors``: ".." ] -The join table can also be detected if the composite key has additional columns: - -.. code-block:: postgresql - - create table roles( - id int generated always as identity, - , film_id int references films(id) - , actor_id int references actors(id) - , character text, - , primary key(id, film_id, actor_id) - ); - .. _one-to-one: One-to-one relationships @@ -228,7 +235,7 @@ One-to-one relationships One-to-one relationships are detected in two ways. -- When the foreign key is a primary key as specified in the :ref:`DB structure example `. +- When the foreign key is a primary key as specified in the :ref:`sample film database `. - When the foreign key has a unique constraint. .. code-block:: postgresql @@ -265,30 +272,24 @@ One-to-one relationships are detected in two ways. Computed Relationships ====================== -You can manually define relationships between using functions. This is useful for database objects that can't define foreign keys, like `Foreign Data Wrappers `_. +You can manually define relationships by using functions. This is useful for database objects that can't define foreign keys, like `Foreign Data Wrappers `_. Assuming there's a foreign table ``premieres`` that we want to relate to ``films``. -.. tabs:: +.. code-block:: postgresql - .. group-tab:: ERD + create foreign table premieres ( + id integer, + location text, + "date" date, + film_id integer + ) server import_csv options ( filename '/tmp/directors.csv', format 'csv'); - .. image:: ../../_static/premieres.png + create function film(premieres) returns setof films rows 1 as $$ + select * from films where id = $1.film_id + $$ stable language sql; - .. code-tab:: postgresql SQL - - create foreign table premieres ( - id integer, - location text, - "date" date, - film_id integer - ) server import_csv options ( filename '/tmp/directors.csv', format 'csv'); - - create function film(premieres) returns setof films rows 1 as $$ - select * from films where id = $1.film_id - $$ stable language sql; - -The above function (see the **SQL** tab) defines a relationship between ``premieres`` (the parameter) and ``films`` (the return type). Since there's a ``rows 1``, this defines a many-to-one relationship. +The above function defines a relationship between ``premieres`` (the parameter) and ``films`` (the return type). Since there's a ``rows 1``, this defines a many-to-one relationship. The name of the function ``film`` is arbitrary and can be used to do the embedding: .. tabs:: @@ -363,36 +364,31 @@ Thanks to overloaded functions, you can use the same function name for different select * from directors where film_school_id = $1.id $$ stable language sql; -Computed relationships have good performance as their intended design enable `inlining `_. +Computed relationships have good performance as their intended design enable `function inlining `_. .. warning:: - - Always use ``SETOF`` when creating computed relationships. Functions can return a table without using ``SETOF``, but bear in mind that they will not be inlined. + - Always use ``SETOF`` when creating computed relationships. Functions can return a table without using ``SETOF``, but bear in mind that PostgreSQL will not inline them. - Make sure to correctly label the ``to-one`` part of the relationship. When using the ``ROWS 1`` estimation, PostgREST will expect a single row to be returned. If that is not the case, it will unnest the embedding and return repeated values for the top level resource. .. _embed_disamb: .. _target_disamb: +.. _hint_disamb: .. _complex_rels: -FK Joins on Multiple Foreign Key Relationships -============================================== +Foreign Key Joins on Multiple Foreign Key Relationships +======================================================= When there are multiple foreign keys between tables, :ref:`fk_join` need disambiguation to resolve which foreign key columns to use for the join. +To do this, you can specify a foreign key by using the ``!hint`` syntax. -.. code:: +.. _multiple_m2o: - HTTP/1.1 300 Multiple Choices +Multiple Many-To-One +-------------------- - { - "code": "PGRST201", - "details": [ "..." ], - "hint": "...", - "message": "Could not embed because more than one relationship was found for 'sites' and 'big_projects'" - } - -Instead of the **table name**, you can specify the **foreign key constraint name** or the **column name** that is part of the foreign key. -For example, let's use the following tables: +For example, suppose you have the following ``orders`` and ``addresses`` tables: .. tabs:: @@ -415,23 +411,41 @@ For example, let's use the following tables: name text, billing_address_id int, shipping_address_id int, - constraint billing_address - foreign key(billing_address_id) references addresses(id), - constraint shipping_address - foreign key(shipping_address_id) references addresses(id) + constraint billing foreign key(billing_address_id) references addresses(id), + constraint shipping foreign key(shipping_address_id) references addresses(id) ); -To successfully join ``orders`` with the billing and shipping ``addresses``, use the corresponding foreign key constraints: +Since the ``orders`` table has two foreign keys to the ``addresses`` table, a foreign key join is ambiguous and PostgREST will respond with an error: .. tabs:: .. code-tab:: http - GET /orders?select=name,billing_address(name),shipping_address(name) HTTP/1.1 + GET /orders?select=*,addresses(*) HTTP/1.1 .. code-tab:: bash Curl - curl "http://localhost:3000/orders?select=name,billing_address(name),shipping_address(name)" + curl "http://localhost:3000/orders?select=*,addresses(*)" -i + + +.. code-block:: http + + HTTP/1.1 300 Multiple Choices + + {..} + + +To successfully join ``orders`` with ``addresses``, you can specify the foreign key name like so: + +.. tabs:: + + .. code-tab:: http + + GET /orders?select=name,billing_address:addresses!billing(name),shipping_address:addresses!shipping(name) HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/orders?select=name,billing_address:addresses!billing(name),shipping_address:addresses!shipping(name)" .. code-block:: json @@ -447,43 +461,49 @@ To successfully join ``orders`` with the billing and shipping ``addresses``, use } ] -.. _hint_disamb: +Note that ``!billing`` and ``!shipping`` are foreign keys names, which have been named explicitly in the :ref:`SQL definition above `. -Multiple FK Relationships to Many Resources -------------------------------------------- +.. _multiple_o2m: -Additionally, let's create two views for ``addresses``: ``central_addresses`` and ``eastern_addresses``. -Using the the view name is not enough to join ``orders`` with any of them. -To solve this, you need to add the foreign key as a hint: +Multiple One-To-Many +-------------------- + +Let's take the tables from :ref:`multiple_m2o`. To get the opposite one-to-many relationship, we can also specify the foreign key name: .. tabs:: .. code-tab:: http - GET /orders?select=name,central_addresses!billing_address(name),central_addresses!shipping_address(name) HTTP/1.1 + GET /addresses?select=name,billing_orders:orders!billing(name),shipping_orders!shipping(name)&id=eq.1 HTTP/1.1 .. code-tab:: bash Curl - curl "http://localhost:3000/orders?select=name,central_addresses!billing_address(name),central_addresses!shipping_address(name)" + curl "http://localhost:3000/addresses?select=name,billing_orders:orders!billing(name),shipping_orders!shipping(name)&id=eq.1" .. code-block:: json [ { - "name": "Personal Water Filter", - "billing_address": { - "name": "32 Glenlake Dr.Dearborn, MI 48124" - }, - "shipping_address": { - "name": "30 Glenlake Dr.Dearborn, MI 48124" - } + "name": "32 Glenlake Dr.Dearborn, MI 48124", + "billing_orders": [ + { "name": "Personal Water Filter" }, + { "name": "Coffee Machine" } + ], + "shipping_orders": [ + { "name": "Coffee Machine" } + ] } ] +Recursive Relationships +----------------------- + +To disambiguate recursive relationships, PostgREST requires :ref:`computed_relationships`. + .. _recursive_o2o_embed: Recursive One-To-One --------------------- +~~~~~~~~~~~~~~~~~~~~ .. tabs:: @@ -541,7 +561,7 @@ Now, to query a president with their predecessor and successor: .. _recursive_o2m_embed: Recursive One-To-Many ---------------------- +~~~~~~~~~~~~~~~~~~~~~ .. tabs:: @@ -593,7 +613,7 @@ Now, the query would be: .. _recursive_m2o_embed: Recursive Many-To-One ----------------------- +~~~~~~~~~~~~~~~~~~~~~~ Let's take the same ``employees`` table from :ref:`recursive_o2m_embed`. To get the Many-To-One relationship, that is, the employees with their respective supervisor, you need to create a function like this one: @@ -630,7 +650,7 @@ Then, the query would be: .. _recursive_m2m_embed: Recursive Many-To-Many ----------------------- +~~~~~~~~~~~~~~~~~~~~~~ .. tabs:: @@ -703,8 +723,8 @@ Then, the request would be: .. _embedding_partitioned_tables: -FK Joins on Partitioned Tables -============================== +Foreign Key Joins on Partitioned Tables +======================================= Foreign Key joins can also be done between `partitioned tables `_ and other tables. @@ -749,17 +769,17 @@ Since it contains the ``films_id`` foreign key, it is possible to join ``box_off .. note:: - * FK joins on partitions is not allowed because it leads to ambiguity errors (see :ref:`embed_disamb`) between them and their parent partitioned table. More details at `#1783(comment) `_). :ref:`computed_relationships` can be used if this is needed. + * Foreign key joins on partitions is not allowed because it leads to ambiguity errors (see :ref:`embed_disamb`) between them and their parent partitioned table. More details at `#1783(comment) `_). :ref:`computed_relationships` can be used if this is needed. * Partitioned tables can reference other tables since PostgreSQL 11 but can only be referenced from any other table since PostgreSQL 12. .. _embedding_views: -FK Joins on Views -================= +Foreign Key Joins on Views +========================== -PostgREST will infer the relationships of a view based on its base tables. Base tables are the ones referenced in the ``FROM`` and ``JOIN`` clauses of the view definition. -The foreign keys of the relationships must be present in the top ``SELECT`` clause of the view for this to work. +PostgREST will infer the foreign keys of a view using its base tables. Base tables are the ones referenced in the ``FROM`` and ``JOIN`` clauses of the view definition. +The foreign keys' columns must be present in the top ``SELECT`` clause of the view for this to work. For instance, the following view has ``nominations``, ``films`` and ``competitions`` as base tables: @@ -792,7 +812,7 @@ It's also possible to foreign key join `Materialized Views `_. This may fail depending on the complexity of the view. @@ -802,15 +822,15 @@ It's also possible to foreign key join `Materialized Views ` that returns a table type, you can do a Foreign Key join on the result. @@ -845,6 +865,59 @@ A request with ``directors`` embedded: } ] +.. _mutation_embed: + +Foreign Key Joins on Writes +=========================== + +You can join related database objects after doing :ref:`insert`, :ref:`update` or :ref:`delete`. + +Say you want to insert a **film** and then get some of its attributes plus join its **director**. + +.. tabs:: + + .. code-tab:: http + + POST /films?select=title,year,director:directors(first_name,last_name) HTTP/1.1 + Prefer: return=representation + + { + "id": 100, + "director_id": 40, + "title": "127 hours", + "year": 2010, + "rating": 7.6, + "language": "english" + } + + .. code-tab:: bash Curl + + curl "http://localhost:3000/films?select=title,year,director:directors(first_name,last_name)" \ + -H "Prefer: return=representation" \ + -d @- << EOF + { + "id": 100, + "director_id": 40, + "title": "127 hours", + "year": 2010, + "rating": 7.6, + "language": "english" + } + EOF + +Response: + +.. code-block:: json + + { + "title": "127 hours", + "year": 2010, + "director": { + "first_name": "Danny", + "last_name": "Boyle" + } + } + .. _nested_embedding: Nested Embedding @@ -1157,55 +1230,3 @@ You can use this to get the columns of a join table in a many-to-many relationsh The spread operator ``...`` is borrowed from the Javascript `spread syntax `_. -.. _mutation_embed: - -Embedding after Insertions/Updates/Deletions -============================================ - -You can embed related resources after doing :ref:`insert`, :ref:`update` or :ref:`delete`. - -Say you want to insert a **film** and then get some of its attributes plus embed its **director**. - -.. tabs:: - - .. code-tab:: http - - POST /films?select=title,year,director:directors(first_name,last_name) HTTP/1.1 - Prefer: return=representation - - { - "id": 100, - "director_id": 40, - "title": "127 hours", - "year": 2010, - "rating": 7.6, - "language": "english" - } - - .. code-tab:: bash Curl - - curl "http://localhost:3000/films?select=title,year,director:directors(first_name,last_name)" \ - -H "Prefer: return=representation" \ - -d @- << EOF - { - "id": 100, - "director_id": 40, - "title": "127 hours", - "year": 2010, - "rating": 7.6, - "language": "english" - } - EOF - -Response: - -.. code-block:: json - - { - "title": "127 hours", - "year": 2010, - "director": { - "first_name": "Danny", - "last_name": "Boyle" - } - }