当前位置: 首页 > 软件库 > 大数据 > 数据查询 >

join-monster

授权协议 MIT License
开发语言 Java
所属分类 大数据、 数据查询
软件类型 开源软件
地区 不详
投 递 者 杜绍元
操作系统 跨平台
开源组织
适用人群 未知
 软件概览

join-monster Documentation Status

Query Planning and Batch Data Fetching between GraphQL and SQL.

What is Join Monster?

Efficient query planning and data fetching for SQL.Use JOINs and/or batched requests to retrieve all your data.It takes a GraphQL query and your schema and automatically generates the SQL.Send that SQL to your database and get back all the data needed to resolve with only one or a few round-trips to the database.

Translate a GraphQL query like this:

{
  user(id: 2) {
    fullName
    email
    posts {
      id
      body
      comments {
        body
        author { fullName }
      }
    }
  }
}

...into a couple SQL queries like this:

SELECT
  "user"."id" AS "id",
  "user"."email_address" AS "email_address",
  "posts"."id" AS "posts__id",
  "posts"."body" AS "posts__body",
  "user"."first_name" AS "first_name",
  "user"."last_name" AS "last_name"
FROM accounts AS "user"
LEFT JOIN posts AS "posts" ON "user".id = "posts".author_id
WHERE "user".id = 2

-- then get the right comments for each post
SELECT
  "comments"."id" AS "id",
  "comments"."body" AS "body",
  "author"."id" AS "author__id",
  "author"."first_name" AS "author__first_name",
  "author"."last_name" AS "author__last_name",
  "comments"."post_id" AS "post_id"
FROM comments AS "comments"
LEFT JOIN accounts AS "author" ON "comments".author_id = "author".id
WHERE "comments".archived = FALSE AND "comments"."post_id" IN (2,8,11,12) -- the post IDs from the previous query 

...and get back correctly hydrated data.

{
  "user": {
    "fullName": "Yasmine Rolfson",
    "email": "Earl.Koss41@yahoo.com",
    "posts": [
      {
        "id": 2,
        "body": "Harum unde maiores est quasi totam consequuntur. Necessitatibus doloribus ut totam dolore omnis quos error eos. Rem nostrum assumenda eius veniam fugit dicta in consequuntur. Ut porro dolorem aliquid qui magnam a.",
        "comments": [
          {
            "body": "The AI driver is down, program the multi-byte sensor so we can parse the SAS bandwidth!",
            "author": { "fullName": "Yasmine Rolfson" }
          },
          {
            "body": "Try to program the SMS transmitter, maybe it will synthesize the optical firewall!",
            "author": { "fullName": "Ole Barrows" }
          },
        ]
      },
      // other posts omitted for clarity...
    ]
  }
}

It works on top of Facebook's graphql-js reference implementation.All you have to do is add a few properties to the objects in your schema and call the joinMonster function.A SQL query is "compiled" for you to send to the DBMS.The data-fetching is efficiently batched.The data is then hydrated into the right shape for your GraphQL schema.

Why?

More details on the "round-trip" (a.k.a. N+1) problem are here.

  • Batching - Fetch all the data in a single, or a few, database query(s).
  • Efficient - No over-fetching data. Retrieve only the data that the client actually requested.
  • Maintainability - SQL is automatically generated and adaptive. No need to manually write queries or update them when the schema changes.
  • Declarative - Simply define the data requirements of the GraphQL fields on the SQL columns.
  • Unobtrusive - Coexists with your custom resolve functions and existing schemas. Use it on the whole graph or only in parts. Retain the power and expressiveness in defining your schema.
  • Object-relational impedance mismatch - Don't bother duplicating a bunch of object definitions in an ORM. Let GraphQL do your object mapping for you.

Since it works with the reference implementation, the API is all very familiar. Join Monster is a tool built on top to add batch data fetching. You add some special properties along-side the schema definition that Join Monster knows to look for. The use of graphql-js does not change. You still define your types the same way. You can write resolve functions to manipulate the data from Join Monster, or incorporate data from elsewhere without breaking out of your "join-monsterized" schema.

Get Pagination out of the Box

Join Monster has support for several different implementations of pagination, all based on the interface in the Relay Connection Specification. Using Relay on the client is totally optional!

Works with the RelayJS

Great helpers for the Node Interface and automatic pagination for Connection Types. See docs.

Usage with GraphQL

