构建由 LLM 驱动的 Neo4j 应用程序——使用电影数据集构建你的Neo4j图数据库

在前几章中,我们了解到知识图谱作为一种变革性工具出现,提供了一种结构化方式连接多样化的数据点,支持更智能的搜索、推荐和推理能力,应用范围广泛。

知识图谱擅长捕捉实体间复杂关系,是需要深度上下文理解的应用不可或缺的工具。

Neo4j 作为领先的图数据库技术平台,专注于构建和管理知识图谱。正如上一章所示,与传统关系型数据库不同,Neo4j 设计用于轻松处理高度关联的数据,实现更直观的查询和更快速的洞察获取。这使其成为希望将原始非结构化数据转化为有意义洞察、驱动AI应用的开发者和数据科学家的理想选择。

本章主要内容包括:

  • 设计高效搜索的Neo4j图谱的考虑因素
  • 利用电影数据集
  • 结合代码示例构建电影知识图谱
  • 进阶内容:复杂图结构的高级Cypher技巧

技术要求

成功完成本章练习,你需要准备以下工具:

  • Neo4j AuraDB :可使用Neo4j云端版本AuraDB,访问:neo4j.com/aura

  • Cypher查询语言 :需熟悉Cypher语法,因本章大量使用Cypher进行图创建与查询。详见:neo4j.com/docs/cypher...

  • Python :需安装Python 3.x,用于脚本编写和Neo4j数据库交互。官方下载:www.python.org/downloads/

  • Python库:

    • Neo4j Python驱动:通过pip安装,命令为pip install neo4j
    • pandas:用于数据处理与分析,命令为pip install pandas
  • 集成开发环境(IDE) :推荐使用PyCharm、VS Code或Jupyter Notebook等高效管理Python代码的IDE

  • Git与GitHub:需具备基本Git知识进行版本控制,并拥有GitHub账号以访问本章代码仓库

  • 电影数据集 :使用Movie Database (TMDb),可在Kaggle下载:www.kaggle.com/datasets/ro...

    该数据集衍生自Movie Lens Datasets(F. Maxwell Harper and Joseph A. Konstan, 2015)。部分数据文件如credits.csv和ratings.csv由于存储限制可能未托管于GitHub,但你可通过GCS桶获取所有原始数据文件。

本章所有代码托管于GitHub仓库:github.com/PacktPublis...。

该文件夹包含使用电影数据集和Cypher代码构建Neo4j图谱所需的全部文件和脚本。

请确保克隆或下载该仓库,以便跟进本章中的代码示例,仓库中提供了访问GCS以获取原始数据文件的路径。

为高效搜索设计Neo4j图谱的考虑事项

一个设计良好的Neo4j图谱不仅能保证搜索功能的准确性,还能提升效率,实现快速检索相关信息。数据在图中的组织方式直接影响搜索结果的性能和相关性,因此理解有效图建模的原则至关重要。

本节将深入探讨正确构建Neo4j图谱结构的重要性,它如何影响搜索过程,以及设计图模型时需要重点关注的关键因素。

节点与关系类型定义的考虑

回顾第3章,任何Neo4j图谱的基础都是节点和关系。节点表示实体,如电影或人物(例如演员或导演),而关系定义这些实体之间的连接方式。你选择的节点和关系类型在决定搜索查询的效果中起着关键作用。

在电影数据集中,传统上节点可能分别代表不同实体,如电影(Movies)、演员(Actors)、导演(Directors)和类别(Genres)。关系则描述这些节点如何互动,例如ACTED_IN、DIRECTED或BELONGS_TO。但还有一种更高效的替代方法------将相似实体合并为单一节点类型。

与其为演员和导演分别创建节点,不如创建单一的Person节点。每个Person节点的具体属性------是演员、导演,还是两者兼有------由其与电影节点之间的关系类型决定。例如,Person节点通过ACTED_IN关系连接到电影节点,表示该人是电影的演员;通过DIRECTED关系连接则表示该人为导演。我们将在后续章节创建完整图谱。

