Beaver's Blog

Freedom Openness Inclusion

0%

前言

tuplelist作为Python中的重要且频繁使用的基础数据类型,我们有必要进一步了解二者之间相同点和差异。

list和tuple基础知识

  • 什么是list和tuple?

    实际上,列表和元组都是一个可以放置任意数据类型的有序集合。

  • 二者的区别

    列表是动态的,长度大小不固定,可以随意地增加、删减或者改变元素。
    而元组是静态的,长度大小固定,无法增加删减或者改变。

如果tuple想要对元素进行增加、删改操作时,需要新创建一个tuple实现这些操作。而list只需使用append方法,将新的元素添加到列表中,而不会新建一个列表。

1
2
3
4
5
t = (11, 12, 13, 14)
l = [11, 12, 13, 14]
# adds an element to the tuple
t1 = t + (5,)
l.append(5)

tuple和list都支持负数索引、切片、嵌套等操作,而且tuple和list还可以通过list()及tuple()相互转换。

1
2
3
4
5
6
7
8
9
10
11
t=(1,2,3)
l=[1,2,3]
# 负数索引
t[-1] -->3
l[-2] -->2
# 切片
t[0,2] -->(1,2)
l[0,1] -->[1]
# 相互转换
l2=list(t)
t2=tuple(l)

list和tuple的常见内置方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# List
list = [] # create a new list

list[i] = val
list[i:j] = otherlist # replace ith to jth-1 elements with otherlist
del list[i:j]

list.append(item) # even the item is a list, but the list as a element in list
list.extend(another_list)
list.insert(index, item) #
list.pop() # returns and removes last element from the list
list.pop(i) # returns and removes i-th element from the list
list.remove(i) # removes the first item from the list whose value is i
list.copy() # returns a copy of the list
list1 + list2 # combine two list

list.reverse() # reverses the elements of the list in-place
list.count(item)

zip(list1, list2, ...) # returns list of tuples with n-th element of both list1
# and list2 and ...listn
list.sort() # sorts in-place, returns None
sorted(list) # returns sorted copy of list
",".join(list) # returns a string with list elements seperated by comma
# Tuple
tuple1 + tuple2 # return a new tuple contains tuple1 and tuple2
tuple.count() # Returns the number of times a specified value occurs in a tuple
tuple.index() # Searches the tuple for a specified value and returns the position of
# where it was found

二者的性能差异和使用场景

tuple和list的存储方式

上面说过,由于tuple和list特性不同,势必会影响二者的存储方式。

1
2
3
4
t = (1,2)
t.__sizeof__() -->40
l = [1,2]
l.__sizeof__() -->56

从上面我们可以看到,tuple和list存储了相同的元素下,list要比tuple多使用16字节的空间。

由于列表是动态的,所以它需要存储指针,来指向对应的元素(上述例子中,对于int 型,8 bytes)。另外,由于列表是可变的,所以需要额外存储已经分配的长度大小(8 bytes),这样才可以实时追踪列表空间的使用情况,当空间不足时,及时分配额外空间。

对于元组而言,由于元组长度大小固定,元素不可变,所以存储空间也是固定的。

当tuple和list存储的元素越来越多的时候,二者的差异的性能差异也将进一步体现。

性能差异

通过了解列表和元组存储方式的差异,我们可以得出结论:元组要比列表更加轻量级一些,所以总体上来说,元组的性能速度要略优于列表。

初始化相同元素的tuple和list,tuple速度更快。对于索引操作,二者没有明显差异。

如果想进行增加、删改操作时,list性能更好。因为这些操作,tuple都必须通过新建tuple实现。

使用场景

经过以上对比,该如何使用哪种类型?具体问题分析。

如果存储的数据和数量不变,应该选取tuple;如果存储的数据或数量是可变的,应选取list。

list和tuple的底层实现

list和tuple的内部实现都是array的形式,list因为可变,所以是一个over-allocate的array,tuple因为不可变,所以长度大小固定。

t=list(),t2=[]同样是新建一个列表,二者有什么区别?

主要在于list()是一个function调用,Python的函数调用会创建stack,并且进行一系列参数检查等操作,有一定的开销,而[]是一个内置的C函数,可以直接被调用,因此效率高。

总结

总的来说,列表和元组都是有序的,可以存储任意数据类型的集合,区别主要在于下面这两点:

