表建模可视化

通常表的血缘关系、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语法解析关联获取到实际的数据内容,然后再建表入库操作。

相关推荐
小青柑-2 小时前
Go语言中的接收器(Receiver)详解
开发语言·后端·golang
顾尘眠3 小时前
http常用状态码(204,304, 404, 504,502)含义
前端
C++小厨神4 小时前
Bash语言的计算机基础
开发语言·后端·golang
BinaryBardC4 小时前
Bash语言的软件工程
开发语言·后端·golang
王先生技术栈4 小时前
思维导图,Android版本实现
java·前端
凡人的AI工具箱4 小时前
每天40分玩转Django:Django DevOps实践指南
运维·后端·python·django·devops
土豆凌凌七4 小时前
GO:sync.Map
开发语言·后端·golang
蟹黄堡在逃员工5 小时前
消息队列MQ(一)
java·后端
小兵张健5 小时前
记一个 IDEA 关于 Git 的神坑
git·后端·intellij idea
@_@哆啦A梦5 小时前
Django中自定义模板字符串
后端·python·django