为什么这种方法更优?

正如第3章所示,这种方法带来以下优势:

  • 简化数据模型
    使用单一的Person节点同时表示演员和导演,使数据模型更简洁,降低图的复杂度,便于理解和管理。
  • 提升查询性能
    节点类型减少,图数据库在查询时能更高效地遍历关系,因为数据库引擎需要区分的实体更少,查询执行更快。
  • 减少冗余
    统一的Person节点避免重复信息存储。对于既是演员又是导演的人,无需创建两个节点,减少冗余,节省存储空间。
  • 灵活的关系定义
    此方法支持更灵活且细粒度的关系定义。如果一个人在不同电影中扮演多重角色(如一部电影出演,一部电影导演),关系可以清晰区分这些角色,无需多个节点。
  • 便于维护和扩展
    随着数据集增长,维护简洁的节点结构愈发重要。新增角色或关系时,操作更简单高效。

通过谨慎选择和定义节点类型及关系,你能够构建一个贴合真实世界连接的图结构,使搜索查询更直观,结果更具意义,整体系统效率更高。

索引和约束对搜索性能的影响

随着Neo4j图谱的不断增长,索引和约束的重要性日益凸显。索引使Neo4j能够快速定位查询的起点,极大提升搜索性能,尤其是在大规模数据集中。而约束则保证数据完整性,防止创建重复节点或无效关系。

在我们使用统一Person节点表示演员和导演的电影数据集中,索引变得尤为关键。你可以基于节点属性如person_name或role建立索引,确保针对特定人物或其在电影中角色的搜索能够迅速返回结果。例如,你可以在关系的role属性(如ACTED_IN或DIRECTED)上建立索引,以便快速筛选某人在特定电影中的参与角色。

约束对于维护图谱的完整性同样必不可少。以下是一些示例,展示如何针对电影数据集创建约束和索引。这些示例涵盖了常见场景,如保证person标识的唯一性,以及优化节点和关系属性上的搜索性能。你可以根据具体使用场景和数据质量,调整这些模式以保障数据完整性和提升查询速度。

  • person_name的唯一约束(简化场景)

    在许多情况下,比如我们的电影数据集中,假设每个人名唯一,你可以对person_name属性施加唯一约束,确保每个个体只对应一个节点,即使他们在不同电影中担任多个角色(演员、导演等)。示例如下:

    sql 复制代码
    CREATE CONSTRAINT unique_person_name IF NOT EXISTS
    FOR (p:Person)
    REQUIRE p.person_name IS UNIQUE;

    这样有助于避免重复节点的产生,保持图谱整洁高效。

  • 更可靠的唯一ID约束(如person_id)

    上述唯一性约束基于对数据的假设,而实际场景中常见同名不同人。

    此时应使用更可靠的标识符,如来自外部源(IMDb或TMDb)的person_id,来保证唯一性。示例代码:

    sql 复制代码
    CREATE CONSTRAINT unique_person_id IF NOT EXISTS
    FOR (p:Person)
    REQUIRE p.person_id IS UNIQUE;
  • person_name索引(若不强制唯一性)

    若不强制唯一约束但频繁按名称查找人物,可为person_name属性建立索引,加快查询速度:

    css 复制代码
    CREATE INDEX person_name_index IF NOT EXISTS
    FOR (p:Person)
    ON (p.person_name);
  • Movie的title属性索引

    电影常按标题查询,尤其在推荐系统和搜索功能中。对title属性建索引能保证快速定位电影:

    scss 复制代码
    CREATE INDEX movie_title_index IF NOT EXISTS
    FOR (m:Movie)
    ON (m.title);
  • ACTED_IN关系的role属性索引

    若应用需按角色类型(主角、客串等)过滤演员,为ACTED_IN关系的role属性建立索引,有助于避免全量扫描关系,加速查询:

    scss 复制代码
    CREATE INDEX acted_in_role_index IF NOT EXISTS
    FOR ()-[r:ACTED_IN]-()
    ON (r.role);