列表是动态的,长度可变,可以随意的增加、删减或改变元素。列表的存储空间略大于元组,性能略逊于元组。
元组是静态的,长度大小固定,不可以对元素进行增加、删减或者改变操作。元组相对于列表更加轻量级,性能稍优。

通过本文的对listtuple的对比,想要说明的是:不需要过度强调孰优孰劣,应在合适的场景下选择合适的数据类型。

简言之,没有不合适的数据类型,只有使用了不合适的场景。

References

COUNT的作用

COUNT函数在数据库可以用来统计行数,可以是表的总行数,也可以用来统计某一列的行数,或者符合条件的结果的行数。现在来看下MySQL官方文档对函数的功能描述:

Returns a count of the number of NON-NULL values of expr in the rows retrieved by a SELECT statement. The result is a BIGINT value. If there are no matching rows, COUNT() returns 0.
COUNT(DISTINCT exp): Returns a count of the number of rows with different non-NULL expr values.

注意:使用COUNT([DISTINCT] expr)统计expr时,统计的是non-null的行数。

以下是COUNT函数的具体使用:

1
2
3
4
5
6
7
8
9
-- 统计表的总行数
SELECT COUNT(*) FROM T1
-- 统计列的数量
SELECT COUNT(COLUMN1) FROM T1
-- 带有过滤条件的统计
SELECT COUNT(*) FROM T1,T2 WHERE T1.ID=T2.ID AND T1.ID>100;
SELECT COUNT(T1.ID) FROM T1,T2 WHERE T1.ID=T2.ID AND T1.ID<1000;
-- DISTINCT去重
SELECT COUNT(DISTINCT C1) FROM T1;

COUNT(1) VS COUNT(*)

COUNT(1)COUNT(*)在功能上一样,都是用来统计行数。但这里要讨论的区别,主要是指数据库性能上的差异。有些人可能会觉得,COUNT(1)会比COUNT(*)性能更好一些。但是,根据MySQL官方文档的解释,在Innodb存储引擎中,其实二者并没有性能上的差异[1]。以下是MySQL官方解释:

InnoDB handles SELECT COUNT(*) and SELECT COUNT(1) operations in the same way. There is no performance difference.

COUNT函数与存储引擎

对于Innodb存储引擎,执行COUNT(*)统计行数,需要对表一行一行的计数统计。但对于采用了MyISAM存储引擎的表,COUNT(*)的速度非常快,这是因为MySQL可以利用MyISAM的存储引擎特性(MyISAM 引擎把一个表的总行数存在了磁盘上)直接获得这个值,但这也只适用于没有WHERE条件的COUNT(*),即SELECT COUNT(*) FROM tMyISAM在处理使用了过滤条件的统计或统计部分列,其实速度与别的存储引擎并没有什么不同[1]

For MyISAM tables, COUNT(*) is optimized to return very quickly if the SELECT retrieves FROM one table, no other columns are retrieved, and there is no WHERE clause.

  • 如何从事务的角度分析 InnoDBCOUNT(?)

    由于 InnoDB 引擎支持事务的特性,数据表中的一行记录,可能有多个版本 (row),每个版本有自己的 row trx_id。所以,对于InnoDB引擎来说,不同事务中count(?)的结果可能是不一致的。

总结

使用了MyISAM 引擎的表, COUNT(*) 速度很快,但是MyISAM引擎不支持事务,而且含有 where 条件 COUNT与别的引擎没什么不同。
show table status 命令虽然返回很快,但是结果不够准确[2]
对使用了InnoDB 引擎的表直接 COUNT(*) 的结果很准确,但会遍历全表,这可能会导致性能问题。
效率对比:count(*)~count(1)>count(column_x)

参考文档

[1] MySQL 8.0 Reference Manual: Aggregate Function Descriptions - count

[2] MySQL 8.0 Reference Manual: SHOW TABLE STATUS Statement

前言

不像JavaPython等编程语言直接或间接的提供了对set的支持。在Go语言中,内置数据类型并没有提供对集合(set)的支持,但是有相当多的场景下都会用到这种数据类型。这时候,就需要我们自己实现这种类型。

实现

所谓集合,不过是由相同或不同数据类型不同元素所组成的。

