{"id":22071266,"url":"https://github.com/lancejpollard/rel.js","last_synced_at":"2025-10-06T11:09:57.578Z","repository":{"id":66858552,"uuid":"450896461","full_name":"lancejpollard/rel.js","owner":"lancejpollard","description":"Relational Algebra Data Models Experiment in JavaScript","archived":false,"fork":false,"pushed_at":"2022-01-24T05:28:32.000Z","size":25,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"make","last_synced_at":"2025-07-01T04:44:11.446Z","etag":null,"topics":["json","relational-algebra","sql"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lancejpollard.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-01-22T18:04:45.000Z","updated_at":"2022-01-24T05:28:35.000Z","dependencies_parsed_at":"2023-04-07T04:15:53.448Z","dependency_job_id":null,"html_url":"https://github.com/lancejpollard/rel.js","commit_stats":null,"previous_names":["haresurf/rel.js","termhare/rel.js","lancejpollard/rel.js"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/lancejpollard/rel.js","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lancejpollard%2Frel.js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lancejpollard%2Frel.js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lancejpollard%2Frel.js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lancejpollard%2Frel.js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lancejpollard","download_url":"https://codeload.github.com/lancejpollard/rel.js/tar.gz/refs/heads/make","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lancejpollard%2Frel.js/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278598620,"owners_count":26013291,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-06T02:00:05.630Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["json","relational-algebra","sql"],"created_at":"2024-11-30T20:29:43.993Z","updated_at":"2025-10-06T11:09:57.549Z","avatar_url":"https://github.com/lancejpollard.png","language":null,"readme":"\n# Basic Relational Algebra Structure in JavaScript\n\nBasically there are two things that need to happen here. First, we need to have a data structure which can represent a relational algebra \"tree\". Second is we need to create a DSL which makes it more intuitive for writing a query, basically like what the SQL standard is. But we don't want to use SQL because there are better ways of specifying queries, such as like GraphQL, or others.\n\nThe first piece is important (defining a relational algebra model), because it tells us which steps/actions we need to perform right out of the box. We can then take this relational algebra tree and start directly performing the pieces/steps of the overall query.\n\nThe second piece is important, because it should be easy to read and write, and also easy to serialize into JSON, and then transform into the relational algebra form. Ideally you shouldn't need to know exactly how to write the relational algebra form, but the system would take your query in a nice DSL JSON object, and convert it to the most optimal relational algebra form. However, I am not sure exactly how that would be done, that is in the realm of query planning and optimization, which also needs to take into account the physical layout of your data and do cost estimates to find a good relational algebra tree to end up with. That is for far future, when we can eventually figure it out.\n\nIn principle, you would from the browser create a DSL query, and send that to the backend. Then the backend would convert that query to a basic relational algebra tree. It would then somehow know about the state of all your shards and their properties, so it could optimize the relational algebra tree. Then it would evaluate the relational algebra tree basically, going step by step, requesting from various shards what it needs, and then sending results back to the app. So the browser would only care about the DSL, but the backend would also care about the relational algebra. The frontend could potentially also use the relational algebra if it wanted to perform an in-memory query like a real database would, as well.\n\nAnother thing I'd like to potentially do here is have the cost evaluator for the query. So it would take JSON describing the state of your database system (RAM each machine has, table sizes, average record sizes, IO times, etc.) and it would use this information to figure out the optimal query plan.\n\nSo basically:\n\n1. query JSON structure to use in apps.\n2. relational algebra tree to use for making queries.\n3. transformer from query to relational algebra tree.\n4. cost estimator, given JSON description of system.\n5. query optimizer / simplifier, so it can make roughly optimal queries.\n\nIt won't actually perform the queries, that will be saved for somewhere else.\n\n## DSL\n\nTaking into account SQL and GraphQL (and other graph query languages like Gremlin), this section should ultimately land upon a robust query DSL using JSON-like syntax. It should literally be easiest to write in JavaScript, using JSON objects so it serializes nicely, not some alternative made-up syntax which we would then have to parse.\n\n- SQL is _declarative_ (what)\n- Relational algebra is _procedual_ (how)\n\n## Relational Algebra Data Structure\n\nThis is still being figured out, but here is a rough approximation currently.\n\nSome notes on [Relational Algebra math notation](https://github.com/lancejpollard/math-notation/blob/make/book.ipynb). Also, try seeing what relational algebra diagrams would look like by [converting SQL to relational algebra here](http://www.grammaticalframework.org/qconv/qconv-a.html).\n\n### Not\n\n```js\n{\n  type: 'not',\n  base: path,\n  expression: condition\n}\n```\n\n### Attribute Comparison\n\n```js\n{\n  type: 'gt',\n  base: path,\n  operation: string,\n  head: path\n}\n```\n\n- GT\n- LT\n- GTE\n- LTE\n- EQ\n\n### Logical Combination\n\n#### And Combination\n\n```js\n{\n  type: 'and',\n  expression: [...condition]\n}\n```\n\n#### Or Combination\n\n```js\n{\n  type: 'or',\n  expression: [...condition]\n}\n```\n\n### Selection\n\n```js\n{\n  type: 'selection',\n  condition: [...],\n  table: 'name'\n}\n```\n\n### Projection\n\n```js\n{\n  field: [name, ...],\n  selection: [selection]\n}\n```\n\n_Note: Extended (Generalized) Projection allows for functions like count and min/max involving arithmetic expressions to be projected. So basically, you can do functions here too._\n\n### Renaming\n\n```js\n{\n  type: 'renaming',\n  mapping: [...relationRenamingMapping]\n}\n```\n\n```js\n{\n  type: 'relation-renaming-mapping',\n  base: string,\n  head: string,\n  attribute: [...attributeRenamingMapping]\n}\n```\n\n```js\n{\n  type: 'attribute-renaming-mapping',\n  base: string,\n  head: string,\n}\n```\n\n### Natural Join\n\n```js\n{\n  type: 'natural-join',\n  base: relation,\n  head: relation,\n}\n```\n\n### Theta Join\n\n```js\n{\n  type: 'theta-join',\n  base: relation,\n  condition: condition,\n  head: relation,\n}\n```\n\n### Semi Join\n\n1. Compute natural join of R and S.\n2. Output the projection of that on just the attributes of R.\n\n```js\n{\n  type: 'semi-join',\n  base: relation,\n  attribute: [...attribute],\n  head: relation,\n}\n```\n\n### Outer Join\n\n### Union\n\n### Set Difference\n\n### Cross Product\n\n### Intersection\n\n### Division\n\n### Send Operator\n\n### Receive Operator\n\n### Temp Operator\n\n### Scan Operator\n\n## SQL to Relational Algebra Examples\n\nPerhaps could try [this tool](https://dbis-uibk.github.io/relax/landing) as well.\n\n### Left Outer Join\n\nLeft outer join (also called just \"left join\") returns all the rows of the leftmost table and the matching rows for the rightmost table.\n\n```sql\nSELECT b.id, b.title, b.type, a.last_name AS author, t.last_name AS translator\nFROM books b\nLEFT OUTER JOIN authors a\nON b.author_id = a.id\nLEFT OUTER JOIN translators t\nON b.translator_id = t.id\nORDER BY b.id\n```\n\n```\nτ b.id\n π b.id, b.title, b.type, a.last_name → author, t.last_name → translator\n  (ρ b books ⋈oL b.author_id = a.id\n   ρ a authors ⋈oL b.translator_id = t.id\n    ρ t translators)\n```\n\n![https://imgur.com/tc7lOxu.png](https://imgur.com/tc7lOxu.png)\n\n### Right Outer Join\n\nRight join (also called just \"right join\") returns all the rows of the rightmost table of and the matching rows for the leftmost table.\n\n```sql\nSELECT b.id, b.title, e.last_name AS editor\nFROM books b\nRIGHT OUTER JOIN editors e\nON b.editor_id = e.id\nORDER BY b.id\n```\n\n```\nτ b.id\n π b.id, b.title, e.last_name → editor\n  (ρ b books ⋈oR b.editor_id = e.id\n   ρ e editors)\n```\n\n![https://imgur.com/vsrWMcm.png](https://imgur.com/vsrWMcm.png)\n\n### Full Outer Join\n\nFull join returns all records when there is a match in either the left table or right table.\n\n```sql\nSELECT b.id, b.title, a.last_name AS author, e.last_name AS editor, t.last_name AS translator\nFROM books b\nFULL OUTER JOIN authors a\nON b.author_id = a.id\nFULL OUTER JOIN editors e\nON b.editor_id = e.id\nFULL OUTER JOIN translators t\nON b.translator_id = t.id\nORDER BY b.id\n```\n\n```\nτ b.id\n π b.id, b.title, a.last_name → author, e.last_name → editor, t.last_name → translator\n  (ρ b books ⋈o b.author_id = a.id\n   ρ a authors ⋈o b.editor_id = e.id\n    ρ e editors ⋈o b.translator_id = t.id\n     ρ t translators)\n```\n\n![https://imgur.com/wzvswte.png](https://imgur.com/wzvswte.png)\n\n### Inner Join\n\nInner join returns dataset that have matching values in both tables.\n\n```sql\nSELECT b.id, b.title, a.first_name, a.last_name\nFROM books b\nINNER JOIN authors a\nON b.author_id = a.id\nORDER BY b.id\n```\n\n```\nτ b.id\n π b.id, b.title, a.first_name, a.last_name\n  (ρ b books ⋈ b.author_id = a.id\n   ρ a authors)\n```\n\n[![https://imgur.com/xrKeK6x.png](https://imgur.com/xrKeK6x.png)](https://learnsql.com/blog/sql-join-examples-with-explanations/)\n\n### Join\n\n```sql\nSELECT b.id, b.title, b.type, t.last_name AS translator\nFROM books b\nJOIN translators t\nON b.translator_id = t.id\nORDER BY b.id\n```\n\n```\nτ b.id\n π b.id, b.title, b.type, t.last_name → translator\n  (ρ b books ⋈ b.translator_id = t.id\n   ρ t translators)\n```\n\n![https://imgur.com/bwfKEF8.png](https://imgur.com/bwfKEF8.png)\n\n### Group By\n\n```sql\nSELECT agents.agent_code, agents.agent_name, SUM(orders.advance_amount)\nFROM agents, orders\nWHERE agents.agent_code = orders.agent_code\nGROUP BY agents.agent_code, agents.agent_name\nORDER BY agents.agent_code\n```\n\n```\nτ agents.agent_code\n γ agent_code, agent_name, SUM(advance_amount)\n  σ agents.agent_code = orders.agent_code (agents × orders)\n```\n\n![https://imgur.com/My9iV3D.png](https://imgur.com/My9iV3D.png)\n\n### Count + Nested Select + Union + Join\n\n```sql\nSELECT name, COUNT(*)\nFROM\n (SELECT name\n  FROM videos_games\n  INNER JOIN game_tags\n  ON game_tags.game_id = videos_games.id\n  WHERE game_tags.id IN(10, 3)\n  UNION ALL\n  SELECT name\n  FROM videos_games\n  INNER JOIN games_genres\n  ON games_genres.game_id = videos_games.id\n  WHERE games_genres.id IN(17, 22)) AS nameslist\nGROUP BY NAME\nORDER BY COUNT(*) DESC\n```\n\n```\nτ COUNT (*) ↓\n γ name, COUNT (*)\n  ρ nameslist\n   (π name\n    σ game_tags.id = 10 OR game_tags.id = 3 (videos_games ⋈ game_tags.game_id = videos_games.id game_tags) ∪\n     π name\n      σ games_genres.id = 17 OR games_genres.id = 22 (videos_games ⋈ games_genres.game_id = videos_games.id games_genres))\n```\n\n![https://imgur.com/ZlL9nMS.png](https://imgur.com/ZlL9nMS.png)\n\n### Intersect\n\n```sql\nSELECT supplier_id\nFROM suppliers\nWHERE supplier_id \u003e 78\nINTERSECT\nSELECT supplier_id\nFROM orders\nWHERE quantity \u003c\u003e 0\n```\n\n```\nπ supplier_id\n σ supplier_id \u003e 78 suppliers ∩\n  π supplier_id\n   σ quantity \u003c\u003e 0 orders\n```\n\n![https://imgur.com/lL0mX6E.png](https://imgur.com/lL0mX6E.png)\n\n### Group by and Having\n\n```sql\nSELECT age, SUM(salary) FROM people\nGROUP BY age\nHAVING age = 30\n```\n\n```\nσ age = 30\n γ age, SUM(salary) people\n```\n\n![https://imgur.com/SHF81sT.png](https://imgur.com/SHF81sT.png)\n\n### Distinct\n\n```sql\nSELECT id, COUNT(DISTINCT(val)), COUNT(DISTINCT(found))\nFROM (SELECT id, val, found FROM TEST_DATA) TMP\nGROUP BY id\n```\n\n```\nγ id, COUNT(\\delta val), COUNT(\\delta found)\n ρ tmp\n  π id, val, found test_data\n```\n\n![https://imgur.com/Q1dpT8M.png](https://imgur.com/Q1dpT8M.png)\n\n## Joins in More Detail\n\n- https://dataschool.com/how-to-teach-people-sql/sql-join-types-explained-visually/\n- https://dataschool.com/how-to-teach-people-sql/full-outer-join-animated/\n- https://www.educative.io/blog/what-are-sql-joins\n\n## Notes\n\nTwo relational algebra expressions are said to be\nequivalent if on every legal database instance the\ntwo expressions generate the same set of tuples.\n\n- for CNF, convert to negation normal form with De Morgan laws then distribute OR over AND\n- for DNF, convert to negation normal form with De Morgan laws then distribute AND over OR\n\nYou only need select, project, and rename. The joins can be defined in terms of those.\n\nAggregation function takes a collection of values and\nreturns a single value as a result.\n\n- avg: average value\n- min: minimum value\n- max: maximum value\n- sum: sum of values\n- count: number of values\n\nPure relational algebra removes all duplicates, e.g. after projection.\n_Multiset_ relational algebra retains duplicates, to match SQL\nsemantics.\n\nThanks to [this nice presentation](http://itu.dk/~mogel/SIDD2012/lectures/SIDD.2012.05.pdf) from _Rasmus Ejlers Møgelberg_, we have a mapping from SQL statements to relational algebra.\n\n### Select\n\n```\nselect name, salary from instructor\n```\n```\nΠ{name, salary}(instructor)\n```\n\n```\nselect * from instructor where salary \u003e 90000;\n```\n```\nσ{salary\u003e90000}(instructor)\n```\n\n```\nselect name, dept_name from instructor\nwhere salary \u003e 90000;\n```\n\n```\nΠ{name, dept_name}(σ{salary\u003e90000}(instructor))\n```\n\nRelational algebra expression says:\n\n- First do selection\n- Then do projection\n\n(i.e., start from leaves and work your way back to the base).\n\n```\n-- cartesian product\nselect * from instructor, department;\n```\n\n```\ninstructor × department\n```\n\n```\nselect * from student join advisor on s_ID = ID;\n```\n\n```\nstudent ⋈{(ID=s ID)} advisor\n```\n\n```\nselect avg(salary), dept_name from instructor\ngroup by dept_name;\n```\n\n```\n{dept_name}G{average}(salary)\n```\n\nr ⋈ s is defined as:\n```\n∏{r.A, r.B, r.C, r.D, s.E}(σ{r.B = s.B ∧ r.D = s.D}(r x s))\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flancejpollard%2Frel.js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flancejpollard%2Frel.js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flancejpollard%2Frel.js/lists"}