注意

Neo4j仅在5.x及以上版本支持关系属性索引。

合理实施索引和约束,可增强图谱的健壮性,使搜索过程更快、更可靠。这不仅提升用户体验,还能减轻系统计算负载,助力解决方案的可扩展性。

下一节,我们将探讨如何利用公开电影数据集的力量,构建你的图谱。

利用电影数据集

本节将重点介绍如何使用TMDb(The Movie Database)数据集,这是一个在Kaggle上提供的全面元数据集合:www.kaggle.com/datasets/ro...。该数据集包含丰富的电影信息,如标题、类型、演员、工作人员、上映日期和评分等。拥有超过45,000部电影以及详细的相关人员信息,这个数据集为构建能够捕捉电影行业复杂关系的Neo4j图谱提供了坚实基础。

你将使用该数据集将数据建模为知识图谱,学习如何在实际环境中进行数据集成。你将了解如何获取、准备并导入数据到Neo4j中。

在处理像TMDb这样的大型数据集时,确保数据干净、一致且结构合理,是集成到Neo4j图谱前的关键步骤。尽管原始数据包含丰富信息,但往往存在不一致、冗余及复杂结构,这会影响知识图谱的性能和准确性。因此,数据规范化和清洗非常必要。

为什么要规范化和清洗数据?

构建Neo4j图谱时,保持数据清洁和规范化至关重要,因为它直接影响应用的质量和性能。通过规范化和清洗数据,可以确保一致性、提升效率,并为分析创建可扩展的基础。具体原因如下:

  • 一致性
    原始数据中相似信息的记录方式可能存在差异。例如,电影类型可能以不同格式出现,甚至有重复。规范化能保证相似数据点以统一格式记录,便于查询和分析。
    现实数据集中解决这些问题往往具有挑战性。Neo4j提供了强大功能,如Cypher模式匹配、APOC过程用于合并节点和去重,以及图数据科学库中的节点相似性算法,帮助识别并合并相关实体。这些能力使你能构建一个清洁可靠、真实反映数据结构的图谱。
  • 效率
    规范化数据能减少冗余,从而提升Neo4j图谱的效率。通过将数据组织成标准格式,减少存储需求,优化查询性能。
  • 准确性
    清洗数据包括删除或修正不准确记录,确保从图谱中获得的洞察基于准确可靠的数据。
  • 可扩展性
    清洁且规范化的数据集更易于扩展。随着数据集增长,保持标准化结构有助于图谱管理,并保证系统在负载增加时依然高效运行。

接下来,我们将开始清洗和规范化CSV文件的步骤。

清洗和规范化CSV文件

现在,我们将清洗并规范化TMDb数据集中的每个CSV文件。数据集中包含的CSV文件如下:

credits.csv

该文件包含数据集中每部电影的演员和工作人员详细信息,格式为字符串化的JSON对象。我们重点提取与角色、演员、导演和制片人相关的字段:

ini 复制代码
# 读取CSV文件
df = pd.read_csv('./raw_data/credits.csv')

# 提取演员信息函数
def extract_cast(cast_str):
    cast_list = ast.literal_eval(cast_str)
    return [
        {
            'actor_id': c['id'],
            'name': c['name'],
            'character': c['character'],
            'cast_id': c['cast_id']
        }
        for c in cast_list
    ]

# 提取工作人员信息函数
def extract_crew(crew_str):
    crew_list = ast.literal_eval(crew_str)
    relevant_jobs = ['Director', 'Producer']
    return [
        {
            'crew_id': c['id'],
            'name': c['name'],
            'job': c['job']
        }
        for c in crew_list if c['job'] in relevant_jobs
    ]

# 应用提取函数
df['cast'] = df['cast'].apply(extract_cast)
df['crew'] = df['crew'].apply(extract_crew)