对于如何实现set,我们可以考虑根据Go语言已有的数据类型,简单的实现set以满足我们的需要。Go语言内置数据类型中有map数据类型,map的底层是依靠的hash实现的,效率非常高,且map数据类型的key是不会重复的,正好这个特性也能满足set元素不重复的特性。由于map数据类型的形式是map[key_type]value_type,即:map是由key-value组成的,而set只包含元素,没有key-value中的value。但是,我们可以考虑使用Go中空结构体struct{}代替value

我们先声明一个map变量:

1
2
3
var mySet = make(map[int]struct{}, 2)
mySet[1] = struct{}{}
mySet[2] = struct{}{}

上面虽然使用了空结构体struct{}作为set的”value“,但是使用时要写很多的struct{}{},这实在不够简约。现在让我们使用none作为空结构体struct{}的别名。

1
2
3
4
type none struct{}
var mySet = make(map[int]none, 2)
mySet[10] = none{}
mySet[20] = none{}

经过以上步骤,基本实现了set的雏形,我们还要做一些收尾工作:定义一个set类型。

1
type set map[int]none

现在我们就可以使用set了,首先来实现一些set的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var mySet = make(set, N)
// 获取set的元素个数
// 通过内置函数len也可以达到这个目的
func getSetLength(mySet set) int {
return len(mySet)
}
// 判断元素是否在集合中
func elementInSet(mySet set, key key_type) bool {
if _, ok := mySet[key]; ok {
return ok
}
return false
}
// 删除set中元素操作
// 因为定义的set其实根据map实现的,所以对set的操作遵循对map的操作
// 因此,删除set中元素的操作就是执行删除map中元素的操作,使用内置函数delete就可以
delete(mySet, key)
// set新增元素
// 其实,直接执行set[key] = none就能实现set新增元素操作,但还是写一个函数吧。
func add(mySet set, key key_type) set {
mySet[key] = none{}
return mySet
}

上面只是简单实现了一些关于set操作的函数,其实我们可以根据需要实现更多对set的操作。

总结

借助Go中的map,我们算以曲线救国的方式实现了set这个缺失的基础数据类型。虽然Go相比与其他语言足够的简约,设计上的简约可能意味着使用实现上的繁琐,就像set的缺失,其实是让人感觉相当遗憾的事,使得我们想要使用它,就得以自己的方式实现,并需要实现这个类型相关的函数或方法。

前言

本来想给这篇文章叫SQL编程规范的,感觉大家有把SQL当作编程语言吗?索性叫SQL代码规范好了,废话不多说,下面开始说一下SQL的代码风格。

对于很多的编程语言来说,大家总有个人的或者通用的代码风格,其实这对于SQL同样也不例外。好的代码风格可以提高代码的可读性,降低维护成本。有些编程语言(如Golang)内置了格式化代码命令gofmt,可以非常方便的格式化代码。对于代码的风格,大家的看法总是不尽相同,每个人可能都有自己的风格,如何缩进,怎么变量命名等等。当一种代码规范形成一种共识后,我想这才能最大程度上提高代码的可读性、可维护性。

SQL需要有代码规范吗

正如上面所说,好的代码风格可以提高代码的可读性。其实,SQL作为一种被经常使用的语言,好的代码规范对SQL使用来说非常重要。我见过很多SQL代码写的很漂亮的,读起来让人赏心悦目;也见过很多糟糕的SQL代码,这种糟糕的代码,让我一半的时间都在理解这到底写的什么。所以,我们也要对SQL的代码规范重视起来,就像重视其他编程语言代码规范那样。

SQL的代码规范

我根据自己的喜好及大家经常使用的规范,整理了一份SQL规范,仅供参考。

编码总体原则

  • SQL语句应正确、规范、高效
  • 应避免写较为复杂的SQL语句
  • 代码要整体层次分明及结构化
  • 代码中应有必要的注释以增强代码的可读性
  • 代码应充分考虑数据库性能
  • 使用SELECT语句时,应指出列名,不应使用列的序号或者用“*”替代所有列名
  • 如果 SQL 语句连接多表时,应使用表的别名来引用列

根据编码总体原则,详细说一下SQL的代码规范。

大小写

SQL中的关键字、内置函数应大写,其余部分(列名、别名、表名等)应小写,以便于理解。不应该出现大小写混着写的情况。

SQL常见的关键字:SELECT,UPDATE,DELETE,INSERT,WHERE,CASE WHEN END,AS,INTO,GROUP BY,HAVING等。

