JavaScript深入之类型转换

JavaScript 是弱类型的语言,它的取值非常灵活。你期望一种类型值的时候,你可以提供任何类型的值,JavaScript 将根据需要自行转换类型,在 JavaScript 中通常将它们统称为 强制类型转换

ECMAScript 规范Type Conversion 章节中定义了转换规则。这里我们着重介绍 **ToPrimitiveToBoolean**、 ToNumberToString

ToPrimitive

在 JavaScript 中,想要将对象转换成原始值,必然会调用 toPrimitive()内部函数,那么它是如何工作的呢?

1
2
ToPrimitive(input [, PreferredType])
input 是输入的值,preferedType 是期望转换的类型,
输入类型 结果
Undefined 返回 input 自身
Null 返回 input 自身
Boolean 返回 input 自身
Number 返回 input 自身
String 返回 input 自身
Symbol 返回 input 自身
Object 返回该对象的默认值。具体过程请(通过内部操作 DefaultValue ,参见 ES5 规范 8.12.8 节)

如果 PreferredType 是 Number,执行顺序如下:

  1. 如果 input 为 primitive,返回。
  2. 否则,input 为 Object。调用 obj.valueOf()。如果结果是 primitive,返回。
  3. 否则,调用 obj.toString(). 如果结果是 primitive,返回。
  4. 如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。
  5. 如果 PreferredType 是 String,步骤 2 跟 3 互换,如果 PreferredType 为空,Date 类型默认为 String,其他都是 Number。

ToBoolean

ToBoolean 运算符根据下表将其参数转换为布尔值类型的值:

输入类型 结果
Undefined false
Null false
Boolean 结果等于输入的参数(不转换)。
Number 如果参数是 +0, -0, 或 NaN,结果为 false ;否则结果为 true。
String 如果参数参数是空字符串(其长度为零),结果为 false,否则结果为 true。
Object true
Symbol true

ToNumber

ToNumber 运算符根据下表将其参数转换为数值类型的值:

输入类型 结果
Undefined NaN
Null +0
Boolean 如果参数是 true,结果为 1。如果参数是 false,此结果为 +0。
Number 直接返回。
String 参见下文的文法和注释。
Symbol TypeError
Object 执行下列步骤:
1. 设 primValue 为 ToPrimitive( 输入参数 , hint Number)。
2.返回 ToNumber(primValue)。

ToString

ToString 运算符根据下表将其参数转换为字符串类型的值:

输入类型 结果
Undefined “undefined”
Null “null”
Boolean 如果参数是 true,那么结果为 “true”。
如果参数是 false,那么结果为 “false”。
Number 详见7.1.12.1NumberToString
String 直接返回 。
Symbol TypeError
Object 执行下列步骤:
1、设 primValue 为 ToPrimitive( 输入参数 , hint String)。
2、返回 ToString(设 primValue 为 )。

参考资料

Type Conversion and Testing
Type Conversion

JavaScript深入之引用类型

基本类型

JavaScript 变量可以用来保存两种类型的值:基本类型值和引用类型值。基本类型值指的是 简单的数据段,包括以下 6 种基本数据类型:Undefined、Null、Boolean、Number 、 String 和 Symbol。
基本类型保存在栈中,存储的是具体的值,是轻量级的数据存储方式。

引用类型

引用类型的值是保存在内存中的对象。与其他语言不同,JavaScript 不允许直接访问内存中的位置, 也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。 为此,引用类型的值是按引用访问的。

Object 类型

Object 是一个基础类型,其他所有类型都从 Object 继承了基本的行为。
创建 Object 实例的方式有两种。第一种是使用 new 操作符后跟 Object 构造函数,另一种方式是使用对象字面量表示法。

1
2
3
4
5
6
7
8
9
10
// new操作符创建对象
let person1 = new Object();
person.name = 'Zubin';
person.age = 18;

// 字面量表示法
let person2 = {
name: 'Zubin',
age: 18,
};

Array 对象

