関係代数の演算

3.8.7. 関係代数の演算#

PySIMPLE は多次元の疎な集合を頻繁に扱いますが,これらの操作は関係代数の演算として整理できます.

まずは,基本的な演算である,和,差,交わり,直積,射影,選択を見てみます.

  • \(I\cup J=\{n \mid n\in I \lor n\in J\}\)

  • \(I-J=\{n \mid n\in I \land n\notin J\}\)

  • 交わり \(I\cap J=\{n \mid n\in I \land n\in J\}\)

  • 直積 \(I\times J=\{(i,j) \mid i\in I \land j\in J\}\)

  • 射影 \(\pi_I(IJ)=\{i \mid (i,j)\in IJ\}\)

  • 選択 \(\sigma_{I=3}(IJ)=\{(i,j) \mid i=3 \land (i,j)\in IJ\}\):

    >>> I = Set(value=[1,2,3,4], name='I')
    >>> J = Set(value=[2,4,6,8], name='J')
    >>> print(I | J)  # 和
    (I|J)=Set(value=[1, 2, 3, 4, 6, 8])
    >>> print(I - J)  # 差
    (I-J)=Set(value=[1, 3])
    >>> print(I & J)  # 交わり
    (I&J)=Set(value=[2, 4])
    >>> S = Set(value=[1,2], name='S')
    >>> print(S * S)  # 直積
    (S*S)=Set(value=[(1, 1), (1, 2), (2, 1), (2, 2)])
    >>> IJ = Set(value=[(1,3), (1,4), (2,3)], name='IJ')
    >>> print(IJ(0))  # 射影
    IJ(0)=Set(value=[1, 2])
    >>> ij = Element(set=IJ, name='ij')
    >>> Condition(ij, ij(1)==3)  # 選択
    (ij, (ij(1)==3))[ij] in [(1, 3), (2, 3)]
    

選択以外は専用の演算子やメソッドが用意されており,集合の演算として自然に記述できます. また,これらの演算は(疎な)多次元集合でも記述できます.

結合演算の中でも基本的な演算が,直積を一般化した等結合(equijoin)や自然結合(natural join)です. これらの他に頻出する有用な結合演算が,交わりと差を一般化した準結合(semijoin)と反結合(antijoin)です.

  • 等結合 \(\{(i,j_1,j_2,k) \mid j_1=j_2 \land (i,j_1)\in IJ \land (j_2,k)\in JK\}\)

  • 自然結合 \(IJ \bowtie_J JK=\{(i,j,k) \mid (i,j)\in IJ \land (j,k)\in JK\}\)

  • 準結合 \(IJK \ltimes_{I,K} IK=\{(i,j,k) \mid (i,j,k)\in IJK \land (i,k)\in IK\}\)

  • 反結合 \(IJK \rhd_{I,K} IK=\{(i,j,k) \mid (i,j,k)\in IJK \land (i,k)\notin IK\}\):

    >>> ij = Element(value=[(1,3), (1,4), (2,3)], name='ij')
    >>> jk = Element(value=[(3,5), (3,6), (4,5)], name='jk')
    >>> Condition((ij,jk), ij(1)==jk(0))  # 等結合
    ((ij,jk), (ij(1)==jk(0)))[ij,jk] in [(1,3,3,5), (1,3,3,6), (1,4,4,5), (2,3,3,5), (2,3,3,6)]
    >>> Condition((ij,jk(1)), ij(1)==jk(0))  # 自然結合
    ((ij,jk(1)), (ij(1)==jk(0)))[ij,jk(1)] in [(1,3,5), (1,3,6), (1,4,5), (2,3,5), (2,3,6)]
    >>>
    >>> ijk = Element(value=[(1,3,5), (1,3,6), (1,4,5), (2,3,5)], name='ijk')
    >>> ik = Element(value=[(1,6), (2,5)], name='ik')
    >>> Condition(ijk, ijk(0,2)<ik.set)  # 準結合
    (ijk, (ijk(0,2)<ik.set))[ijk] in [(1, 3, 6), (2, 3, 5)]
    >>> Condition(ijk, ijk(0,2)>ik.set)  # 反結合
    (ijk, (ijk(0,2)>ik.set))[ijk] in [(1, 3, 5), (1, 4, 5)]
    

結合は直積に対する条件付けとして定義されます.それぞれに専用の記法が用意されている訳ではありませんが, Condition によって述語を明示的に与えることで,結合を関係論理的に表現しています. このことは,先ほどの選択についても同様です.

最後に集約を見てみましょう.:

>>> ab = Element(value=[(1,1), (1,2), (1,4), (2,2), (2,3), (3,1), (3,5)], name='ab')
>>> Sum(ab(1))  # 合計
Sum(ab(1), ab(1))[1]=7
Sum(ab(1), ab(1))[2]=5
Sum(ab(1), ab(1))[3]=6
>>> Max(ab(1))  # 最大
Max(ab(1), ab(1))[1]=4
Max(ab(1), ab(1))[2]=3
Max(ab(1), ab(1))[3]=5
>>> Min(ab(1))  # 最小
Min(ab(1), ab(1))[1]=1
Min(ab(1), ab(1))[2]=2
Min(ab(1), ab(1))[3]=1
>>> Sum(1, ab(1))  # カウント
Sum(1, ab(1))[1]=3
Sum(1, ab(1))[2]=2
Sum(1, ab(1))[3]=2

平均は Sum(ab(1))/Sum(1, ab(1)) で計算できます.

データベースでは必須の関係代数ですが,PySIMPLE と異なる点として,データベースの関係は属性に 名前がついている一方,PySIMPLE の集合には次元ごとに名前はついていません. そのため,属性は次元で指定することになりますが,IJK など次元に対応したオブジェクト名を用意することで, 却って簡潔に記述することができます.