1
2
3
4
5
6
-- good sql
SELECT id FROM t
SELECT id, COUNT(1) cnt FROM t GROUP BY id HAVING COUNT(1)>1
-- bad sql
select ID from T
Select Id From T

指定列名

无论是对于查询,插入还是排序等操作,都应指定列名。

  • 查询

    查询时应避免使用*代替所有列,只指定使用的列名。

    1
    2
    3
    4
    -- Good sql
    SELECT C1, C2 FROM T;
    -- Bad sql
    SELECT * FROM T
  • 插入

    使用INSERT语句插入数据时,也应指定列名。当然,插入数据时,我们会指定列名,不然数据可能插入到别的字段。

  • 排序、分组

    排序、分组也应遵循指定列名

    1
    2
    3
    4
    -- Good sql
    SELECT C1, C2 FROM T ORDER BY C1, C2;
    -- Bad sql
    SELECT C, C2 FROM T ORDER BY 1, 2;

注释

好的SQL编码会添加注释,以提高可读性。对于SQL的注释做以下规范:

  • 每条SQL语句均应添加注释说明;
  • 每条SQL语句的注释单独成行,并放在语句的前面;
  • 字段注释紧跟在字段后面;
  • 对不易理解的分支条件表达式应添加注释,便于理解;
  • 对重要的计算添加注释,以说明其功能。

命名

SQL中不像其他其他编程语言中,需要大量对变量、函数等对象命名,但是这不意味着命名在写SQL中不重要。sql中有命名情况的有:列别名、表别名、子查询别名等,命名应具有实际的含义且应小写,避免使用中文。太长的命名应使用_隔开,例如:thisIsAColumn,应该这么写this_is_a_column,以提高可读性。

1
2
3
4
5
6
7
8
9
10
11
-- Good sql
SELECT COUNT(C1) AS cnt
FROM T t
LEFT JOIN (
SELECT id,name
FROM T2
) t3 ON t.id = t3.id
WHERE t.id > 100
-- Bad sql
SELECT COUNT(1) as 总数
FROM T t LEFT JOIN (SELECT id FROM t2) t3 ON t.id = t3.id

字段排列

SQL中字段应在SELECT后另起一行,且排列应保持对齐,过滤语句中使用了ANDOR,应另起一行。

1
2
3
4
5
6
7
8
9
10
11
12
SELECT	
C1,
C2,
C3
FROM t
LEFT JOIN t2
ON t.id=t2.id
WHERE t.id > 100
AND t.name LIKE '%any'
GROUP BY
t.id,
t2.name

CASE语句

CASE语句用于SQL中字表达式值的判断,对于CASE语句的代码规范做以下约定:

1
2
3
4
5
6
7
CASE WHEN t.id >= 90 THEN 'Great'
WHEN t.id >= 60 THEN 'OK'
ELSE 'Bad' END AS column_x,
-- 另一种CASE写法
CASE expr WHEN r1 THEN ...
WHEN r2 THEN ...
END NULL END AS column_x,

多表关联

sql中多表关联是很常见的,但是对于多表关联的表应使用别名,避免使用产生笛卡尔积的关联操作。关联条件的ON应另起一行且使用缩进。

1
2
3
4
5
6
7
8
9
-- Good sql
SELECT t.id, t.name, t2.score
FROM table1 t
INNER JOIN table2 t2
ON t.id = t2.id
-- Bad sql
SELECT t.id, t.name, t2.score
FROM table1 t, table2 t2
WHERE t.id = t2.id

嵌套子查询

SQL中应避免写过于复杂的嵌套子查询,子查询语句应层次分明,结构化强。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
SELECT 
t.id,
t.name,
t.address
FROM table1 t
WHERE t.id > 100
AND t.name IN (
SELECT
t2.name
FROM table2 t2
LEFT JOIN table3 t3
ON t2.id = t3.id
WHERE t2.socre > 90
)
AND t.address NOT IN (
SELECT
address
FROM table4 t4
WHERE t4.address_status = 'False'
)
ORDER BY t.id

数据库性能

SQL编码应考虑数据库性能,考虑执行速度最优原则,SQL代码应尽量使用索引,避免全表扫描,提高数据库性能。

SQL很常见,使用的人群也很广,但也应该重视SQL的编码规范。

References

[1] SQL代码编码原则和规范 - DataWorks - 阿里云

[2] Gitlab: SQL Style Guide

