表建模可视化

通常表的血缘关系、ER图、异构表建模都属于差不多类型,相较于关系模型,此类更于复杂,本次展开讲一讲异构表建模的实现及过程。

发现身边事儿、聊点周奇遇,我是沈二,期待奇遇的互联网灵魂~、一起聊天吹水,探索新的可能~wx:breathingss,入圈吧!

效果

先说说思路,需求是能够表现出不同数据库连接之间的关联汇聚,类似于一个ETL的汇聚过程,但能尽量在可视化部分表现出相关的关联;

  1. 表类似于组的概念,列类似于连接锚点的概念,此处的思路会有一些区别

  2. 有关于结构化存储的json存储,涉及的内容包含表信息源信息选择列信息连接信息 等,处理起来比较繁琐,因此有了转sql的处理

  3. Spark SQL 作为处理端,通过源和sql的汇聚计算处理,形成新的物理表存储

组件解析

前端

以vue2作为基础进行开发,相关涉及到自定义HTML节点需要引用@antv/x6-vue-shape

@antv/x6

经过该节点的自定义,实现了列的显示,因原本x6 markup的语法针对一些如选择、文本超长缩略显示等问题,最终选用灵活可控的这种方式进行,紧接着遇到一些问题。

1. 锚点的设置及显示交互问题

因为用的html表格,而与绘制的锚点需要进行重叠,因此对高度及相关的显示都要准确,

  • table-layout: fixed; 表格和列的宽度是由 tablecol 元素的宽度或第一行单元格的宽度来设置的。后续行中的单元格不会影响列的宽度。
  • 高度的问题,需要注意的是,如果没有内部子标签,高度经常会得不到理想高度,通过增加子标签的形式,对子标签设置高度进行解决
js 复制代码
  .data-table {
  width: 300px;
  background: #fff;
  table-layout: fixed;
  border-collapse: collapse;
  word-break: break-all;
  padding: 10px;

  }
  tr {
    td {
      white-space: nowrap;
      text-overflow: ellipsis;
      overflow: hidden;
      //  max-width: 100px;
      margin: 0;
      padding: 0;
      section {
        box-sizing: border-box;
       border-bottom: 2px dashed #eee;
        height: 24px;
        line-height:24px;
        overflow: hidden;
        white-space: nowrap;
      text-overflow: ellipsis;
      }
    }
  }

2. 附加操作问题

本来想着可能如果涉及LEFT JOIN ,RIGHT JOIN 会导致顺序及参照问题,需要设置一个表作为主参照,另外就是全选和反选的全局操作,因此增加了此项,最开始想放在点击或者其他地方,后面就索性放在了显眼的位置。

3.锚点交互的问题

一种是如此类进行以⚪的形式一行数据两个锚点,但这种感觉绘制的有点儿多 主要采用的是长方形的锚点,左右突出,设置层级的方式,保证了交互及显示的形式,通过样式融入。

waterline-sql-builder

用以将关系数据解析成sql语法表达式

js 复制代码
var SQLBuilder = require('waterline-sql-builder');
var compile = SQLBuilder({ dialect: 'postgres' }).generate;

// Compile a statement to obtain a SQL template string and an array of bindings.
var report = compile({
  select: ['id'],
  where: {
    firstName: 'Test',
    lastName: 'User'
  },
  from: 'users'
});

console.log(report);
//=>
//{
//  sql: 'select "id" from "users" where "firstName" = $1 and "lastName" = $2',
//  bindings: ['Test', 'User']
//}

转换方法

将x6产生的关系构建成sql的解析结构,从而获取sql表达式