$ npm install join-monster
  1. Take your GraphQLObjectType from graphql-js and add the SQL table name.
  2. Do the fields need values from some SQL columns? Computed columns? Add some additional properties like sqlColumn, sqlDeps, or sqlExpr to the fields. Join Monster will look at these when analyzing the query.
  3. Got some relations? Write a function that tells Join Monster how to JOIN your tables and it will hydrate hierarchies of data.
  4. Resolve any type (and all its descendants) by calling joinMonster in its resolver. All it needs is the resolveInfo and a callback to send the (one) SQL query to the database. Voila! All your data is returned to the resolver.
import joinMonster from 'join-monster'
import {
  GraphQLObjectType,
  GraphQLList,
  GraphQLString,
  GraphQLInt
  // and some other stuff
} from 'graphql'

const User = new GraphQLObjectType({
  name: 'User',
  sqlTable: 'accounts', // the SQL table for this object type is called "accounts"
  uniqueKey: 'id', // the id in each row is unique for this table
  fields: () => ({
    id: {
      // the column name is assumed to be the same as the field name
      type: GraphQLInt
    },
    email: {
      type: GraphQLString,
      // if the column name is different, it must be specified specified
      sqlColumn: 'email_address'
    },
    idEncoded: {
      description: 'The ID base-64 encoded',
      type: GraphQLString,
      // this field uses a sqlColumn and applies a resolver function on the value
      // if a resolver is present, the `sqlColumn` MUST be specified even if it is the same name as the field
      sqlColumn: 'id',
      resolve: user => toBase64(user.idEncoded)
    },
    fullName: {
      description: "A user's first and last name",
      type: GraphQLString,
      // perhaps there is no 1-to-1 mapping of field to column
      // this field depends on multiple columns
      sqlDeps: [ 'first_name', 'last_name' ],
      // compute the value with a resolver
      resolve: user => `${user.first_name} ${user.last_name}`
    },
    capitalizedLastName: {
      type: GraphQLString,
      // do a computed column in SQL with raw expression
      sqlExpr: (table, args) => `UPPER(${table}.last_name)`
    },
    // got tables inside tables??
    // get it with a JOIN!
    posts: {
      description: "A List of posts this user has written.",
      type: new GraphQLList(Post),
      // a function to generate the join condition from the table aliases
      sqlJoin(userTable, postTable) {
        return `${userTable}.id = ${postTable}.author_id`
      }
    },
    // got a relationship but don't want to add another JOIN?
    // get this in a second batch request
    comments: {
      description: "The comment they have written",
      type: new GraphQLList(Comment),
      // specify which columns to match up the values
      sqlBatch: {
        thisKey: 'author_id',
        parentKey: 'id'
      }
    },
    // many-to-many relations are supported too
    following: {
      description: "Other users that this user is following.",
      type: new GraphQLList(User),
      // name the table that holds the two foreign keys
      junction: {
        sqlTable: 'relationships',
        sqlJoins: [
          // first the parent table to the junction
          (followerTable, junctionTable, args) => `${followerTable}.id = ${junctionTable}.follower_id`,
          // then the junction to the child
          (junctionTable, followeeTable, args) => `${junctionTable}.followee_id = ${followeeTable}.id`
        ]
      }
    },
    numLegs: {
      description: 'Number of legs this user has.',
      type: GraphQLInt,
      // data isn't coming from the SQL table? no problem! joinMonster will ignore this field
      resolve: () => 2
    }
  })
})

const Comment = new GraphQLObjectType({
  name: 'Comment',
  sqlTable: 'comments',
  uniqueKey: 'id',
  fields: () => ({
    // id and body column names are the same
    id: {
      type: GraphQLInt
    },
    body: {
      type: GraphQLString
    }
  })
})

const Post = new GraphQLObjectType({
  name: 'Post',
  sqlTable: 'posts',
  uniqueKey: 'id',
  fields: () => ({
    id: {
      type: GraphQLInt
    },
    body: {
      type: GraphQLString
    }
  })
})

export const QueryRoot = new GraphQLObjectType({
  name: 'Query',
  fields: () => ({
    // place this user type in the schema
    user: {
      type: User,
      // let client search for users by `id`
      args: {
        id: { type: GraphQLInt }
      },
      // how to write the WHERE condition
      where: (usersTable, args, context) => {
        if (args.id) return `${usersTable}.id = ${args.id}`
      },
      resolve: (parent, args, context, resolveInfo) => {
        // resolve the user and the comments and any other descendants in a single request and return the data!
        // all you need to pass is the `resolveInfo` and a callback for querying the database
        return joinMonster(resolveInfo, {}, sql => {
          // knex is a query library for SQL databases
          return knex.raw(sql)
        })
      }
    }
  })
})

Detailed instructions for set up are found in the docs.

Using with graphql-tools

The GraphQL schema language doesn't let you add arbitrary properties to the type definitions. If you're using something like the Apollo graphql-tools package to write your code with the schema language, you'll need an adapter. See the join-monster-graphql-tools-adapter if you want to use this with graphql-tools.

Running the Demo

$ git clone https://github.com/stems/join-monster-demo.git
$ cd join-monster-demo
$ npm install
$ npm start
# go to http://localhost:3000/graphql

# if you also want to run the paginated version, create postgres database from the dump provided
psql $YOUR_DATABASE < data/paginated-demo-dump.sql
DATABASE_URL=postgres://$USER:$PASS@$HOST/$YOUR_DATABASE npm start
# go to http://localhost:3000/graphql-relay

Explore the schema, try out some queries, and see what the resulting SQL queries and responses look like in our custom version of GraphiQL!

graphsiql

There's still a lot of work to do. Please feel free to fork and submit a Pull Request!

Future Work

  • Address this known bug #126.
  • Support custom ORDER BY expressions #138.
  • Support binding parameters #169.
  • Write static Flow types.
  • Support "lookup tables" where a column might be an enum code to look up in another table.
  • Support "hyperjunctions" where many-to-many relations can join through multiple junction tables.
  • Cover more SQL dialects, like MSSQL and DB2.
  •  关于inner join 与 left join 之间的区别,以前以为自己搞懂了,今天从前端取参数的时候发现不是预想中的结果,才知道问题出在inner join 上了。 需求是从数据库查数据,在前端以柱形图的形式展现出来,查到的数据按行业分组,显示每个行业的户数及户数占比,涉及到的字段有A表的用户数、总用户数和B表的行业名称。本来是不管查不查的到数据,在X轴都应该显示行业名称的,结果是X、Y轴都

  • 内连接 内连接(INNER JOIN)主要通过设置连接条件的方式,来移除查询结果中某些数据行的交叉连接。简单来说,就是利用条件表达式来消除交叉连接的某些数据行。 内连接使用 INNER JOIN 关键字连接两张表,并使用 ON 子句来设置连接条件。如果没有连接条件,INNER JOIN 和 CROSS JOIN 在语法上是等同的,两者可以互换。 内连接的语法格式如下: SELECT <字段名> F

  • For Example: Table A have 12( 8+4) entries, 8 entries have valid relation with B Table B have 80(77+3) entries , 77 entries have valid relation with A. then the return amount of join is : cross join :

  • join和join in有什么区别么?怎样使用他们?请详细说明,谢谢 join和join in的区别为:意思不同、用法不同、侧重点不同。 一、意思不同 1、join:成为…的一员,参加。 2、join in:加入,参加(活动)。 二、用法不同 1、join:join作“参加”解时,其含义是以非发起人和非主办人的身份加入到业已存在的组织(如军队、党团、社团协会等)或正在进行的某种集体活动(如游戏、比

  • String.Join 在指定 String 数组的每个元素之间串联指定的分隔符 String,从而产生单个串联的字符串。 有两个重载函数: public static string Join( string separator, string[] value ); public static string Join( string separator, string[]

  • 1、left join(左链接)       作用:以左表为主,查询左表的全部数据,右表展示与左表相关联的部分,也就是有交集的部分。 select * from staff s left join company c on s.company_id = c.company_id; 2、right join (右链接)       作用:以右表为主,查询右表的全部数据,左表展示与右表相关联的部分,有

  • mongodb提供ref和populate的方法,支持类似join的SQL操作。本文给出一个实际的例子: 1. 数据1: var daob = new Schema({ user: { type: String }, title: { type: String }, tag: [{ type: String

  •   SQL语句的并集UNION,交集JOIN(内连接,外连接),交叉汇总 2010-07-28 09:05:23 1. a. 并集UNION SELECT column1, column2 FROM table1 UNION SELECT column1, column2 FROM table2   b. 交集JOIN SELECT * FROM table1 AS a JOIN table2 b

  • 概述: 将序列中的元素以指定的字符连接生成一个新的字符串。 语法: ‘delimiter’.join(seq) delimiter:分隔符。可以为空 delimiter:要连接的元素序列、字符串、元组、字典 返回通过指定字符连接序列中元素后生成的新字符串。 上代码: while True:     try:         print(''.join(sorted(input())))     e

  • quoted from: https://docs.oracle.com/en/database/oracle/oracle-database/12.2/tgsql/joins.html#GUID-8E7760A6-48D6-4794-BF2F-290349C019B9 A join type is determined by the type of join condition. This se

  • JOIN 表运算符对两个输入表进行操作。联接的类型有交叉联接、内部联接和外部联接,它们的区别在于如何应用逻辑查询处理阶段。交叉联接仅应用一个阶段——笛卡尔乘积,内部联接应用两个阶段——笛卡尔乘积和筛选,外部联接应用三个阶段——笛卡尔乘积、筛选和添加外部行。 交叉联接(CROSS JOIN) 交叉联接仅执行一个逻辑查询处理阶段——笛卡尔乘积,这一阶段对提供的两个输入表进行操作,联接并生成两个表的笛卡

  • Inner join:内连接,也叫等值连接,inner join产生同时符合A和B的一组数据。 Cross join:交叉连接,得到的结果是两个表的乘积,即笛卡尔积 笛卡尔(Descartes)乘积又叫直积。假设集合A={a,b},集合B={0,1,2},则两个集合的笛卡尔积为{(a,0),(a,1),(a,2),(b,0),(b,1), (b,2)}。可以扩展到多个集合的情况。类似的例子有,如果

  • join()方法:将数组所有元素拼接成一个数组,相当于java的toString方法 默认逗号连接 var joinArr = ["hello","world"]; var joinArr1 = joinArr.join(); // joinArr1: "hello,world" var joinArr2 = joinArr.join(""); // joinArr2: "helloworld

  • join(separator) join是数组中的方法,将数组中的每个元素用分隔符连接起来并转换为字符串。即将数组元素连接成字符串。返回的是字符串哈,不在是数组了。 let arr = [1,2,3,4,5] let str = arr.join('aa') console.log(str); // 输出:1aa2aa3aa4aa5 console.log(typeof str); // 输

  • JOIN:表示连接组合两个表中的列字段记录,一共有三种用法 第一种:内连接,即单纯的使用join ... on select d.dname,e.ename,e.sal from emp e join dept d on e.deptno=d.deptno; 第二种:左连接,left join ... on(会检查左边表的数据是否都包含在新生成的表中,若是,则和join一样;若不是,则用NULL和

 相关资料
  • join 方法 把数组的元素放入一个字符串,元素通过指定的分隔符进行分隔。 语法: arrayObject.join( separator ); 参数说明: separator - 可选。指定要使用的分隔符。如果省略该参数,则使用逗号作为分隔符。 返回值: 返回一个字符串。该字符串是通过把 arrayObject 的每个元素转换为字符串,然后把这些字符串连接起来,在两个元素之间插入 sepa

  • 描述 (Description) Javascript数组join()方法将数组的所有元素连接成一个字符串。 语法 (Syntax) 其语法如下 - array.join(separator); 参数细节 (Parameter Details) separator - 指定用于分隔数组的每个元素的字符串。 如果省略,则使用逗号分隔数组元素。 返回值 (Return Value) 连接所有数组元素

  • 描述 (Description) 此函数使用EXPR的值将LIST的元素组合成单个字符串,以分隔每个元素。 它实际上与分裂相反。 注意,EXPR仅在LIST中的元素对之间进行插值; 它不会放在字符串中的第一个元素之前或之后。 要将没有分隔符的字符串连接在一起,请提供空字符串而不是undef。 语法 (Syntax) 以下是此函数的简单语法 - join EXPR, LIST 返回值 (Retur

  • 一个Function,它返回一个字符串,该字符串包含数组中指定数量的子字符串。 这与Split Method完全相反。 语法 (Syntax) Join(List[,delimiter]) 参数描述 (Parameter Description) List - 必需参数。 包含要连接的子字符串的数组。 Delimiter - 可选参数。 该字符,在返回字符串时用作分隔符。 默认分隔符是Spac

  • join()方法将数组的所有元素连接成一个字符串。 语法 (Syntax) array.join(separator); 参数 (Parameters) separator - 指定用于分隔数组的每个元素的字符串。 如果省略,则使用逗号分隔数组元素。 返回值 (Return Value) 连接所有数组元素后返回一个字符串。 例子 (Example) var arr = new Array(

  • 此方法返回一个字符串,其中各个字符由指定的分隔符连接。 import numpy as np print np.char.join(':','dmy') print np.char.join([':','-'],['dmy','ymd']) 其输出如下 - d:m:y ['d:m:y' 'y-m-d']