前言

本文不对Cron Jobs的相关命令做具体的介绍,只是想解释一下使用cron作业调度的时间设置。

有时,我们会执行一些经常性、周期性的任务,比如:文件备份、发送通知等。但是每次手动操作又十分的繁琐,一般会使用命令或脚本辅助我们完成这些工作。而在Unix-like操作系统中有作业调度命令,可以实现一些作业的定时调度,将这两者结合起来就自动化定时执行这些任务了。

使用

作业调度的形式一般像是这样:? ? ? ? ? command_to_execute,其完整的表达式是:

{minute} {hour} {day of month} {month} {day of week} command_to_execute

以下是表达式各部分的具体解释:

1
2
3
4
5
6
7
8
9
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12)
# │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
# │ │ │ │ │ 7 is also Sunday on some systems)
# │ │ │ │ │
# │ │ │ │ │
# * * * * * command_to_execute

上面的解释中,*代表所有可能的值,比如:分钟的*表示0-59。其中,对于monthweek都可以用英文缩写代替,Week: SUN-SAT代表0-6JAN-DEC代表1-12月。不过,一般习惯用数字表示。

现在讲一下,时间设置中常见的几种类型(以minute为例):

1
2
3
1,5,7  # 表示在1,5,7分钟触发
1-5,8-10 # 表示在1-5,8-10时间段内触发
*/N # 表示每N分钟触发一次,10/5 则表示从10分钟开始,每5分钟触发一次

根据以上解释,现在我们可以写一些作业调度的时间设置了。

1
2
3
4
5
6
# 每10分钟调度一次
*/10 * * * * command_to_execute
# 10月份每周五每小时2分时调度一次
2 * * 10 5 command_to_execute
# 每日5-10,10小时时间段的5分时调度一次
5 5-10,15 * * * command_to_execute

推荐一个网站(crontab guru)可以让你更加直观的理解Cron Jobs调度的时间设置 。

总结

Cron Jobs是一种管理重复性任务的不错的选择,它非常实用,也很容易上手,选择使用正确的命令和时间设置,就可以开启定时执行我们的任务了。

参考文档

[1] Wikipedia: cron

前言

本文是根据GithubHow to ask questions the smart way一文整理而来。由于原文篇幅较多,本文仅摘取了原文我感兴趣或认为重要的一部分,放在这里。想看原文的,请点击这里

为什么学会提问很重要

在现实世界中,我们难以避免他人沟通交流。无论是面对面还是网络的形式。那在交流中,也总是会出现提问题的时候。好的提问题的方式,能够提高沟通的效率,降低交流的成本;而坏的方式,别人心里可能很有意见,敷衍你的问题,甚至不想回答这个问题。

实际中,我们可能或多或少的经历过低效沟通的痛苦以及高效带来的便利。

这个有哪些要注意的方面

提问之前

在你准备要通过电子邮件、论坛、贴吧等提出技术问题前,请先做到以下事情:

  1. 尝试在你准备提问的论坛的旧文章中搜索答案。

  2. 尝试上网搜索以找到答案。

  3. 尝试阅读手册以找到答案。

  4. 尝试去FAQ看看能不能找到答案。

  5. 尝试自己检查或试验以找到答案。

  6. 向你身边的有经验的人打听以找到答案。

  7. 如果你是开发者,请尝试阅读源码、查看日志等定位问题寻找答案。

当你提出问题的时候,请先表明你已经做了上述的努力;这将有助于树立你并不是一个不劳而获且浪费别人的时间的提问者。如果你能一并表达在做了上述努力的过程中所学到的东西会更好,因为人们更乐于回答那些表现出能从答案中学习的人的问题。

在你提问时

慎选提问的论坛

小心选择你要提问的场合。如果你做了下述的事情,你很可能被忽略掉或者被看作失败者:

  • 在与主题不合的论坛上贴出你的问题。
  • 在探讨进阶技术问题的论坛张贴非常初级的问题;反之亦然。
  • 在太多的不同新闻群组上重复转贴同样的问题。
  • 向既非熟人也没有义务解决你问题的人发送私人邮件或信息。

如果在错误的场合提问,可能不会有人回复这个问题,或者不够专业的人回复这个问题。当然,有人回答这个问题可能是出于好心,但是答案未必能解决你的问题,你也不想看到这样是吧。

描述问题