除了 Object 之外,Array 类型恐怕是 ECMAScript 中最常用的类型了。而且,ECMAScript 中 的数组与其他多数语言中的数组有着相当大的区别。

  • ECMAScript 数组的每一项可以保存任何类型的数据。
  • ECMAScript 数组的大小是可以动态调整的,即可以随着数据的添加自动增长以容 纳新增数据。

方法:

  1. Array.from()
    从类数组对象或者可迭代对象中创建一个新的数组实例。

  2. Array.isArray()
    用来判断某个变量是否是一个数组对象。

  3. Array.of()
    根据一组参数来创建新的数组实例,支持任意的参数数量和类型。

数组实例的常用方法:

方法名称 说明
concat 用于连接两个或更多的数组并返回结果,arr1.concat(arr2)
join 把数组的所有元素放入一个字符串,元素通过制定的分隔符进行分离 arr1.join(‘,’)
pop 删除并返回数组中的最后一个元素 arr1.pop()
push 向数组的末尾添加一个或更多元素,并返回新的长度 arr1.push(1)
reverse 颠倒数组中的元素顺序,arr1.reverse()
shift 删除并返回数组中的第一个元素 arr1.shift()
slice 从某个已有的数组返回指定的元素
sort 对数组的元素进行排序 arr1.sort()
splice 删除元素,并向数组中添加新元素
toString 把数组转成字符串 arr1.toString()
toLocaleString 把数组转换为本地字符串 arr1.toLocaleString()
valueOf 返回数组对象的原始值

Date 对象

创建 Date 实例用来处理日期和时间。Date 对象基于 1970 年 1 月 1 日(世界标准时间)起的毫秒数。

1
2
3
4
5
6
7
8
var today = new Date();
var today = new Date(1453094034000); // by timestamp(accurate to the milliseconds)
var birthday = new Date('December 17, 1995 03:24:00');
var birthday = new Date('1995-12-17T03:24:00');
var birthday = new Date(1995, 11, 17);
var birthday = new Date(1995, 11, 17, 3, 24, 0);

var unixTimestamp = Date.now(); // in milliseconds

方法

  1. Date.now()
    返回自 1970-1-1 00:00:00 UTC (世界标准时间)至今所经过的毫秒数。
  2. Date.parse()
    解析一个表示日期的字符串,并返回从 1970-1-1 00:00:00 所经过的毫秒数。
  3. Date.UTC()
    接受和构造函数最长形式的参数相同的参数(从 2 到 7),并返回从 1970-01-01 00:00:00 UTC 开始所经过的毫秒数。

RegExp 类型

类型是 ECMAScript 支持正则表达式的一个接口,提供了最基本的和一些高级的正则表 达式功能。

1
2
3
4
5
6
7
8
9
10
11
var regex1 = /\w+/;
var regex2 = new RegExp('\\w+');

console.log(regex1);
// expected output: /\w+/

console.log(regex2);
// expected output: /\w+/

console.log(regex1 === regex2);
// expected output: false

Function 类型

Function 构造函数 创建一个新的 Function 对象。 在 JavaScript 中, 每个函数实际上都是一个 Function 对象。

1
2
3
4
var sum = new Function('a', 'b', 'return a + b');

console.log(sum(2, 6));
// expected output: 8

参考文档

MDN JavaScript
JavaScript 高级程序设计(第 3 版)

JavaScript深入之原生函数

JavaScript 为基本数据类型值提供了封装对象,称为原生函数,常见的原生函数包括

  • String()
  • Number()
  • Boolean()
  • Array()
  • Object()
  • Function()
  • RegExp()
  • Date()
  • Error()
  • Symbol()

它们可以被当作构造函数来使用, 但其构造出来的对象可能会和我们设想的有所 出入

1
2
3
4
let a = new String('abc');
typeof a; // 是"object",不是"String"
a instanceof String; // true
Object.prototype.toString.call(a); // "[object String]"

通过构造函数(如 new String(“abc”) )创建出来的是封装了基本类型值(如 “abc” )的封 装对象。

内部属性[[Class]]

