Есть ли способ отслеживать сквозное происхождение данных с помощью запроса Neo4j Cypher?
Я использую Spring-Data вместе с SpringBoot, чтобы заполнить мою базу данных Neo4j.
У меня определены следующие сущности Neo4j:
Source
сущность ->
@NodeEntity
public class Source implements Comparable<Source> {
@GraphId private Long id;
private String name;
private SourceType type;
private String dataStoreName;
private String dataStoreDesc;
private Source() {
// Empty constructor required as of Neo4j API 2.0.5
};
public Source(String name, SourceType type, String dataStoreName, String dataStoreDesc) {
this.name = name;
this.type = type;
this.dataStoreName = dataStoreName;
this.dataStoreDesc = dataStoreDesc;
}
@Relationship(type = "CONTAINS", direction = Relationship.UNDIRECTED)
public Set<Field> fields;
public void contains(Field field) {
if (fields == null) {
fields = new HashSet<Field>();
}
fields.add(field);
}
/* Getter and Setters */
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public SourceType getType() {
return type;
}
public void setType(SourceType type) {
this.type = type;
}
public String getDataStoreName() {
return dataStoreName;
}
public void setDataStoreName(String dataStoreName) {
this.dataStoreName = dataStoreName;
}
public String getDataStoreDesc() {
return dataStoreDesc;
}
public void setDataStoreDesc(String dataStoreDesc) {
this.dataStoreDesc = dataStoreDesc;
}
public Set<Field> getFields() {
return fields;
}
public void setFields(Set<Field> fields) {
this.fields = fields;
}
@Override
public int compareTo(Source other) {
String name = other.getName();
SourceType type = other.getType();
if(this.name.equalsIgnoreCase(name) && this.type.equals(type))
return 0;
return -1;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((type == null) ? 0 : type.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Source other = (Source) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (type != other.type)
return false;
return true;
}
}
Field
юридическое лицо ->
@NodeEntity
public class Field implements Comparable<Field> {
@GraphId private Long id;
private String name;
private FieldType fieldType;
private SourceType sourceType;
private String logicalName;
private String dataType;
private String dataSize;
private String description;
private Field() {
// Empty constructor required as of Neo4j API 2.0.5
};
public Field(String name, FieldType fieldType, SourceType sourceType, String logicalName, String dataType, String dataSize, String description) {
this.name = name;
this.fieldType = fieldType;
this.sourceType = sourceType;
this.logicalName = logicalName;
this.dataType = dataType;
this.dataSize = dataSize;
this.description = description;
}
@Relationship(type = "MAPS-TO", direction = Relationship.UNDIRECTED)
public Set<Field> fields;
public void mapsTo(Field field) {
if (fields == null) {
fields = new HashSet<Field>();
}
fields.add(field);
}
/* Getter and Setters */
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public FieldType getFieldType() {
return fieldType;
}
public void setFieldType(FieldType fieldType) {
this.fieldType = fieldType;
}
public SourceType getSourceType() {
return sourceType;
}
public void setSourceType(SourceType sourceType) {
this.sourceType = sourceType;
}
public String getLogicalName() {
return logicalName;
}
public void setLogicalName(String logicalName) {
this.logicalName = logicalName;
}
public String getDataType() {
return dataType;
}
public void setDataType(String dataType) {
this.dataType = dataType;
}
public String getDataSize() {
return dataSize;
}
public void setDataSize(String dataSize) {
this.dataSize = dataSize;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Set<Field> getFields() {
return fields;
}
public void setFields(Set<Field> fields) {
this.fields = fields;
}
@Override
public int compareTo(Field other) {
String name = other.getName();
FieldType fieldType = other.getFieldType();
SourceType sourceType = other.getSourceType();
if(this.name.equalsIgnoreCase(name) && this.fieldType.equals(fieldType) && this.sourceType.equals(sourceType))
return 0;
return -1;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((fieldType == null) ? 0 : fieldType.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((sourceType == null) ? 0 : sourceType.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Field other = (Field) obj;
if (fieldType != other.fieldType)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (sourceType != other.sourceType)
return false;
return true;
}
}
Итак, Source
CONTAINS
множественный Field
s. И Field
является MAPS-TO
один или несколько других Field
s.
каждый Source
имеет SourceType
,
Мой разный SourceType
Это: ПРОИЗВОДИТЕЛЬ, ВСТАВКА, СТАЖИРОВКА, ПРОМЕЖУТОЧНАЯ, НАРУЖНАЯ, ПОТРЕБИТЕЛЬ.
public enum SourceType {
PRODUCER, INBOUND, STAGING, INTERMEDIATE, OUTBOUND, CONSUMER;
}
каждый Field
имеет FieldType
,
Мой разный FieldType
Это: FILE_FIELD, DB_COLUMN.
public enum FieldType {
FILE_FIELD, DB_COLUMN;
}
Мои данные следующие: ПРОИЗВОДИТЕЛЬ -> ВСТАВКА -> ОЧИСТКА -> ПРОМЕЖУТОЧНАЯ -> НАРУЖНАЯ -> ПОТРЕБИТЕЛЬ
Сейчас я ищу расширенный запрос Cypher, с помощью которого, если я предоставлю Field
в ПОТРЕБИТЕЛЬСКОМ Source
Я могу отследить его происхождение до ПРОИЗВОДИТЕЛЯ Source
,
Точно так же я ищу запрос, по которому, если я предоставлю Field
в ПРОИЗВОДИТЕЛЕ Source
Я могу отследить его происхождение до ПОТРЕБИТЕЛЯ Source
,
Я пытался создать запрос, используя shortestPath
а также neighbors
функции, но это, кажется, не подтягивает результаты, которые я ищу.
Любые предложения / указатели будут оценены.
Заранее спасибо!
UPDATE-1
Предыстория моей линии данных: мое приложение получает файл из внешнего приложения (PRODUCE). Мне известно о том, какие таблицы / столбцы базы данных внешнего приложения заполняли поля в файле. Так вот, ПРОИЗВОДИТЕЛЬ будет моим Source
узел; каждый table.column внешнего приложения (заполнившего файл) является Field
узел и ПРОИЗВОДИТЕЛЬ Source
узел будет иметь CONTAINS
отношения со всеми Field
узлы (представляющие table.column таблицы базы данных внешнего приложения, которая заполнила файл).
Файл из внешнего приложения называется INBOUND. Это файл с разделителями-запятыми. Я знаю, какие имена полей идут в файле и в каком порядке. Так вот, INBOUND будет моим Source
узел; каждое поле в файле будет Field
узел и INBOUND Source
узел будет иметь CONTAINS
отношения со всеми Field
узлы (представляющие поля файла во входящем файле). Также каждый из Field
узлы INBOUND Source
будет иметь MAPS_TO
отношения с Field
узел ПРОИЗВОДИТЕЛЯ Source
(сопоставление один к одному).
Продолжая аналогичный рабочий процесс, мой следующий этап называется STAGING, в котором я загружаю поля входящего файла в свою таблицу / столбец базы данных. Так что здесь, STAGING будет моим Source
узел и каждый столбец таблицы базы данных (в которую я загружаю поля файла) будет представлять Field
узел.
STAGING Исходный узел будет СОДЕРЖАТЬ отношения со всеми Field
узлы (которые представляют db table.column таблицы db, в которую я загружаю поля файла). Также каждый из Field
узлы STAGING Source
будет иметь MAPS_TO
отношения с Field
узел INBOUND Source
(сопоставление один к одному).
Похоже, мой следующий этап - ПРОМЕЖУТОЧНЫЙ. На этом этапе я запрашиваю таблицу, в которую я загрузил поля входного файла, а затем сбрасываю выходные данные в другой файл (в зависимости от моего бизнес-сценария, я могу выбрать запрос ко всем или только к подмножеству столбцов таблицы, которые были заполняется из входного файла). Я знаю, какие поля и в каком порядке попадут в мой промежуточный файл. Так что здесь, INTERMEDIATE - это мое Source
узел и каждое поле, которое входит в промежуточный файл представляет мой Field
узел. Также ПРОМЕЖУТОЧНО Source
буду иметь CONTAINS
отношения со всеми Field
узлы, которые представляют поле в промежуточном файле. Также каждый из этих Field
узлы будут иметь MAPS_TO
связь с полями STAGING Source (сопоставление один-к-одному).
Точно так же у меня есть этап OUTBOUND и, наконец, этап CONSUMER.
... (Я надеюсь, что теперь вы можете визуализировать родословную)
Цель моего запроса, скажем, если я дам Field
name (которые представляют table.column PRODUCER) в качестве входных данных, тогда я должен быть в состоянии отследить его происхождение до ПОТРЕБИТЕЛЯ (т. е. до последней стадии моей линии).
2 ответа
Я смог получить желаемое происхождение данных с помощью запроса ниже:
MATCH (f5:Field)-[:MAPS_TO]-(f4:Field)-[:MAPS_TO]-(f3:Field)-[:MAPS_TO]-(f2:Field)-[:MAPS_TO]-(f1:Field)-[:MAPS_TO]-(f:Field)<-[:CONTAINS]-(s:Source {type: "SOURCE"}) WHERE f.name="<my input source field>" RETURN f,s,f1,f2,f3,f4,f5
Точно так же я ищу запрос, по которому, если я предоставлю
Field
в ПРОИЗВОДИТЕЛЕSource
Я могу отследить его происхождение до ПОТРЕБИТЕЛЯSource
,
Я не думаю, что полностью понимаю вашу модель данных и требования, но вот идея для этого запроса:
MATCH
(:Field {name: { fieldName } })<-[:CONTAINS]-
(:Source {type: "PRODUCER" })-[:MAPS_TO]->
(:Source {type: "INBOUND" })-[:MAPS_TO]->
(:Source {type: "STAGING" })-[:MAPS_TO]->
(:Source {type: "INTERMEDIATE"})-[:MAPS_TO]->
(:Source {type: "OUTBOUND" })-[:MAPS_TO]->
(consumer:Source {type: "CONSUMER" })
RETURN consumer