问题的描述同样重要,不够清晰的描述,难以让别人快速了解你的情况。问题描述应足够精炼,又能同时覆盖你的问题。

  • 使用有意义且明确的标题
  • 描述问题的环境(例如机器配置等),你在什么操作下出现的这个问题
  • 说明一下问题的详细情况,去掉无意义的描述。
  • 你在处理这个问题上做了哪些努力,尝试了哪些方案,都有什么样的效果。
  • 说明一下你的预期结果,再说你哪个过程出现了问题。

态度

不会有人喜欢无礼、傲慢的提问并要求得到回答,但也不应该过于低声下气的提问,这无益于让别人帮你解决问题。

这个过程中,大家应做到相互尊重,有理有据。

除非双方或大部分人都具备一样的知识背景,否则不应使用过于专业的术语交流。与陌生人交流,好的态度,别人才可能愿意进一步帮你解决问题。

问题解决后

当问题解决后,我们仍然有些补充性工作要做:

  • 向为你提供帮助的人表示感谢
  • 愿意的话,可以把这次问题及解决过程写出来,让别人知道这是怎么解决的。
  • 如果是bug的话,也可以提交给开发者,让他们知道并修复这个bug

总结

有几点我觉得是在这个过程中比较重要的:

  • 学会使用Google等搜索引擎,查询自己的答案,不要遇到问题就问。
  • 找合适的场合寻找问题答案,对技术性问题跑到生活板块下找,可能不会解决你的问题。
  • 对问题要有足够清晰的描述,以帮助别人能快速定位你的问题,不要问为什么会这样、我该怎么办等描述。
  • 和别人交流问题的过程中,要把握分寸,懂礼貌,不会有人喜欢无礼的人。

以上就是我根据Github仓库How to ask questions the smart way中摘取的一些。

其实,我们能够发现整个过程还是比较繁琐的,但这是必要的。可能寻找一个问题的答案,未必要完整按这个流程来,但是这给我们提供一个途径的参考。我也认为,解决问题的过程可能需要付出很多的“代价”,但这是值得的。

References

在数据库中,日期类型属于最经常使用的类型之一。在很多场景中都会遇到日期类型转换问题,在关系型数据库中日期转换函数有很多,但这些数据库日期转换函数不能通用,接下来就以OracleMySQLSQL Server这3个常见的关系型数据库为例,简单介绍一下日期转换函数的使用。

这3个关系型数据库常见的日期转换函数有:OracleTO_DATETO_CHARMySQLSQL Serverconvert等函数。

Oralce日期转换

Oracle中,日期转换常用的函数是to_char[1]to_date[2],一个可以将其日期转换为字符型,另一个可以将字符型转换为日期型。以下是他们的基本语法格式:

1
2
3
TO_CHAR(expr, fmt[ ,nlsparam] ) -> varchar2
TO_DATE(expr, fmt[ ,nlsparam] ) -> date_type
nlsparam: 用于指定国家日期格式

现在分别讲解一下这两个函数的用法:

1
2
3
4
5
6
7
8
9
-- TO_CHAR()
SELECT TO_CHAR(sysdate, 'yyyy-mm-dd') FROM dual; -- 2021-06-11
SELECT TO_CHAR(sysdate, 'hh24:mi:ss') FROM dual; -- 20:15:30
-- TO_DATE()
SELECT TO_DATE('2020-12-23', 'yyyy-mm-dd') FROM dual; -- 2020-12-23 00:00:00
SELECT TO_DATE('2020-12-23 12:12:00', 'yyyy-mm-dd hh24:mi:ss') FROM dual;
-- 设置了nlsparam的用法
SELECT TO_DATE('January 15, 1989, 11:00 A.M.','Month dd, YYYY, HH:MI A.M.',
'NLS_DATE_LANGUAGE = American') FROM DUAL;

上面只是讲解了这两个函数最常见的一些用法,更详细的用法参见References[1]-[2]。

SQL SERVER日期转换

SQL Server中使用的日期转换函数是CONVERT[3],其基本语法格式是:

1
CONVERT( type [ (length) ], expression [ , style ] )

语法中length是目标类型的长度,通过长度的设置可以截取我们所需要的格式,而通过style的值实现不同格式的转换(具体使用见References[5])。

现在举例讲解一下CONVERT函数在SQL Server中的用法