# 将列表拆分成单独的行
df_cast = df.explode('cast').dropna(subset=['cast'])
df_crew = df.explode('crew').dropna(subset=['crew'])

# 规范化拆分的数据
df_cast_normalized = pd.json_normalize(df_cast['cast'])
df_crew_normalized = pd.json_normalize(df_crew['crew'])

# 重置索引以避免重复
df_cast_normalized = df_cast_normalized.reset_index(drop=True)
df_crew_normalized = df_crew_normalized.reset_index(drop=True)

# 删除重复行
df_cast_normalized = df_cast_normalized.drop_duplicates()
df_crew_normalized = df_crew_normalized.drop_duplicates()

# 添加电影ID
df_cast_normalized['tmdbId'] = df_cast.reset_index(drop=True)['id']
df_crew_normalized['tmdbId'] = df_crew.reset_index(drop=True)['id']

# 保存规范化数据
df_cast_normalized.to_csv(os.path.join(output_dir, 'normalized_cast.csv'), index=False)
df_crew_normalized.to_csv(os.path.join(output_dir, 'normalized_crew.csv'), index=False)

# 显示样例数据进行验证
print("规范化演员数据样例:")
print(df_cast_normalized.head())
print("规范化工作人员数据样例:")
print(df_crew_normalized.head())

keywords.csv

此文件包含每部电影的剧情关键词,有助于分类和主题识别,支持搜索、推荐和内容分析等功能:

ini 复制代码
# 读取CSV文件
df = pd.read_csv('./raw_data/keywords.csv')  # 根据实际路径调整

# 规范化关键词函数
def normalize_keywords(keyword_str):
    if pd.isna(keyword_str) or not isinstance(keyword_str, str):
        return []
    keyword_list = ast.literal_eval(keyword_str)
    return [kw['name'] for kw in keyword_list]

# 应用规范化
df['keywords'] = df['keywords'].apply(normalize_keywords)

# 合并每个tmdbId的所有关键词
df_keywords_aggregated = df.groupby('id', as_index=False).agg({
    'keywords': lambda x: ', '.join(sum(x, []))
})

# 重命名列
df_keywords_aggregated.rename(columns={'id': 'tmdbId'}, inplace=True)

# 保存合并后的数据
df_keywords_aggregated.to_csv(os.path.join(output_dir, 'normalized_keywords.csv'), index=False)

# 显示数据验证
print(df_keywords_aggregated.head())
  • links.csv:连接完整MovieLens数据集中的电影与TMDb和IMDb的关键元数据文件。虽有重要性,但本项目当前不使用该文件,侧重其他更相关CSV文件。未来需要整合外部数据库时,该文件依然有价值。
  • links_small.csv:MovieLens的9000部电影的TMDb和IMDb ID精简版。我们不使用此文件,因已采用完整Kaggle数据集。

movies_metadata.csv

该文件包含45000部电影的详细信息,如海报、预算、收入、上映日期、语言、制片国家及公司等。为了有效管理与分析,我们将其拆分并规范化成多个CSV文件,分别对应数据中的节点类别,如类型(genres)、制片公司(production companies)、制片国家(production countries)和语言(spoken languages)。这样便于管理和利用丰富信息。

示例代码(仅展示genres和production companies部分,完整代码见GitHub):

scss 复制代码
import pandas as pd
import ast

df = pd.read_csv('./raw_data/movies_metadata.csv')  # 根据实际路径调整

# 规范化类型字段
def extract_genres(genres_str):
    if pd.isna(genres_str) or not isinstance(genres_str, str):
        return []
    genres_list = ast.literal_eval(genres_str)
    return [{'genre_id': int(g['id']), 'genre_name': g['name']} for g in genres_list]

# 规范化制片公司字段
def extract_production_companies(companies_str):
    if pd.isna(companies_str) or not isinstance(companies_str, str):
        return []
    companies_list = ast.literal_eval(companies_str)
    if isinstance(companies_list, list):
        return [{'company_id': int(c['id']), 'company_name': c['name']} for c in companies_list]
    return []

