{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "⊕ [基本关系模式 - SQLAlchemy 1.3文档](https://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-01-19T15:37:14.050913Z",
     "start_time": "2019-01-19T15:37:13.882261Z"
    }
   },
   "outputs": [],
   "source": [
    "from sqlalchemy import Table, Column, Integer, ForeignKey\n",
    "from sqlalchemy.orm import relationship\n",
    "from sqlalchemy.ext.declarative import declarative_base\n",
    "\n",
    "Base = declarative_base()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 一对多\n",
    "一对多关系将外键放在引用父对象的子表上。 relationship()然后在父项上指定，作为引用子项表示的项集合：\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Parent(Base):\n",
    "    __tablename__ = 'parent'\n",
    "    id = Column(Integer, primary_key=True)\n",
    "    children = relationship(\"Child\")\n",
    "\n",
    "class Child(Base):\n",
    "    __tablename__ = 'child'\n",
    "    id = Column(Integer, primary_key=True)\n",
    "    parent_id = Column(Integer, ForeignKey('parent.id'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-01-19T15:37:17.215934Z",
     "start_time": "2019-01-19T15:37:17.198797Z"
    }
   },
   "outputs": [],
   "source": [
    "# 要在一对多中建立双向关系，其中“反向”侧是多对一，\n",
    "# 请指定一个附加relationship()并使用relationship.back_populates参数\n",
    "# 连接两者：\n",
    "\n",
    "class Parent(Base):\n",
    "    __tablename__ = 'parent'\n",
    "    id = Column(Integer, primary_key=True)\n",
    "    # children = relationship(\"Child\", back_populates=\"parent\")\n",
    "    children = relationship(\"Child\", backref=\"parent\")\n",
    "\n",
    "class Child(Base):\n",
    "    __tablename__ = 'child'\n",
    "    id = Column(Integer, primary_key=True)\n",
    "    parent_id = Column(Integer, ForeignKey('parent.id'))\n",
    "    parent = relationship(\"Parent\", back_populates=\"children\")"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-01-19T15:37:24.080996Z",
     "start_time": "2019-01-19T15:37:23.914932Z"
    }
   },
   "source": [
    "# Child将获得parent具有多对一语义的属性。\n",
    "# 或者，该backref选项可用于单个relationship()而不是使用 back_populates：\n",
    "\n",
    "class Parent(Base):\n",
    "    __tablename__ = 'parent'\n",
    "    id = Column(Integer, primary_key=True)\n",
    "    children = relationship(\"Child\", backref=\"parent\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-01-19T15:56:12.020464Z",
     "start_time": "2019-01-19T15:56:11.829275Z"
    }
   },
   "outputs": [],
   "source": [
    "from sqlalchemy import Integer, ForeignKey, String, Column\n",
    "from sqlalchemy.ext.declarative import declarative_base\n",
    "from sqlalchemy.orm import relationship\n",
    "\n",
    "Base = declarative_base()\n",
    "\n",
    "class User(Base):\n",
    "    __tablename__ = 'user'\n",
    "    id = Column(Integer, primary_key=True)\n",
    "    name = Column(String)\n",
    "\n",
    "    addresses = relationship(\"Address\", backref=\"user\")\n",
    "\n",
    "class Address(Base):\n",
    "    __tablename__ = 'address'\n",
    "    id = Column(Integer, primary_key=True)\n",
    "    email = Column(String)\n",
    "    user_id = Column(Integer, ForeignKey('user.id'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-01-19T16:00:26.282074Z",
     "start_time": "2019-01-19T16:00:26.275660Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "None\n"
     ]
    }
   ],
   "source": [
    "u1 = User()\n",
    "a1 = Address()\n",
    "u1.addresses\n",
    "print(a1.user)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-01-19T16:00:28.310172Z",
     "start_time": "2019-01-19T16:00:28.302466Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[<__main__.Address object at 0x10ca8ba58>]\n",
      "<__main__.User object at 0x10ca8be80>\n"
     ]
    }
   ],
   "source": [
    "# 一旦将Address其附加到u1.addresses集合，就会填充集合和标量属性：\n",
    "u1.addresses.append(a1)\n",
    "print(u1.addresses)\n",
    "print(a1.user)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-01-19T16:00:30.776699Z",
     "start_time": "2019-01-19T16:00:30.766632Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[]\n"
     ]
    }
   ],
   "source": [
    "# 这种行为也适用于移除操作，以及双方的等效操作。例如，当.user再次设置时None，该Address对象将从反向集合中删除：\n",
    "a1.user = None\n",
    "print(u1.addresses)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "⊕ [python - 如何构建具有复合主键的表的外键？](https://stackoverrun.com/cn/q/4139106)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-01-19T18:05:00.666590Z",
     "start_time": "2019-01-19T18:05:00.628746Z"
    }
   },
   "outputs": [],
   "source": [
    "import sqlalchemy as db\n",
    "from sqlalchemy import Table, Column, Integer, ForeignKey\n",
    "from sqlalchemy.orm import relationship\n",
    "from sqlalchemy.ext.declarative import declarative_base\n",
    "\n",
    "Base = declarative_base()\n",
    "class AreaCode(Base): \n",
    "    __tablename__ = 'areacodes' \n",
    "\n",
    "    area_code = db.Column(db.Integer, primary_key=True) \n",
    "\n",
    "\n",
    "class Exchange(Base): \n",
    "    __tablename__ = 'exchanges' \n",
    "\n",
    "    exchange = db.Column(db.Integer, primary_key=True) \n",
    "    area_code_pk = db.Column(db.Integer, db.ForeignKey('areacodes.area_code'), \n",
    "          primary_key=True) \n",
    "    area_code = relationship('AreaCode', backref='exchanges') \n",
    "\n",
    "\n",
    "class PhoneNumber(Base): \n",
    "    __tablename__ = 'phonenumbers' \n",
    "    __table_args__ = (\n",
    "     db.ForeignKeyConstraint(\n",
    "      ['exchange_exchange', 'exchange_area_code_pk'], \n",
    "      ['exchanges.exchange', 'exchanges.area_code_pk'], \n",
    "     ), \n",
    "    ) \n",
    "\n",
    "    phone_number = db.Column(db.Integer, primary_key=True) \n",
    "    exchange_exchange = db.Column(db.Integer, primary_key=True) \n",
    "    exchange_area_code_pk = db.Column(db.Integer, primary_key=True) \n",
    "    exchange = relationship('Exchange', backref='phone_numbers')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "⊕ [python - SQLAlchemy multiple foreign keys in one mapped class to the same primary key - Stack Overflow](https://stackoverflow.com/questions/22355890/sqlalchemy-multiple-foreign-keys-in-one-mapped-class-to-the-same-primary-key)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-01-20T09:44:38.870670Z",
     "start_time": "2019-01-20T09:44:38.772243Z"
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2019-01-20 17:44:38,813 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1\n",
      "2019-01-20 17:44:38,814 INFO sqlalchemy.engine.base.Engine ()\n",
      "2019-01-20 17:44:38,815 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1\n",
      "2019-01-20 17:44:38,817 INFO sqlalchemy.engine.base.Engine ()\n",
      "2019-01-20 17:44:38,819 INFO sqlalchemy.engine.base.Engine PRAGMA table_info(\"company\")\n",
      "2019-01-20 17:44:38,820 INFO sqlalchemy.engine.base.Engine ()\n",
      "2019-01-20 17:44:38,823 INFO sqlalchemy.engine.base.Engine PRAGMA table_info(\"stakeholder\")\n",
      "2019-01-20 17:44:38,824 INFO sqlalchemy.engine.base.Engine ()\n",
      "2019-01-20 17:44:38,825 INFO sqlalchemy.engine.base.Engine \n",
      "CREATE TABLE company (\n",
      "\tid INTEGER NOT NULL, \n",
      "\tname VARCHAR(50) NOT NULL, \n",
      "\tPRIMARY KEY (id)\n",
      ")\n",
      "\n",
      "\n",
      "2019-01-20 17:44:38,827 INFO sqlalchemy.engine.base.Engine ()\n",
      "2019-01-20 17:44:38,829 INFO sqlalchemy.engine.base.Engine COMMIT\n",
      "2019-01-20 17:44:38,831 INFO sqlalchemy.engine.base.Engine \n",
      "CREATE TABLE stakeholder (\n",
      "\tid INTEGER NOT NULL, \n",
      "\tcompany_id INTEGER NOT NULL, \n",
      "\tstakeholder_id INTEGER NOT NULL, \n",
      "\tPRIMARY KEY (id), \n",
      "\tFOREIGN KEY(company_id) REFERENCES company (id), \n",
      "\tFOREIGN KEY(stakeholder_id) REFERENCES company (id)\n",
      ")\n",
      "\n",
      "\n",
      "2019-01-20 17:44:38,833 INFO sqlalchemy.engine.base.Engine ()\n",
      "2019-01-20 17:44:38,835 INFO sqlalchemy.engine.base.Engine COMMIT\n",
      "2019-01-20 17:44:38,846 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)\n",
      "2019-01-20 17:44:38,853 INFO sqlalchemy.engine.base.Engine SELECT company.id AS company_id, company.name AS company_name \n",
      "FROM company\n",
      "2019-01-20 17:44:38,856 INFO sqlalchemy.engine.base.Engine ()\n",
      "2019-01-20 17:44:38,862 INFO sqlalchemy.engine.base.Engine SELECT stakeholder.id AS stakeholder_id_1, stakeholder.company_id AS stakeholder_company_id, stakeholder.stakeholder_id AS stakeholder_stakeholder_id \n",
      "FROM stakeholder\n",
      "2019-01-20 17:44:38,864 INFO sqlalchemy.engine.base.Engine ()\n",
      "[]\n"
     ]
    }
   ],
   "source": [
    "from sqlalchemy import create_engine, Column, Integer, String, ForeignKey\n",
    "from sqlalchemy.orm import relationship, scoped_session, sessionmaker\n",
    "from sqlalchemy.ext.declarative import declarative_base\n",
    "\n",
    "engine = create_engine(u'sqlite:///:memory:', echo=True)\n",
    "session = scoped_session(sessionmaker(bind=engine))\n",
    "Base = declarative_base()\n",
    "\n",
    "#The business case here is that a company can be a stakeholder in another company.\n",
    "class Company(Base):\n",
    "    __tablename__ = 'company'\n",
    "    id = Column(Integer, primary_key=True)\n",
    "    name = Column(String(50), nullable=False)\n",
    "\n",
    "class Stakeholder(Base):\n",
    "    __tablename__ = 'stakeholder'\n",
    "    id = Column(Integer, primary_key=True)\n",
    "    company_id = Column(Integer, ForeignKey('company.id'), nullable=False)\n",
    "    stakeholder_id = Column(Integer, ForeignKey('company.id'), nullable=False)\n",
    "    company = relationship(\"Company\", foreign_keys=[company_id])\n",
    "    stakeholder = relationship(\"Company\", foreign_keys=[stakeholder_id])\n",
    "\n",
    "Base.metadata.create_all(engine)\n",
    "\n",
    "# simple query test\n",
    "q1 = session.query(Company).all()\n",
    "q2 = session.query(Stakeholder).all()\n",
    "\n",
    "print(q1)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