1
2
3
4
5
6
7
-- 日期转字符串
SELECT CONVERT(VARCHAR(10), GETDATE(), 120); -- 2021-06-11
SELECT CONVERT(VARCHAR, GETDATE(), 120); -- 2021-06-11 20:25:32
SELECT CONVERT(VARCHAR, GETDATE(), 101); -- 06/25/2021
-- 字符串转日期
SELECT CONVERT(DATETIME, '2021-10-22', 101);
SELECT CONVERT(TIME,'20:20:36',114);

SQL ServerCONVERT函数已经能非常灵活的达到多种格式转换的目的,通过控制type_lengthstyle的值能够获取我们想要的各种格式。

MySQL日期转换

MySQL中实现日期转换操作,可以通过CONVERT[6]函数实现字符串转日期,通过DATE_FORMAT[3]实现日期转字符串。

函数的基本语法格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- string_to_date
CONVERT(expr, type)
type: DATE, DATETIME, TIME
-- date_to_string
DATE_FORMAT(date_expr, fmt)
fmt:
%d Day of the month as a numeric value (01 to 31)
%f Microseconds (000000 to 999999)
%H Hour (00 to 23)
%h Hour (00 to 12)
%i Minutes (00 to 59)
%M Month name in full (January to December)
%m Month name as a numeric value (00 to 12)
%S/%s Seconds (00 to 59)
%Y Year as a numeric, 4-digit value
%y Year as a numeric, 2-digit value

下面举例解释一下这两个函数:

1
2
3
4
5
6
-- CONVERT
SELECT CONVERT("20:13:45", TIME)
SELECT CONVERT("2021-03-10", DATE)
-- DATE_FORMAT
SELECT DATE_FORMAT(NOW(), "%Y-%m-%d"); -- 2021-06-11
SELECT DATE_FORMAT('2007-10-04 22:23:00', '%H:%i:%s'); -- '22:23:00'

CONVERT函数实现了将时间字符串转成TIMEDATE等类型,DATE_FORMAT函数与OracleTO_CHAR用法非常类型,这里就不多做过多的赘述了。

CAST函数

其实CAST函数不是一个只用于日期转换的函数,很多类型都可以通过这个函数进行类型转换,比如:字符串转数值型,日期转字符串等。以下是CAST函数的语法知识:

1
2
-- CAST Syntax:  
CAST ( expression AS data_type [ ( length ) ] )

而且Oracle[5]MySQL[6]SQL Server[4]都实现了对CAST函数的支持。但是本文只讲该函数对日期类型转换的操作部分。

1
2
3
4
5
6
7
8
-- Oracle
SELECT CAST('22-OCT-1997' AS TIMESTAMP WITH LOCAL TIME ZONE) FROM dual;
SELECT CAST(sysdate AS VARCHAR2(30)) FROM dual;
-- MySQL
SELECT CAST("2017-08-29" AS DATETIME);
SELECT CAST("14:06:10" AS TIME);
-- SQL Server
SELECT CAST('2014-05-02' AS date); -- '2014-05-02'

总结

本文简单的介绍了一些常见的关系型数据库涉及日期转换的函数,其中大部分函数可移植性较差,是不能跨数据库使用的,这一点需要注意下。CAST函数将日期类型转换为字符串类型,不能够灵活的格式化。对于函数使用的出处,参见参考文献中官方文档详细的解释。

References

[1] Oracle: TO_CHAR

[2] Oracle: TO_DATE

[3] MySQL8.0 Date and Time Functions: DATE_FORMAT

[4] CAST 和 CONVERT - SQL Server | Microsoft Docs

[5] Oracle: CAST Function

[6] MySQL8.0 Cast Functions and Operators

本文不对join的数学理论多做介绍,感兴趣的可以看看维基百科的解释

为什么要使用join

在关系型数据库(RDB)中,多表的关联总是没办法避免使用join。而很多初学者对于SQLjoin的用法总是搞不清楚,再加上join中类型有多种,很容易让人搞混这几种join的用法。下面我根据自己的理解,对join的用法做一个简单地介绍。

join有哪几种类型

SQL中的join大概有这几种:leftrightfullinnercross。而它们又可以分为3大类:内连接(inner join)和外连接(outer join)以及交叉连接(cross join)。其中,外连接有3种,分别是:左外连接(left outer join)、右外连接right outer join、全外连接full outer join

:在实际使用中,外连接的outer经常忽略不写