所有 typeof 返回值为 “object” 的对象(如数组)都包含一个内部属性 [[Class]] (我们可 以把它看作一个内部的分类,而非传统的面向对象意义上的类)。这个属性无法直接访问, 一般通过 Object.prototype.toString(..) 来查看。例如:

1
2
3
4
5
6
7
8
9
Object.prototype.toString.call([1, 2, 3]); // "[object Array]"
Object.prototype.toString.call(/regex-literal/i); // "[object RegExp]"

Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"

Object.prototype.toString.call('abc'); // "[object String]"
Object.prototype.toString.call(42); // "[object Number]"
Object.prototype.toString.call(true); // "[object Boolean]"

内部属性和创建该对象的内建原生构造函数相对应,虽然 Null() 和 Undefined() 这样的原生构造函数并不存在,但是内部属性值仍 然是 “Null” 和 “Undefined” 。
其他基本类型值(如字符串、数字和布尔)的情况有所不同,它们的值被各自的封装对象自动包装,所以它们的内部属性值分别为 “String” 、 “Number” 和 “Boolean” 。

封装

原生函数为基本数据类型值提供了该子类型所特有的方法和属性(如:String#trim() 和 Array#concat(..))。

对于简单标量基本类型值,比如 “abc” ,如果要访问它的 length 属性或 String.prototype 方法, JavaScript 引擎会自动对该值进行封装(即用相应类型的封装对象来包装它)来实现对这些属性和方法的访问。

1
2
3
4
5
6
7
8
9
var a = 'abc';

a.length; // 3
a.toUpperCase(); // "ABC"

var b = new Boolean(false);
if (!b) {
console.log('Oops'); // 执行不到这里
}

拆封

如果想要得到封装对象中的基本类型值,可以使用 valueOf() 函数:

1
2
3
4
5
6
7
var a = new String('abc');
var b = new Number(42);
var c = new Boolean(true);

a.valueOf(); // "abc"
b.valueOf(); // 42
c.valueOf(); // true

参考文档

你不知道的 JavaScript(中卷)

JavaScript深入之数据类型

一、内置类型

最新的 ECMAScript 标准定义了 7 种数据类型:

  • 空值(null)
  • 未定义(undefined)
  • 布尔值(boolean)
  • 数字(number)
  • 字符串(string)
  • 符号(symbol,ES6 中新增)
  • 对象(object)

除对象为“复杂类型”之外,其他统称为“基本类型”。

阅读更多

JavaScript之常量

简单类型常量

众所周知 ES6 新增的 const 关键字可以用来声明常量。常量是块级作用域,很像使用 let 语句定义的变量。常量的值不能通过重新赋值来改变,并且不能重新声明。

1
2
const name = 'zubin';
name = 'zhang'; //TypeError: Assignment to constant variable.

const 实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动,对于基本数据类型(Number、String、Boolean 等),值就保存在变量指向的内存地址,因此等同于常量。
对于复合类型(Object、Array 等)变量指向的内存地址,保存的只是一个指向实际数据的指针,const 只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。

1
2
3
4
5
6
7
8
9
10
11
12
13
const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop; // 123

// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError

const a = [];
a.push('Hello'); // 可执行
a.length = 0; // 可执行
a = ['Dave']; // TypeError

复合类型常量

如果真的想将对象冻结,应该使用 Object.freeze 方法,冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。该方法返回被冻结的对象。

1
2
3
4
5
const foo = Object.freeze({});

// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;

除了将对象本身冻结,对象的属性也应该冻结。

1
2
3
4
5
6
7
8
var constantize = obj => {
Object.freeze(obj);
Object.keys(obj).forEach((key, i) => {
if (typeof obj[key] === 'object') {
constantize(obj[key]);
}
});
};

参考文档

阮一峰:ECMAScript 6 入门
MDN:Object.freeze

egg中egg-sequelize和egg-mongoose不能同时使用

今天在项目中加入 egg-mongoose 插件,配置好 config 文件和 plugin.ts 信息后,运行代码,在加载 mongoose 插件的时候报错了,错误信息:

1
nodejs.TypeError: Cannot assign to read only property 'model' of object '#<Application>'

回看下代码发现原有的 egg-sequelize 已经挂载 model 到 app 上面

去掉其中一个插件都可以正常运行,看来 egg-sequelize 和 egg-mongoose 插件冲突了,excuse me?

错误原因

浏览 egg-sequelize 源码发现已经定义好 model 不能被重写

1
2
3
4
5
Object.defineProperty(model, delegate[len - 1], {
value: sequelize,
writable: false,
configurable: true,
});

egg-mongoose 挂载 model 到 app 上时就会报Cannot assign to read only property错误了。

解决方案

  1. 给官方提 issue:把问题和解决方案描述一下;
  2. 降级方案:去掉 MongoDB 的 model 层使用其他插件如 egg-mongo-native 或者自己封装一个;
  3. model 层使用一种数据库,将使用 MongoDB 和 MySQL 的业务拆分为两个服务;

netstat命令详解

功能说明

netstat 命令用来打印 Linux 中网络系统的状态信息,它可以用来查询整个 Linux 系统的网络情况,包括 tcp,udp 以及 Unix 套接字;另外它还能列出路由表,接口状态和多播成员等信息。

选项

-a 或–all:显示所有连线中的 Socket;
-A<网络类型>或–<网络类型>:列出该网络类型连线中的相关地址;
-c 或–continuous:持续列出网络状态;
-C 或–cache:显示路由器配置的快取信息;
-e 或–extend:显示网络其他相关信息;
-F 或–fib:显示 FIB;
-g 或–groups:显示多重广播功能群组组员名单;
-h 或–help:在线帮助;
-i 或–interfaces:显示网络界面信息表单;
-l 或–listening:显示监控中的服务器的 Socket;
-M 或–masquerade:显示伪装的网络连线;
-n 或–numeric:直接使用 ip 地址,而不通过域名服务器;
-N 或–netlink 或–symbolic:显示网络硬件外围设备的符号连接名称;
-o 或–timers:显示计时器;
-p 或–programs:显示正在使用 Socket 的程序识别码和程序名称;
-r 或–route:显示 Routing Table;
-s 或–statistice:显示网络工作信息统计表;
-t 或–tcp:显示 TCP 传输协议的连线状况;
-u 或–udp:显示 UDP 传输协议的连线状况;
-v 或–verbose:显示指令执行过程;
-V 或–version:显示版本信息;
-w 或–raw:显示 RAW 传输协议的连线状况;
-x 或–unix:此参数的效果和指定”-A unix”参数相同;
–ip 或–inet:此参数的效果和指定”-A inet”参数相同。

常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#列出所有端口(LISTEN,ESTABLISHED)
netstat -a #列出所有端口
netstat -at #列出所有tcp端口
netstat -au #列出所有udp端口

#查看程序运行的端口(LISTEN,ESTABLISHED)
netstat -ap
netstat -ap | grep '程序名'
netstat -ap | grep 8080

#显示路由表的信息
netstat -r

#持续输出netstat信息
netstat -c #每隔一秒输出网络信息

#显示网络接口列表
netstat -i

MongoDB-aggregate用法

MongoDB 中聚合(aggregate)方法可以对集合中的文档进行变换和组合,主要用于处理数据。语法:

1
db.collection.aggregate(pipeline, options);

管道操作符

MongoDB 的聚合管道将 MongoDB 文档在一个管道处理完毕后将结果传递给下一个管道处理,管道操纵是可以重复的。

管道聚合阶段:

1
2
3
4
5
6
7
$project:包含、排除、重命名和显示字段
$match:查询,需要同find()一样的参数
$limit:限制结果数量
$skip:忽略结果的数量
$sort:按照给定的字段排序结果
$group:按照给定表达式组合结果
$unwind:分割嵌入数组到自己顶层文件<br>

group 查询操作符:

1
2
3
4
5
6
7
8
$sum 总结从集合中的所有文件所定义的值.
$avg 从所有文档集合中所有给定值计算的平均.
$min 获取集合中的所有文件中的相应值最小.
$max 获取集合中的所有文件中的相应值的最大.
$push 值插入到一个数组生成文档中.
$addToSet 值插入到一个数组中所得到的文档,但不会创建重复.
$first 根据分组从源文档中获取的第一个文档。通常情况下,这才有意义,连同以前的一些应用 “$sort”-stage.
$last 根据分组从源文档中获取最后的文档。通常,这才有意义,连同以前的一些应用 “$sort”-stage.

附加选项

  • explain:布尔值,指定返回结果是否显示该操作的执行计划
  • allowDiskUse:布尔值,指定该聚合操作是否使用磁盘。
    每个阶段管道限制为 100MB 的内存。如果一个节点管道超过这个极限,MongoDB 将产生一个错误。为了能够在处理大型数据集,可以设置 allowDiskUse 为 true 来在聚合管道节点把数据写入临时文件。这样就可以解决 100MB 的内存的限制。
  • cursor
  • maxTimeMS
  • bypassDocumentValidation
  • readConcern
  • collation

参考文档

https://docs.mongodb.com/manual/reference/method/db.collection.aggregate/#db.collection.aggregate
http://www.mongodb.org.cn/tutorial/19.html

Redis基础数据类型

Redis 有 5 种基础数据结构,分别为:String (字符串)、Hash (哈希)、List (列表)、Set (集合) 和 Sorted Set (有序集合)

String (字符串)

String 数据结构是简单的 key-value 类型。
最常见的用途就是缓存信息。我们可以将需要缓存结构体使用 JSON 序列化成字符串来缓存,取出来的时候再反序列化一下。

常用命令 说明 示例
set 设置指定 key 的值 set name zubin
get 获取指定 key 的值 get name
del 删除指定 key del key
mset 批量设置指定 key 的值 mset name zubin age 18
mget 批量获取指定 key 的值 mget name age
setex 如果设置指定 key 的值和过期时间(秒) setex name 5 zubin
setnx 只有 key 不存在时设置 key 的值 setnx name zubin
incr 将 key 中存储的数字值增 1 incr age
incyby 将 key 所储存的值加上给定的增量值 incrby age 5
decr 将 key 中存储的数字值减 1 decr age
strlen 返回指定 key 中 value 的长度 strlen name
append 将字符串追加到指定 key 中值的末尾。 append name 1

Hash (哈希)

Redis hash 是一个 string 类型的 field 和 value 的映射表,是无序字典, 特别适合用于存储对象。每个 hash 可以存储2^32 - 1(4294967295) 键值对。

常用命令 说明 示例
hset 设置 hash 表中 field 的值 hset user name zubin
hmset 设置 hash 表中多个 field 的值 hmset user name zubin age 18
hsetnx field 不存在时设置哈希表字段的值 hsetnx user name zubin
hget 获取指定字段的值 hget user name
hmget 获取多个字段的值 hmget user age name
hgetall 获取 hash 表中所有字段的值 hgetall user
hdel 删除一个或多个 hash 字段 hdel user age
hexists 判断 hash 表中指定的字段是否存在 hexists user age
hincrby 为 hash 表中指定的整数值字段加上增量 hincrby user age 5
hkeys 获取 hash 表中所有的字段 hkeys user
hvals 获取 hash 表中所有的值 hvals user
hlen 获取 hash 表中字段的数量 hlen user

List (列表)

Redis 列表是简单的字符串列表,按照插入顺序排序,注意它是链表而不是数组,一个列表最多可以包含 2^32 - 1 个元素 (4294967295, 每个列表超过 40 亿个元素)。
list 的插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为 O(n)。
list 常用来做异步队列使用,你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

常用命令 说明 示例
lpush 将一个或多个值插入到列表头部 lpush skills Node Golang Java
lpushx 将一个或多个值插入到已存在的列表头部 lpushx skills Node Golang Java
lpop 移除列表第一个元素 lpop skills
blpop 移除列表第一个元素, 列表为空会阻塞,直到超时或者有可移除的元素 blpop skills 100
rpush 将一个或多个值插入到列表尾部 rpush skills Node Golang Java
rpushx 将一个或多个值插入到已存在的列表尾部 rpushx skills Node Golang Java
rpop 移除列表最后一个个元素 rpop skills
brpop 移除列表最后一个个元素, 列表为空会阻塞,直到超时或者有可移除的元素 brpop skills 100
brpoplpush 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它 brpoplpush skills list1 500
lindex 通过索引获取列表中的元素 lindex skills 1
lset 通过索引来设置元素的值 lset skills 0 Python
llen 获取列表长度 llen skills
lrange 获取列表指定范围内的元素 lrange skills 1 2
lrem 根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素 lrem skills 0 node
ltrim 修剪列表,只保留指定区间内的元素 ltrim skills 1 2

Set (集合)

Redis 的 Set 是 String 类型的无序集合。它内部的键值对是无序的唯一的,这就意味着集合中不能出现重复的数据,集合中最大的成员数为 2^32 - 1 (4294967295)。
Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

常用命令 说明 示例
sadd 向集合添加一个或多个元素 sadd skills python go java go
scard 获取集合的成员数 scard skills
sismember 判断是否集合中的成员 sismember skills go
smembers 返回集合中的所有成员 smembers skills
spop 随机移除并返回集合中的一个元素 spop skills
sinter 返回指定集合的交集 sinter key1 key2
sunion 返回指定集合的并集 sunion key1 key2
sdiff 返回指定集合的差集 sdiff key2 key2

Sorted Set (有序集合)

Redis 有序集合和集合一样也是 string 类型元素的集合,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 score(分数),代表这个 value 的排序权重。集合中最大的成员数为 2^32 - 1(4294967295)。
Redis 正是通过分数来为集合中的成员进行从小到大的排序,有序集合的成员是唯一的,但分数(score)却可以重复。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

常用命令 说明 示例
zadd 添加一个或多个成员或者更新已存在成员的分数 zadd userscore zubin 1
zcard 获取有序集合的成员数 zcard userscore
zcount 获取有序集合指定区间分数的成员数 zcount userscore 1 3
zincrby 给指定成员的分数加上增量 zincrby userscore 1 zubin
zrange 返回指定区间内的成员 zrange userscore 0 -1
zrangebyscore 返回 score 区间内的成员 zrangebyscore userscore -inf +inf
zrem 移除一个或多个元素 zrem userscore 0 -1
zrevrange 返回指定分数区间内的成员,按分数递减 zrevrange userscore 0 -1
zrevrank 返回成员 member 的排名 zrevrank userscore zubin
zscore 返回 score 的 score 值 zscore userscore zubin

数据库设计-约束

数据库约束是为了保证数据的完整性(正确性)而实现的一套机制。主要分为五大约束:

  1. 主键约束(Primay Key Coustraint): 唯一性,非空性
1
2
3
# 添加主键约束(将UserId作为主键)
alter table UserId
   add constraint PK_UserId primary key (UserId);
  1. 唯一约束 (Unique Counstraint):唯一性,可以空,但只能有一个
1
2
3
# 添加唯一约束(身份证号唯一,因为每个人的都不一样)
alter table UserInfo
   add constraint UQ_IDNumber unique(IdentityCardNumber);
  1. 检查约束 (Check Counstraint):对该列数据的范格式的限制(如:年性别等)
1
2
3
4
5
# 添加检查约束 (对年龄加以限定 20-40 岁之间)
alter table UserInfo
   add constraint CK_UserAge check (UserAge between 20 and 40);
alter table UserInfo
   add constraint CK_UserSex check (UserSex=’男’ or UserSex=’女′);
  1. 默认约束 (Default Counstraint):该数据的默认值
1
2
3
# 添加默认约束(如果地址不填 默认为“地址不详”)
alter table UserInfo
   add constraint DF_UserAddress default (‘地址不详’) for UserAddress;
  1. 外键约束 (Foreign Key Counstraint):需要建立两表间的关系并引用主表的列
1
2
3
# 添加外键约束 (主表 UserInfo 和从表 UserOrder 建立关系,关联字段 UserId)
alter table UserOrder
   add constraint FK_UserId_UserId foreign key(UserId)references UserInfo(UserId);
阅读更多