数据分析agent(十三_5):meta_demo:mysql

bash 复制代码
C:\Users\13032>docker ps -a
CONTAINER ID   IMAGE                   COMMAND                   CREATED        STATUS                       PORTS                                                                                      NAMES
0b5e8081298a   mysql:8.0.41            "docker-entrypoint.s..."   5 hours ago    Exited (255) 2 minutes ago   0.0.0.0:3306->3306/tcp, 33060/tcp                                                          my-mysql
e7d9691151ab   qdrant/qdrant:v1.16     "./entrypoint.sh"         30 hours ago   Up 2 minutes                 0.0.0.0:6333-6334->6333-6334/tcp, [::]:6333-6334->6333-6334/tcp                            qdrant
ddcab439807c   kibana:8.19.10          "/bin/tini -- /usr/l..."   2 days ago     Up 2 minutes                 0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp                                                kibana
b85631b340d8   elasticsearch:8.19.10   "/bin/tini -- /usr/l..."   2 days ago     Up 2 minutes                 0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp, 0.0.0.0:9300->9300/tcp, [::]:9300->9300/tcp   elasticsearch

C:\Users\13032>docker restart my-mysql
my-mysql

C:\Users\13032>docker ps
CONTAINER ID   IMAGE                   COMMAND                   CREATED        STATUS         PORTS                                                                                      NAMES
0b5e8081298a   mysql:8.0.41            "docker-entrypoint.s..."   5 hours ago    Up 6 seconds   0.0.0.0:3306->3306/tcp, [::]:3306->3306/tcp                                                my-mysql
e7d9691151ab   qdrant/qdrant:v1.16     "./entrypoint.sh"         30 hours ago   Up 2 minutes   0.0.0.0:6333-6334->6333-6334/tcp, [::]:6333-6334->6333-6334/tcp                            qdrant
ddcab439807c   kibana:8.19.10          "/bin/tini -- /usr/l..."   2 days ago     Up 2 minutes   0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp                                                kibana
b85631b340d8   elasticsearch:8.19.10   "/bin/tini -- /usr/l..."   2 days ago     Up 2 minutes   0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp, 0.0.0.0:9300->9300/tcp, [::]:9300->9300/tcp   elasticsearch

C:\Users\13032>docker run -d --name bge-large-zh-cpu -p 8080:80 -v "E:/笔记2026项目/项目一_问数/day/资料/1_windows_docker安装/embedding/bge-large-zh-v1.5":/models/bge-large-zh-v1.5 ghcr.io/huggingface/text-embeddings-inference:cpu-latest --model-id /models/bge-large-zh-v1.5 --auto-truncate
b935afed7fa41f1dd919cf65c80c64bfa3eaa96129a35a9ad6da80c47c9f8a1f

C:\Users\13032>docker ps
CONTAINER ID   IMAGE                                                      COMMAND                   CREATED          STATUS          PORTS                                                                                      NAMES
b935afed7fa4   ghcr.io/huggingface/text-embeddings-inference:cpu-latest   "text-embeddings-rou..."   25 seconds ago   Up 24 seconds   0.0.0.0:8080->80/tcp, [::]:8080->80/tcp                                                    bge-large-zh-cpu
0b5e8081298a   mysql:8.0.41                                               "docker-entrypoint.s..."   5 hours ago      Up 20 minutes   0.0.0.0:3306->3306/tcp, [::]:3306->3306/tcp                                                my-mysql
e7d9691151ab   qdrant/qdrant:v1.16                                        "./entrypoint.sh"         30 hours ago     Up 23 minutes   0.0.0.0:6333-6334->6333-6334/tcp, [::]:6333-6334->6333-6334/tcp                            qdrant
ddcab439807c   kibana:8.19.10                                             "/bin/tini -- /usr/l..."   2 days ago       Up 23 minutes   0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp                                                kibana
b85631b340d8   elasticsearch:8.19.10                                      "/bin/tini -- /usr/l..."   2 days ago       Up 23 minutes   0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp, 0.0.0.0:9300->9300/tcp, [::]:9300->9300/tcp   elasticsearch

C:\Users\13032>

一,meta_config_test.yaml

bash 复制代码
tables: #表格信息
  - name: dim_region #真实的表名
    role: dim # 表的角色 或者类型
    description: 地区维度表,用于描述订单发生的地理区域信息 # 这个表 对应的业务含义的说明
    columns:
      - name: region_id # 真实字段的名字
        role: primary_key  # 字段的角色 维度(只要是维度表中的),度量 (小数 类的数值)
        description: 地区唯一标识 # 字段的 业务含义
        alias: [地区ID,区域ID] # 字段同义词 别名
        sync: false # 该字段取值是否需要同步到 ES 建立 全文索引

      - name: province # 真实字段的名字
        role: dimension  # 字段的角色 维度(只要是维度表中的),度量 (小数 类的数值)
        description: 订单所属省份名称 # 字段的 业务含义
        alias: [ 省份,省,所在身份 ] # 字段同义词 别名
        sync: true # 该字段取值是否需要同步到 ES 建立 全文索引

      - name: region_name # 真实字段的名字
        role: dimension  # 字段的角色 维度(只要是维度表中的),度量 (小数 类的数值)
        description: 订单所属的大区名称,如华东,华南等 # 字段的 业务含义
        alias: [ 地区,区域,大区 ] # 字段同义词 别名
        sync: true # 该字段取值是否需要同步到 ES 建立 全文索引

      - name: country # 真实字段的名字
        role: dimension  # 字段的角色 维度(只要是维度表中的),度量 (小数 类的数值)
        description: 地区所属国家的名称 # 字段的 业务含义
        alias: [ 国家,国家名称 ] # 字段同义词 别名
        sync: true # 该字段取值是否需要同步到 ES 建立 全文索引

# ==============================================================

metrics: # 指标信息
  - name: GMV # 指标名称,如, GMV AOV
    description: 全称Gross Merchandise value,表示所有订单的成交金额总和 # 指标的业务含义描述 与 计算口径说明
    relevant_columns:           # 指标相关的字段 ,这个指标所需要的字段
      - fact_order.order_amount
    alias: [成交总额,订单总额] # 指标同义词,这个指标的别名 其他叫法

  # 把所有的订单金额 求和
# -------------------================================================================
  - name: AOV # 指标名称,如, GMV AOV
    description: 全称Average Order value,表示所有订单的成交金额总的平均值 # 指标的业务含义描述 与 计算口径说明
    relevant_columns: # 指标相关的字段 ,这个指标所需要的字段

      # - fact_order.order_quantity
      # 是上面的 还是下面的 ????
      - fact_order.order_amount
      - fact_order.order_id
    alias: [ 平均单价,平均订单金额 ] # 指标同义词,这个指标的别名 其他叫法

    # 我是这样理解的 :把所有的订单 金额 求和 ,然后除以 订单数量 ,这样的理解方式 合不合理呢 ????

1_mysql_demo.py:

python 复制代码
# mysql_demo.py
import json
from dataclasses import dataclass
from typing import Optional, List, Dict, Any
import mysql.connector
from mysql.connector import Error
import uuid


# ========== 数据模型定义 ==========
@dataclass
class ColumnConfig:
    name: str
    role: str
    description: str
    alias: List[str]
    sync: bool

    def to_dict(self):
        return {
            "name": self.name,
            "role": self.role,
            "description": self.description,
            "alias": self.alias,
            "sync": self.sync
        }

    @classmethod
    def from_dict(cls, data):
        return cls(
            name=data["name"],
            role=data["role"],
            description=data["description"],
            alias=data.get("alias", []),
            sync=data.get("sync", False)
        )


@dataclass
class TableConfig:
    name: str
    role: str
    description: str
    columns: List[ColumnConfig]

    def to_dict(self):
        return {
            "name": self.name,
            "role": self.role,
            "description": self.description,
            "columns": [col.to_dict() for col in self.columns]
        }

    @classmethod
    def from_dict(cls, data):
        return cls(
            name=data["name"],
            role=data["role"],
            description=data["description"],
            columns=[ColumnConfig.from_dict(col) for col in data.get("columns", [])]
        )


@dataclass
class MetricConfig:
    name: str
    description: str
    relevant_columns: List[str]
    alias: List[str]

    def to_dict(self):
        return {
            "name": self.name,
            "description": self.description,
            "relevant_columns": self.relevant_columns,
            "alias": self.alias
        }

    @classmethod
    def from_dict(cls, data):
        return cls(
            name=data["name"],
            description=data["description"],
            relevant_columns=data.get("relevant_columns", []),
            alias=data.get("alias", [])
        )


@dataclass
class MetaConfig:
    tables: Optional[List[TableConfig]] = None
    metrics: Optional[List[MetricConfig]] = None

    def to_dict(self):
        result = {}
        if self.tables:
            result["tables"] = [table.to_dict() for table in self.tables]
        if self.metrics:
            result["metrics"] = [metric.to_dict() for metric in self.metrics]
        return result

    @classmethod
    def from_dict(cls, data):
        tables = None
        metrics = None
        if "tables" in data:
            tables = [TableConfig.from_dict(table) for table in data["tables"]]
        if "metrics" in data:
            metrics = [MetricConfig.from_dict(metric) for metric in data["metrics"]]
        return cls(tables=tables, metrics=metrics)


# ========== MySQL 仓储实现 ==========
class MySQLMetaRepository:
    def __init__(self, host: str, port: int, user: str, password: str, database: str):
        self.connection_params = {
            'host': host,
            'port': port,
            'user': user,
            'password': password,
            'database': database
        }
        self._init_tables()

    def _get_connection(self):
        return mysql.connector.connect(**self.connection_params)

    def _init_tables(self):
        """初始化元数据表"""
        conn = self._get_connection()
        cursor = conn.cursor()

        # 创建元数据主表
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS meta_config (
                id VARCHAR(255) PRIMARY KEY,
                config_data JSON NOT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
            )
        """)

        # 创建表名索引表(用于快速搜索)
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS meta_table_index (
                id VARCHAR(255) PRIMARY KEY,
                table_name VARCHAR(255) NOT NULL,
                meta_config_id VARCHAR(255) NOT NULL,
                FOREIGN KEY (meta_config_id) REFERENCES meta_config(id) ON DELETE CASCADE,
                INDEX idx_table_name (table_name)
            )
        """)

        # 创建指标名索引表
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS meta_metric_index (
                id VARCHAR(255) PRIMARY KEY,
                metric_name VARCHAR(255) NOT NULL,
                meta_config_id VARCHAR(255) NOT NULL,
                FOREIGN KEY (meta_config_id) REFERENCES meta_config(id) ON DELETE CASCADE,
                INDEX idx_metric_name (metric_name)
            )
        """)

        conn.commit()
        cursor.close()
        conn.close()
        print("✅ MySQL表初始化完成")

    def _generate_id(self) -> str:
        return str(uuid.uuid4())

    # ========== CRUD 操作 ==========
    def create(self, entity: MetaConfig) -> str:
        """创建元数据配置,返回ID"""
        entity_id = self._generate_id()
        config_data = json.dumps(entity.to_dict(), ensure_ascii=False)

        conn = self._get_connection()
        cursor = conn.cursor()

        try:
            # 插入主数据
            cursor.execute(
                "INSERT INTO meta_config (id, config_data) VALUES (%s, %s)",
                (entity_id, config_data)
            )

            # 插入表索引
            if entity.tables:
                for table in entity.tables:
                    cursor.execute(
                        "INSERT INTO meta_table_index (id, table_name, meta_config_id) VALUES (%s, %s, %s)",
                        (self._generate_id(), table.name, entity_id)
                    )

            # 插入指标索引
            if entity.metrics:
                for metric in entity.metrics:
                    cursor.execute(
                        "INSERT INTO meta_metric_index (id, metric_name, meta_config_id) VALUES (%s, %s, %s)",
                        (self._generate_id(), metric.name, entity_id)
                    )

            conn.commit()
            print(f"✅ MySQL创建成功,ID: {entity_id}")
            return entity_id

        except Error as e:
            conn.rollback()
            print(f"❌ MySQL创建失败: {e}")
            raise e
        finally:
            cursor.close()
            conn.close()

    def get_by_id(self, id: str) -> Optional[MetaConfig]:
        """根据ID获取配置"""
        conn = self._get_connection()
        cursor = conn.cursor()

        cursor.execute("SELECT config_data FROM meta_config WHERE id = %s", (id,))
        result = cursor.fetchone()

        cursor.close()
        conn.close()

        if result:
            data = json.loads(result[0])
            return MetaConfig.from_dict(data)
        return None

    def get_all(self) -> List[MetaConfig]:
        """获取所有配置"""
        conn = self._get_connection()
        cursor = conn.cursor()

        cursor.execute("SELECT config_data FROM meta_config")
        results = cursor.fetchall()

        cursor.close()
        conn.close()

        return [MetaConfig.from_dict(json.loads(row[0])) for row in results]

    def update(self, id: str, entity: MetaConfig) -> bool:
        """更新配置"""
        conn = self._get_connection()
        cursor = conn.cursor()

        try:
            # 检查是否存在
            cursor.execute("SELECT id FROM meta_config WHERE id = %s", (id,))
            if not cursor.fetchone():
                print(f"❌ ID {id} 不存在")
                return False

            # 更新主数据
            config_data = json.dumps(entity.to_dict(), ensure_ascii=False)
            cursor.execute(
                "UPDATE meta_config SET config_data = %s WHERE id = %s",
                (config_data, id)
            )

            # 删除旧的索引
            cursor.execute("DELETE FROM meta_table_index WHERE meta_config_id = %s", (id,))
            cursor.execute("DELETE FROM meta_metric_index WHERE meta_config_id = %s", (id,))

            # 插入新的索引
            if entity.tables:
                for table in entity.tables:
                    cursor.execute(
                        "INSERT INTO meta_table_index (id, table_name, meta_config_id) VALUES (%s, %s, %s)",
                        (self._generate_id(), table.name, id)
                    )

            if entity.metrics:
                for metric in entity.metrics:
                    cursor.execute(
                        "INSERT INTO meta_metric_index (id, metric_name, meta_config_id) VALUES (%s, %s, %s)",
                        (self._generate_id(), metric.name, id)
                    )

            conn.commit()
            print(f"✅ MySQL更新成功,ID: {id}")
            return True

        except Error as e:
            conn.rollback()
            print(f"❌ MySQL更新失败: {e}")
            return False
        finally:
            cursor.close()
            conn.close()

    def delete(self, id: str) -> bool:
        """删除配置"""
        conn = self._get_connection()
        cursor = conn.cursor()

        try:
            cursor.execute("DELETE FROM meta_config WHERE id = %s", (id,))
            affected = cursor.rowcount
            conn.commit()
            if affected > 0:
                print(f"✅ MySQL删除成功,ID: {id}")
                return True
            else:
                print(f"❌ ID {id} 不存在")
                return False
        except Error as e:
            conn.rollback()
            print(f"❌ MySQL删除失败: {e}")
            return False
        finally:
            cursor.close()
            conn.close()

    def search_by_table(self, table_name: str) -> List[MetaConfig]:
        """根据表名搜索"""
        conn = self._get_connection()
        cursor = conn.cursor()

        cursor.execute("""
            SELECT DISTINCT mc.config_data 
            FROM meta_config mc
            JOIN meta_table_index mti ON mc.id = mti.meta_config_id
            WHERE mti.table_name LIKE %s
        """, (f"%{table_name}%",))
        results = cursor.fetchall()

        cursor.close()
        conn.close()

        return [MetaConfig.from_dict(json.loads(row[0])) for row in results]

    def search_by_metric(self, metric_name: str) -> List[MetaConfig]:
        """根据指标名搜索"""
        conn = self._get_connection()
        cursor = conn.cursor()

        cursor.execute("""
            SELECT DISTINCT mc.config_data 
            FROM meta_config mc
            JOIN meta_metric_index mmi ON mc.id = mmi.meta_config_id
            WHERE mmi.metric_name LIKE %s
        """, (f"%{metric_name}%",))
        results = cursor.fetchall()

        cursor.close()
        conn.close()

        return [MetaConfig.from_dict(json.loads(row[0])) for row in results]


# ========== 演示函数 ==========
def mysql_demo():
    print("=" * 60)
    print("🔷 MySQL 元数据管理演示")
    print("=" * 60)

    # 1. 初始化
    repo = MySQLMetaRepository(
        host="127.0.0.1",
        port=3306,
        user="root",
        password="happy123",  # 请修改为你的密码
        database="demo_meta_db"
    )

    # 2. 准备测试数据
    print("\n📦 准备测试数据...")

    columns = [
        ColumnConfig(
            name="region_id",
            role="primary_key",
            description="地区唯一标识",
            alias=["地区ID", "区域ID"],
            sync=False
        ),
        ColumnConfig(
            name="province",
            role="dimension",
            description="订单所属省份名称",
            alias=["省份", "省"],
            sync=True
        ),
        ColumnConfig(
            name="region_name",
            role="dimension",
            description="订单所属的大区名称,如华东,华南等",
            alias=["地区", "区域", "大区"],
            sync=True
        ),
        ColumnConfig(
            name="country",
            role="dimension",
            description="地区所属国家的名称",
            alias=["国家", "国家名称"],
            sync=True
        )
    ]

    tables = [
        TableConfig(
            name="dim_region",
            role="dim",
            description="地区维度表,用于描述订单发生的地理区域信息",
            columns=columns
        )
    ]

    metrics = [
        MetricConfig(
            name="GMV",
            description="全称Gross Merchandise value,表示所有订单的成交金额总和",
            relevant_columns=["fact_order.order_amount"],
            alias=["成交总额", "订单总额"]
        ),
        MetricConfig(
            name="AOV",
            description="全称Average Order value,表示所有订单的成交金额总的平均值",
            relevant_columns=["fact_order.order_amount", "fact_order.order_id"],
            alias=["平均单价", "平均订单金额"]
        )
    ]

    meta_config = MetaConfig(tables=tables, metrics=metrics)

    # 3. 创建
    print("\n📝 创建配置...")
    config_id = repo.create(meta_config)

    # 4. 查询
    print("\n🔍 根据ID查询...")
    retrieved = repo.get_by_id(config_id)
    if retrieved:
        print(f"  查询成功!")
        print(f"  表数量: {len(retrieved.tables) if retrieved.tables else 0}")
        print(f"  指标数量: {len(retrieved.metrics) if retrieved.metrics else 0}")

    # 5. 按表名搜索
    print("\n🔍 按表名搜索 'dim_region'...")
    results = repo.search_by_table("dim_region")
    print(f"  找到 {len(results)} 个配置")

    # 6. 按指标名搜索
    print("\n🔍 按指标名搜索 'GMV'...")
    results = repo.search_by_metric("GMV")
    print(f"  找到 {len(results)} 个配置")

    # 7. 更新
    print("\n📝 更新配置...")
    # 添加一个新指标
    new_metrics = metrics + [
        MetricConfig(
            name="订单量",
            description="所有订单的总数量",
            relevant_columns=["fact_order.order_id"],
            alias=["订单总数"]
        )
    ]
    updated_config = MetaConfig(tables=tables, metrics=new_metrics)
    repo.update(config_id, updated_config)
    print("  更新成功!")

    # 8. 获取所有
    print("\n📋 获取所有配置...")
    all_configs = repo.get_all()
    print(f"  共有 {len(all_configs)} 个配置")

    # 9. 删除
    print("\n🗑️ 删除配置...")
    repo.delete(config_id)
    print("  删除成功!")

    print("\n" + "=" * 60)
    print("✅ MySQL演示完成!")
    print("=" * 60)


if __name__ == "__main__":
    mysql_demo()
相关推荐
暖和_白开水13 小时前
数据分析agent(十三_3):docker mysql容器镜像服务的异常错误
mysql·docker·容器
ttwuai16 小时前
AI 生成后台改数据后,操作日志别只记按钮:Go + MySQL 怎么验
数据库·人工智能·mysql·golang
暖和_白开水17 小时前
数据分析agent(十三_2):业务层和持久层相互转换mapper
数据库·oracle·数据分析
韩楚风17 小时前
【参天引擎】一次宕机后的数据恢复,让我把 Cantian 持久化与恢复的六大机制全搞明白了
服务器·网络·数据库·分布式·mysql·架构·cantian
kingcjh9718 小时前
具身智能数据处理的工程化实践
人工智能·数据挖掘
Mico1818 小时前
MySQL 8.0.35 mydumper/myloader 备份恢复工具使用
mysql
JZC_xiaozhong21 小时前
销售订单如何自动同步多个业务系统?3种方案对比分析
大数据·数据分析·数据一致性·应用集成·数据孤岛解决方案·数据集成与应用集成
这个DBA有点耶1 天前
热点行更新:秒杀场景下一条UPDATE语句的锁等待与性能优化
数据库·sql·mysql·性能优化·database·dba
Y3815326621 天前
serpbase + Pandas + Jupyter 数据分析实战
jupyter·数据分析·pandas