join的具体使用

我们先新建两张表:table1table2,它们的结构如下:

:本次测试使用的数据库是MySQL8.0

1
2
CREATE TABLE table1 (id int, name varchar(5) );
CREATE TABLE table2 (id int, name varchar(5) );

分别向两张表插入少量测试数据,如下:

1
2
3
4
5
6
7
8
table1				table2
id name | id name
100 'A' | 100 'A'
101 'B' | 101 'B'
102 'C' | 102 'C'
103 'D' | 103 'D'
104 'E' | 106 'G'
105 'F' | 107 'H'
  • 笛卡尔积

    在多表关联中,很多人经常使用笛卡尔积的方式进行多表关联。对于m行与n行的数据表,我们知道笛卡尔积产生的结果是mxn行数据。对于测试表table1table2,不加任何过滤条件可产生6*6=36条数据。

    1
    2
    SELECT t.*,t2.* 
    FROM table1 t, table2 t2;

    显然,对于数据量更多的表,不做好条件过滤的情况下,笛卡尔积产生的数据量将是恐怖的。巨大的数据量中,不仅很多是无效的数据,也会降低数据库的性能。因此,一般并不建议使用这种方式进行表间关联。

  • 内连接(inner join)

    对于内连接来说,只会产生符合关联条件的数据。在不使用on过滤条件的情况下,内连接产生的结果也是笛卡尔积。

    1
    2
    3
    SELECT t.*,t2.* 
    FROM table1 t
    INNER JOIN table2 t2 ON t.id=t2.id;

    结果如下:

    id name id name
    100 A 100 A
    101 B 101 B
    102 C 102 C
    103 D 103 D

    从上面的结果,我们也可以看出,产生的结果只含有table1.id等于table2.id的数据。

  • 左连接(left join)

    在左连接中,左表称为主表,而右表称为副表。不加过滤的情况下,连接不仅会产生符合连接条件的数据,也会全部产生左表的数据。

    1
    2
    SELECT t.*,t2.* FROM table1 t
    LEFT JOIN table2 t2 ON t.id = t2.id;

    结果如下:

    id name id name
    100 A 100 A
    101 B 101 B
    102 C 102 C
    103 D 103 D
    104 E
    105 F

    根据上面结果,能看来左连接与内连接不一样的是:左连接不仅会产生符合连接条件的数据,不符合连接条件的左表数据也会在结果中,由于右表存在不符合关联条件的数据,则结果中右表数据都是NULL。有意思的是,将左连接SQL代码改成下面这样,产生的结果将会和内连接是一样的。

    1
    2
    3
    4
    SELECT t.*,t2.* 
    FROM table1 t
    LEFT JOIN table2 t2 ON t.id = t2.id
    WHERE t2.id IS NOT NULL;
  • 右连接(right join)

    在了解左连接之后,就很容易懂右连接的用法了。右连接与左连接相反,右表为主表,左表为副表。

    测试如下:

    1
    2
    3
    SELECT t.*,t2.* 
    FROM table1 t
    RIGHT JOIN table2 t2 ON t.id = t2.id;

    结果如下:

    id name id name
    100 A 100 A
    101 B 101 B
    102 C 102 C
    103 D 103 D
    106 G
    107 H

    同样地,将代码改成下面这样,也能得到与内连接一样的结果,注意这次是过滤条件是左表的id is not null

    1
    2
    3
    4
    SELECT t.*,t2.* 
    FROM table1 t
    RIGHT JOIN table2 t2 ON t.id = t2.id
    WHERE t.id IS NOT NULL;
  • 全外连接(full join)

    如果说左连接与右连接是“相反的”,那么全外连接更像是左右连接的”并集“。不仅会输出符合关联条件的数据,左表右表不符合关联条件的数据也会一并给出,只不过对于左、右表中不符合关联条件的数据,对方位置也是以NULL显示的。

    :目前mysql还不支持全外连接的用法

    结果如下:

    id name id name
    100 A 100 A
    101 B 101 B
    102 C 102 C
    103 D 103 D
    104 E
    105 F
    106 G
    107 H

    对于结果就不多做解释了。

  • 交叉连接(cross join)

    在标准SQL中,[CROSS|INNER] JOIN在没有使用on的情况下产生的结果都是笛卡尔积,需要使用on对结果进行过滤。

参考文档

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment