Как составлять запросы в Neo4j?

Я хочу определить свои запросы Neo4j с точки зрения общей структуры (во многом похожей на GraphQL) и составить их. Например, вот простой запрос без композиции:

chatrooms: {
  query: {limit: 10}
  fields: {
    id: true,
    name: true }}

Мой интерпретатор Neo4j может выглядеть так:

chatrooms = ({query, fields}) ->
  """
    MATCH (c:CHATROOMS)
    RETURN #{R.join(' ', R.map(R.concat('c.'), R.keys(fields)))}
    LIMIT #{query.limit}
  """

Но у меня возникают проблемы, когда я хочу составить более глубоко вложенные запросы. Например, что, если мне нужна информация о владельце каждого чата? Вместо того, чтобы тратить два HTTP-запроса туда и обратно, я должен иметь возможность запрашивать все это одновременно.

chatrooms: {
  query: {limit: 10}
  fields: {
    id: true,
    name: true
    owner: {
      fields: {
        id: true,
        name: true}}}}

В этот момент я немного зацикливаюсь. Я не знаю, как сделать интерпретатор достаточно универсальным для обработки этих случаев. Я знаю, что запрос должен выглядеть примерно так.

MATCH (c:CHATROOM)
MATCH (c)<-[:OWNS]-(u:USER)
RETURN c.id, c.name, u.id, u.name
LIMIT 10

В идеале этот запрос должен возвращать что-то с похожей структурой:

[
  {id: 1, name:'neo4j', owner: {id: 99, name: 'michael'}}
  {id: 2, name:'meteor', owner: {id: 100, name: 'chet'}}
]

Это значительно облегчило бы составление и интерпретацию результатов, но об этом позже.

Наконец, у меня возникают проблемы с вложением этих запросов еще глубже. Например, что, если мне нужны сообщения для каждого чата? А что, если мне нужна информация о владельце каждого чата?

chatrooms: {
  query: {limit: 10}
  fields: {
    id: true,
    name: true
    owner: {
      fields: {
        id: true,
        name: true}}
    messages: {
      query: {limit: 20}
      fields: {
        id: true,
        text: true,
        createdAt: true,
        owner: {
          fields: {
            id: true,
            name: true }}}}}}

Вот мой вариант, хотя я совершенно уверен, что это совершенно неправильно.

MATCH (c:CHATROOM)
MATCH (c)<-[:OWNS]-(u:USER)
UNWIND c as room
  MATCH (room)-[:OWNS]->(m:MESSAGE)
  MATCH (m)<-[:OWNS]-(x:USER)
  RETURN collect(m.id, m.text, m.creatdAt, x.id, x.name)
  LIMIT 20
RETURN c.id, c.name, u.id, u.name, 
LIMIT 10

В любом случае, цель состоит в том, чтобы иметь возможность указать один гигантский вложенный запрос и запустить его все в одном HTTP-запросе. Возможно, потребуется некоторый анализ вывода, но в идеале я также получу сопоставимую структуру данных в качестве вывода.

Любые идеи?


person Chet    schedule 12.11.2015    source источник


Ответы (2)


Во-первых, наличие вложенных структур противоречит философии GraphQL.

Вместо этого вы должны использовать ссылки.

e.g.

chatrooms: {
  query: {limit: 10}
  fields: {
    id: true,
    name: true
    owner: ${owner.id}
  messages: {
    query: {limit: 20}
    fields: {
      id: true,
      text: true,
      createdAt: true,
      owner: ${owner.id}
    }
  }
} 

owners: [{id: 1, fields: { id: true, name: true}}]

Почему вы используете свойство fields вместо полей в корневом объекте?

person MicTech    schedule 12.11.2015
comment
Я хочу, чтобы поля были рекурсивно вложены. Поле владельца зависит от контекста запроса. Мне нужен владелец каждого чата и владелец каждого сообщения. И я не знаю идентификаторы владельцев, идентификаторы сообщений или идентификаторы чата заранее, потому что я хочу сделать все это в одном запросе. Я собираюсь использовать структуру запроса {domain: {query, fields: {domain, ...}}}. - person Chet; 12.11.2015

Вы можете использовать последовательность и комбинацию collect({key:value}) или {key:collect(value).

MATCH (c:CHATROOM)<-[:OWNS]-(u:USER)
RETURN {id: c.id, name: c.name, owner: collect({id: u.id, name: u.name})} AS data
LIMIT 10

то с этими данными:

create (c:CHATROOM {id:1, name:"neo4j"})<-[:OWNS]-(u:USER {id:99,name:"Michael") 
create (c:CHATROOM {id:2, name:"meteor"})<-[:OWNS]-(u:USER {id:100,name:"Chet")

запустив полученный запрос

+--------------------------------------------------------------------+
| data                                                               |
+--------------------------------------------------------------------+
| {id=2, name=meteor, owner=[{id=100, name=Chet}]}                   |
| {id=1, name=neo4j, owner=[{id=99, name=Michael}]}                  |
+--------------------------------------------------------------------+

жить здесь: http://console.neo4j.org/r/d41luc

Чтобы объединить обе строки в одну, вы должны использовать WITH, а затем RETURN, но тогда вы не получите преимущества обратного потока данных.

MATCH (c:CHATROOM)<-[:OWNS]-(u:USER)
WITH {id: c.id, name: c.name, owner: collect({id: u.id, name: u.name})} AS row
RETURN collect(row) as data
LIMIT 10

Обновлять

Пожалуйста, используйте более описательные типы отношений вместо :OWNS везде.

MATCH (c:CHATROOM)<-[:OWNS]-(u:USER)
MATCH (c)-[:OWNS]->(m:MESSAGE)<-[:OWNS]-(x:USER)
WITH u,c,collect({id: m.id, text: m.text, created_at: m.createdAt, author_id: x.id, author: x.name}) as messages[0..20]
RETURN collect({ id: c.id, room: c.name, 
                 owner_id: u.id, owner: u.name, 
                 messages: messages}) as rooms
LIMIT 10
person Michael Hunger    schedule 12.11.2015
comment
1) Что вы подразумеваете под потоковой передачей ваших данных обратно? Вы говорите об использовании java API или остальных API? 2) есть идеи, как получить вложенные сообщения, а также все в одном запросе? - person Chet; 13.11.2015