# 应用规范化
df['genres'] = df['genres'].apply(extract_genres)
df['production_companies'] = df['production_companies'].apply(extract_production_companies)
df['production_countries'] = df['production_countries'].apply(extract_production_countries)
df['spoken_languages'] = df['spoken_languages'].apply(extract_spoken_languages)

# 拆分列表成行
df_genres = df.explode('genres').dropna(subset=['genres'])
df_companies = df.explode('production_companies').dropna(subset=['production_companies'])
df_countries = df.explode('production_countries').dropna(subset=['production_countries'])
df_languages = df.explode('spoken_languages').dropna(subset=['spoken_languages'])

# 规范化genres
df_genres_normalized = pd.json_normalize(df_genres['genres'])
df_genres_normalized = df_genres_normalized.reset_index(drop=True)
df_genres_normalized['tmdbId'] = df_genres.reset_index(drop=True)['id']
df_genres_normalized['genre_id'] = df_genres_normalized['genre_id'].astype(int)

# 保存规范化数据
df_genres_normalized.to_csv(os.path.join(output_dir, 'normalized_genres.csv'), index=False)

集合名称提取

python 复制代码
def extract_collection_name(collection_str):
    if isinstance(collection_str, str):
        try:
            collection_dict = ast.literal_eval(collection_str)
            if isinstance(collection_dict, dict):
                return collection_dict.get('name', "None")
        except (ValueError, SyntaxError):
            return "None"
    return "None"

df_movies = df[['id', 'original_title', 'adult', 'budget', 'imdb_id', 'original_language',
              'revenue', 'tagline', 'title', 'release_date', 'runtime', 'overview', 'belongs_to_collection']].copy()
df_movies['belongs_to_collection'] = df_movies['belongs_to_collection'].apply(extract_collection_name)
df_movies['adult'] = df_movies['adult'].apply(lambda x: 1 if x == 'TRUE' else 0)  # 转换adult为整数
df_movies.rename(columns={'id': 'tmdbId'}, inplace=True)  # 重命名id为tmdbId
df_movies.to_csv('./normalized_data/normalized_movies.csv', index=False)

ratings.csv 与 ratings_small.csv

  • ratings.csv:包含270,000用户对45,000电影的2600万评分和75万标签。此数据详细记录用户互动,虽有价值,但本项目不使用。
  • ratings_small.csv:ratings.csv的子集,包含700用户对9000电影的10万评分。本项目采用此小规模文件,便于处理。

通过以上步骤,我们学会了如何将原始非结构化数据转化为清晰、规范的数据集,为后续导入Neo4j图谱做好准备。这为构建稳健、高效的AI驱动搜索与推荐系统奠定了基础。下一节,我们将使用这些规范化的CSV文件,并通过Cypher代码构建知识图谱,充分发挥数据集的潜力。

用代码示例构建你的电影知识图谱

本节中,我们将导入你已规范化的数据集到Neo4j,并将其转化为一个功能完整的知识图谱。

设置你的AuraDB免费实例

要开始使用Neo4j构建知识图谱,首先需要创建一个AuraDB Free实例。AuraDB Free是Neo4j的云托管数据库,帮助你快速开始,无需担心本地安装或基础设施管理。

创建实例的步骤如下:

  1. 访问 console.neo4j.io。
  2. 使用Google账号或邮箱登录。
  3. 点击"Create Free Instance"创建免费实例。
  4. 实例创建过程中,会弹出窗口显示数据库的连接凭证。

请务必下载并安全保存以下信息,这些是连接你的应用程序与Neo4j所必需的:

ini 复制代码
NEO4J_URI=neo4j+s://<your-instance-id>.databases.neo4j.io
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=<your-generated-password>
AURA_INSTANCEID=<your-instance-id>
AURA_INSTANCENAME=<your-instance-name>

当AuraDB Free实例配置完成后,你就可以导入已规范化的数据集,并使用Cypher代码开始构建你的知识图谱了。接下来的章节将引导你完成数据导入及图中关系的构建。

将数据导入 AuraDB

现在你的 AuraDB 免费实例已经启动运行,接下来是导入规范化的数据集并构建知识图谱。在本节中,我们将通过一个 Python 脚本,演示如何准备 CSV 文件、设置索引与约束、导入数据以及创建关系:

准备 CSV 文件

确保你生成的 CSV 文件(如 normalized_movies.csv、normalized_genres.csv 等)已准备好导入。文件应清晰、结构良好,并托管在可访问的 URL 上。本示例中,graph_build.py 脚本会从公共云存储(例如 storage.googleapis.com/movies-pack...)自动获取文件,无需你手动上传。

添加索引和约束以优化查询

在加载数据之前,创建唯一约束和索引非常关键,能保证数据完整性并提升查询性能。脚本包含如下 Cypher 命令示例:

sql 复制代码
CREATE CONSTRAINT unique_tmdb_id IF NOT EXISTS FOR (m:Movie) REQUIRE m.tmdbId IS UNIQUE;
CREATE CONSTRAINT unique_movie_id IF NOT EXISTS FOR (m:Movie) REQUIRE m.movieId IS UNIQUE;
CREATE CONSTRAINT unique_prod_id IF NOT EXISTS FOR (p:ProductionCompany) REQUIRE p.company_id IS UNIQUE;
CREATE CONSTRAINT unique_genre_id IF NOT EXISTS FOR (g:Genre) REQUIRE g.genre_id IS UNIQUE;
CREATE CONSTRAINT unique_lang_id IF NOT EXISTS FOR (l:SpokenLanguage) REQUIRE l.language_code IS UNIQUE;
CREATE CONSTRAINT unique_country_id IF NOT EXISTS FOR (c:Country) REQUIRE c.country_code IS UNIQUE;
CREATE INDEX actor_id IF NOT EXISTS FOR (p:Person) ON (p.actor_id);
CREATE INDEX crew_id IF NOT EXISTS FOR (p:Person) ON (p.crew_id);
CREATE INDEX movieId IF NOT EXISTS FOR (m:Movie) ON (m.movieId);
CREATE INDEX user_id IF NOT EXISTS FOR (p:Person) ON (p.user_id);

导入数据并创建节点

在添加约束和索引后,脚本会从相应 CSV 文件加载节点:

  • load_movies() 加载所有电影元数据
  • load_genres()、load_production_companies()、load_countries() 等加载对应的类型节点,如 Genre、ProductionCompany、Country、SpokenLanguage
  • load_person_actors() 和 load_person_crew() 加载与人物相关的数据
  • 额外属性通过 load_links()、load_keywords() 和 load_ratings() 添加

例如:

rust 复制代码
graph.load_movies('https://storage.googleapis.com/movies-packt/normalized_movies.csv', movie_limit)

创建关系

每个加载函数不仅创建节点,也建立有意义的关系,比如:

  • Movie 与 Genre 之间的 HAS_GENRE
  • Movie 与 ProductionCompany 之间的 PRODUCED_BY
  • Movie 与 SpokenLanguage 之间的 HAS_LANGUAGE
  • Movie 与 Country 之间的 PRODUCED_IN
  • Movie 与 Person 之间的 ACTED_IN、DIRECTED、PRODUCED
  • Movie 与 User 之间的 RATED 等

运行完整脚本

确保已安装 Neo4j Python 驱动,安装命令:

复制代码
pip install neo4j

然后执行完整图谱构建脚本:

复制代码
python graph_build.py

脚本执行流程:

  • 使用 .env 文件中的凭据连接 AuraDB 实例
  • 清理数据库
  • 添加索引和约束
  • 使用托管的 CSV 批量加载所有节点数据及关系

完整脚本请参考:
github.com/PacktPublis...

验证导入

导入完成后,可用 Neo4j Browser 验证数据:

less 复制代码
MATCH (m:Movie)-[:HAS_GENRE]->(g:Genre)
RETURN m.title, g.genre_name
LIMIT 10;

图4.1展示了一个包含超过9万个节点和32万个关系的连通电影图谱。节点如 Movie、Genre、Person、ProductionCompany 用不同颜色表示,关系如 ACTED_IN、HAS_GENRE、PRODUCED_BY 展示了相互连接的元数据网络。

成功导入数据并使用 Python 和 Cypher 完成知识图谱构建后,你已准备好在下一章构建基于生成式人工智能(GenAI)的搜索应用。在接下来的部分,我们将深入介绍高级 Cypher 技巧,助你处理复杂关系并从数据中提炼更深层次的洞见。

超越基础:应对复杂图结构的高级 Cypher 技巧

随着你的知识图谱规模和复杂度不断增长,对查询和数据管理能力的要求也随之提升。Neo4j 强大的查询语言 Cypher 提供了一系列高级功能,专门用来处理复杂的图结构,并支持更为复杂的数据分析。在本节中,我们将探讨这些高级 Cypher 技巧,包括路径模式、可变长度关系、子查询以及图算法。理解这些技巧将帮助你高效管理复杂的关系,进行更深入的分析,释放知识图谱在高级场景中的全部潜力。

让我们一起深入了解以下关键的高级 Cypher 技巧:

可变长度关系(Variable-length relationships)

Cypher 中的可变长度关系允许你匹配节点之间路径长度可变的关系。这在探索层级结构或多级分离的网络时尤为有用。例如,查找某演员三度关系内所有相关的电影:

css 复制代码
MATCH (a:Actor {name: 'Tom Hanks'})-[:ACTED_IN*1..3]-(m:Movie)
RETURN DISTINCT m.title;

这里,*1..3 指定关系路径长度在1到3步之间。

使用场景:可变长度关系非常适合社交网络分析(比如查找某人一定度数内的所有联系人),或者层级数据中探索多层父子关系。

路径模式匹配(Pattern matching with path patterns)

你可以在 Neo4j 中定义命名的路径模式,并且可以将路径模式进行链式组合。

  • 定义路径模式:Cypher 允许你定义可复用的命名路径模式,这使查询更具可读性,并将复杂关系封装成单一模式。例如:
ini 复制代码
MATCH path = (a:Actor)-[:ACTED_IN]->(m:Movie)
RETURN path;

这里 path 是一个命名的路径模式,可以在后续操作或子查询中复用。

  • 链式路径模式:Cypher 支持将多个路径模式组合,以执行复杂的图遍历。此功能在揭示间接关系或发现满足特定条件的多条路径时非常实用。

举个例子,探索电影数据集中合作关系:

假设想找出某演员与其曾合作导演的电影,可能通过另一部电影间接关联。即,从演员到电影,再到导演,然后查找另一个不同的电影也连接该演员和导演:

scss 复制代码
MATCH (a:Actor {name: "Tom Hanks"})-[:ACTED_IN]->(m1:Movie)<-[:DIRECTED_BY]-(d:Director) 
MATCH (a)-[:ACTED_IN]->(m2:Movie)<-[:DIRECTED_BY]-(d)
WHERE m1 <> m2
RETURN a.name AS actor, d.name AS director, collect(DISTINCT m1.title) + collect(DISTINCT m2.title) AS movies;

这种链式模式极大地帮助识别专业关系、反复合作或分析网络中的间接影响。

子查询与过程逻辑(Subqueries and procedural logic)

你可以使用子查询和存储过程来处理复杂查询。

  • 模块化查询的子查询:Cypher 的子查询允许将复杂查询拆分成模块化、可复用的组件。这在处理大图或对同一数据集执行多次操作时非常有用。示例:
scss 复制代码
CALL {
  MATCH (m:Movie)-[:HAS_GENRE]->(g:Genre {name: 'Action'})
  RETURN m
}
MATCH (m)-[:DIRECTED_BY]->(d:Director)
RETURN d.name, COUNT(m) AS action_movies_directed;

这里,子查询先检索所有动作片,外层查询则匹配这些电影的导演。

  • CALL 过程调用的过程逻辑 :Cypher 的 CALL 语句允许调用存储过程,并在后续查询中使用结果。它是进行高级数据处理的关键,如运行图算法或调用自定义过程。

例如,我们在 graph_build.py 文件的 load_ratings() 函数中使用了 CALL { ... } IN TRANSACTIONS 模式,批量处理大数据集,每次处理 5 万行:

sql 复制代码
LOAD CSV WITH HEADERS FROM $csvFile AS row
CALL (row) {
  MATCH (m:Movie {movieId: toInteger(row.movieId)})
  WITH m, row
  MERGE (p:Person {user_id: toInteger(row.userId)})
  ON CREATE SET p.role = 'user'
  MERGE (p)-[r:RATED]->(m)
  ON CREATE SET r.rating = toFloat(row.rating), r.timestamp = toInteger(row.timestamp)
} IN TRANSACTIONS OF 50000 ROWS;

该方式保证了导入大规模 CSV 时的性能与事务完整性,是 CALL 在实际图应用中的强大用例之一。

  • 嵌套查询:在复杂图结构中,可能需要将多个查询结果组合。Cypher 允许嵌套查询,将一个查询的结果传递给另一个,用于多条件过滤或结果精炼。示例:
sql 复制代码
MATCH (m:Movie)
WHERE m.revenue > 100000000
CALL {
  WITH m
  MATCH (m)-[:HAS_GENRE]->(g:Genre)
  RETURN g.name AS genre
}
RETURN m.title, genre;

这里,嵌套查询先筛选收入超过1亿的电影,然后找出它们所属的类型。

这些 Cypher 技巧使你能够应对复杂的图结构,实现更深层次的洞察和更复杂的分析。你可以访问 neo4j.com/docs/cypher... 进一步探索这些技巧。

小结

本章中,我们将原始的半结构化数据转化为干净且规范化的数据集,为知识图谱的集成做好准备。接着,我们探讨了图建模的最佳实践,重点讲解了如何构建节点和关系,以提升搜索效率,并确保图谱的可扩展性与性能表现。随后,我们学习了更多 Cypher 技巧,掌握了处理可变长度关系、路径模式匹配、子查询以及图算法的能力。现在,你已经准备好构建能够处理复杂数据关系的知识图谱驱动搜索系统。

在下一章,我们将更进一步,探索如何将 Haystack 集成到 Neo4j 中。这份实用指南将教你如何在知识图谱中构建强大的搜索功能,充分发挥 Neo4j 和 Haystack 的优势,实现智能搜索解决方案。

相关推荐
stormzhangV16 分钟前
未来十年的建议
人工智能·aigc·agent
孟健3 小时前
我用Grok Bot本地剪口播视频,3段提示词搞定气口切除、字幕与封面
人工智能·python·aigc
小碗细面3 小时前
Magpie 实战教程:一个本地网关,让 Claude Code 跑 Kimi、Codex 跑 DeepSeek
aigc·ai编程
GISer_Jing6 小时前
SuperAgent
ai·langchain
前端精髓7 小时前
langchain 短期记忆(状态)动态运行时上下文
python·langchain
threerocks7 小时前
2027 年最赚钱的 AI 创业方向
aigc·openai·ai编程
lyai0078 小时前
读完 DeepSeek Harness 源码,我给自己画了一张「最小 CLI Agent」施工图
llm·agent
西安小哥10 小时前
大模型 Agent 做日常任务,卡住的从来不是模型能力
aigc
goehou10 小时前
缓存该删还是该改,LLM 服务里的双写问题
redis·缓存·llm·一致性·系统设计
又过二十21 小时前
我把 5.5 万 Star的AI生成PPT项目ppt-master改造成了桌面应用
aigc·ppt