js 复制代码
//获取sql模板字符串
    getSqlString() {
      const { cells } = this.graph.toJSON();
      if (cells.length <= 0) {
        return { sql: "", tables: [] };
      }
      var tables = [];
      const [firstNode, ...nodes] = cells.filter((it) => it.ports);
      const edgs = cells.filter((it) => !it.ports);
      var columns = [];
      var mapTables = new Map();
      var mapSource=new Map();
      [firstNode, ...nodes].map((it, index) => {
        const { items } = it.ports;
        tables.push(it.data);
        const reName = `T${(index+1)}`;
        mapTables.set(it.data.tableName, reName);
        // mapSource.set(it.data.tableName, reName);
        const column = items
          .filter((iv) => iv.data.checked)
          .map((iv) => `${reName}.${iv.data.columnName}`);
        // .map(iv=>`${it.data.tableName}.${iv.data.columnName}`)

        columns = columns.concat(column);
      });
      const getTplTb = (tableName) =>
        `${tableName} ${mapTables.get(tableName)} `;

      var sqlBuildSql = {
        select: columns,
        from: getTplTb(firstNode.data.tableName),
        join: [],
      };
      // [firstNode,...nodes].sort((a,b)=>{
      const pdt = [firstNode, ...nodes];
      for (let index = 0; index < pdt.length - 1; index++) {
        const a = pdt[index];
        const b = pdt[index + 1];
        const { data } = b;
        // a~b之间存在关系, 关联因为以第一个节点为依据,所以用第二个节点作为主体;
        var item = {};
        const links = edgs
          .filter(
            (it) =>
              (it.source.cell == a.data.tableId &&
                it.target.cell == b.data.tableId) ||
              (it.source.cell == b.data.tableId &&
                it.target.cell == a.data.tableId)
          )
          .map((it) => {
            //it.source.cell==a.data.tableId
            const func = (firstNode, type) => {
              let columnItemA = firstNode.ports.items.find(
                (iv) => iv.id == it[type].port
              );
              if (columnItemA) {
                // item[firstNode.data.tableName]=columnItemA.data.columnName;
                const name = mapTables.get(firstNode.data.tableName);
                item[name] = columnItemA.data.columnName;
              }
            };
            func(a, "source");
            func(a, "target");
            func(b, "source");
            func(b, "target");
          });
        //port
        if (!Object.keys(item).length > 0) {
          return;
        }
        var joinStr = {
          //  from: data.tableName,
          from: getTplTb(data.tableName),
          on: [item],
        };

        sqlBuildSql.join.push(joinStr);
      }
      //解析返回
      var sqlStr = SQL.generate(sqlBuildSql);
      //补充表源信息
      sqlStr.tables = tables;
      sqlStr.sql=sqlStr.sql.replaceAll('`','');
      return sqlStr;
    },

服务端

处理的本质其实在于有点儿类似于labda表达式那种,不同源的数据需要获取,再根据sql语法解析关联获取到实际的数据内容,然后再建表入库操作。

相关推荐
用户214118326360212 分钟前
dify案例分享-Dify+RSS 聚合 8 大平台实时热点,新闻获取效率飙升 300%
前端
百锦再14 分钟前
Razor编程中@Html的方法使用大全
前端·html
aiopencode15 分钟前
WebDebugX 如何助力跨平台 WebView 页面调试?开发者实战拆解
后端
啪叽17 分钟前
JavaScript可选链操作符(?.)的实用指南
前端·javascript
Ian在掘金17 分钟前
bat+python实现easy connect自动连接
前端·python
代码搬运媛20 分钟前
【react实战】如何实现监听窗口大小变化
前端·javascript·react.js
小桥风满袖22 分钟前
Three.js-硬要自学系列30之专项学习环境光
前端·css·three.js
大麦若叶茶22 分钟前
每天学习一点点之进程与线程、并行与并发
后端
Luckyfif25 分钟前
🤯由 性能指标 散发开来的 Performance API 被问爆了呀
前端·面试·性能优化
咸虾米28 分钟前
在uniCloud云对象内使用unipay的微信退款出现错误“uniPayCo.refund Error: token校验未通过”的解决方案
前端·后端