例如,某个房间可从[灯,床,桌,椅,杯子,饮水机……]这些器具中挑选,从而组成这个房间的装潢。我们可能会设计一个房间表,再设计一个器具表,再设计一个关系表,通过这个关系表来保存它们之间的对应关系。但是这样的效率明显是比较差的,需要同时查询三张表才能完成。

为了不适用关系表,我们还可以在房间表中设计一个字段,通过一个有规律的字符串来保存器具表的器具ID,例如:

1,2,3,7

下面,我们提供一种通过一个值来计算即可获得这一器具组合的结果,方法如下:

array(

'1' => '灯'

'2' => '床',

'4' => '桌',

'8' => '椅子',

'16' => '饮水机',

……

);

如果我们将5保存到数据库中,我们可以立马知道,这个房间有“灯”和“桌”,而如果保存的是23,则一定有“灯”“床”“桌”和“饮水机”。

给每一个器具一个给定的值,这个值一定是2的n次方(n>=0),这样就可以保证相加之后的值可以反解。这个情况的核心原理在于,给定任何数值的前面数值相加和,一定小于当前数值。如何进行反解呢?

例如我们拿到一个值为N,那么我们可以首先找到最大的2^n,确定2^n是一定有的,如果没有2^n,就不可能相加得到N。

接下来我们获得M = N - 2^n,找到最大的2^m,再进行M - 2^m,如此推论下去,直到减完为止。

那么怎获得最大的2^n呢?

$n = (int)log(N,2);

log函数在PHP4+之后内置,用于取对数,返回值为float类型,但我们仅需要整数部分,因此前面加(int)。

例如N=22,那么$n=4,再去计算2^4,就是16。

通过这个方法,我们可以非常顺利的在一个数据表中用一个值保存多种情况。但是,这也有一定的适用范围,比如这些情况最好是固定不变的,2n值不能太大等等。通过这种方法可以用该值进行权重设计,进行排序,但是不能用于条件检索,比如你想检索数据库中包含“床”的房间,你就不好进行检索,因为大部分房间的该值可能都大于2.所以,在使用这种方法时,应该根据实际需要进行考虑。

更新:

在数据库中,我们可以使用一种序列化的类二进制字符串来保存多个值,当这个二进制值是以01组成时,实际上就可以换算成为一个十进制数,从而也就实现了一个十进制值保存多种情况的目的。

下面我们来做一个演示。

例如我们在订票系统中,规定某一个活动每天分为6个场次,每个场次2个小时,因此实际上就把一天的12个小时分为了6份,分别是9:00-11:00,11:00-13:00,13:00-15:00,15:00-17:00,17:00-19:00:19:00-21:00,我们用“xxxxxx”(x取0或1)来表示,现在,我们要记录这些场次是否全部被定完了,用1表示全部被订完,所以“010110”就表示11:00-13:00,15:00-17:00,17:00-19:00这三个场次已经被订完了,不能再对外售票。

我们在数据库中怎么保存呢?

php提供了将二进制转换为十进制的函数bindec(),我们先将二进制值转换为十进制值后,再保存到数据库中。而当我们要使用时,从数据库中取出十进制值,再使用decbin()将值转换为二进制值,当然,我们要补全最后得到的二进制值的位数,也就是前面加0,然后再进行字符串数组处理,进行对比。

在编程世界中,还有一个比较好玩的算法,叫“按位异或”。按位,就是以二进制的形式进行计算,“按位异或”就是两个位的值不同时返回1,否则返回0。通过这个运算,我们可以得到看上去非常复杂的结果。在php中,运算为“^”。下面我们来进行一下演算。

001011 ^ 011010 = 010001 (1式,注意,开头的0会被忽略,因此不要把开头的0也算进来)

提按位异或有什么意义呢?因为二进制值可以和十进制值进行转换,因此我们将二进制值转换为十进制值进行按位异或之后,得到的值也是十进制的,我们只有将这些十进制数转换为二进制字串后,才能发现规律,但是如果我们直接用十进制进行计算,却能快速得到结果。

下面我们就来演算一次,我们拿(1式)来看。如果将二进制数转换为十进制,我们就能得到

11 ^ 26 = 17

那事实的结果是不是这样呢?你可以在你的php程序中写上:

echo 11 ^ 26;

是的,结果就是这样。可是,这个复杂的运算有什么用呢?它可以用于比较。比如我们的数据库中存放了11,转换为二进制就是“001011”,也就是表示这一天的场次中,对应的那三个时段已经满票了。但是如果我们现在正好要进行对比,看看这一天中17:00-19:00这个时段是否满票,我们怎么能准确知道11这个值转换为001011后,第5个位上的值是否为1呢?

我们只需要用这种思路来解决即可:

xxxxxx ^ 000010 = ?

其中xxxxxx是我们要对比的值,比如当它等于11时,也就是001011时,等式的右边会得到001001(9)。我们再来看另一个算式:

xxxxxx ^ 000000 = ?

等式右边会得到本身。

如果我们再用001001(9)去按位异或000010,则会得到001011(11)。

我们得到的结论就是,凡是用xxxxx去按位异或yyyyyy(其中只有一个y为1,其他全为0),得到的结果比自身小的,则对应位置上的值为1,得到的结果比自身大的,对应的位置上为0。通过这种方法,也就找到了哪个时间段是被订满票的。

为什么大于自身的,对应的位置上就一定为0呢?因为0^1=1,而二进制数是01构成的,也就是说0和1碰上0时,都不会变化,而只有0碰上1时才会变化。说白了,用任何一个二进制数去按位异或000100,结果发生的情况就两种,一种是第四个位置上的值由1变为0(结果值相对于本身值而言),这种情况下该值变小,一种是第四个位置上的值由0变为1,这种情况下该值变大。了解了这个原理之后,我们只需要在数据库中保存二进制转换而来的十进制值,在查询时,用对比值(二进制转换而来的十进制值)去按位异或一下,即可得到我们想要的结果。

我们创建如下表结构,sale_over在实际存储时,我们转换为十进制整数进行存储,这里方便演示用二进制表示。每次在用户下订单时对票数进行检查,如果该时段已经有20张票被订出,就在下表中更新一条记录,把对应的时段改为1.

tablename = objectorder

id

object_id

day

sale_over

1

5

2015-08-23

011000

2

8

2015-08-24

100101

3

5

2015-08-25

010001

例如:

SELECT COUNT(id) FROM object_order WHERE object_id=8 AND day='2015-08-20' AND (hours ^ 2)

这样就可以判断出8月20号这天17:00-19:00这个时间段是否被订满(如果返回1,则表示被订满了)。

如果我们不满意用大小比较来进行判断,我们还可以深入发现,按位异或结果与原值之间的差值,正好是用来异或的值,也就是满足下面的等式:

|m ^ n - m| = n (n为yyyyyy,只有一个y为1,其他为0)

|x|是指绝对值,当不取觉得值,得到的为负数时,说明结果变小了,那么原值对应的位置上也就是1,而如果得到的为正数,说明结果变大,对应的位置上就为0。所以,上述sql,我们还可以这样去改:

**SELECT COUNT(id) FROM object_order WHERE object_id=8 AND day='2015-08-20' AND (hours ^ 2 + 2)=hours;**

如果查到了结果,说明8这个活动8月20号这天17:00-19:00这个时间段被订满。

这种魔术般的使用方法,你是否思考过呢?

再议

实际上,一个二进制数,我们将它转换为十进制时,将它的各个位置值(从右往左,以0为开始)作为次数求2的次幂,再乘以该位置上的数,再相加,即得到该二进制数对应的十进制数,例如:

10100 = 0(2^0) + 0(2^1) + 1(2^3) + 0(2^4) + 1*(2^5) = 8 + 32 = 40

这样去观察,就发现实际上8和32,就是我们第一次接触这种算法时,将它们作为一个数组的索引值,进行物品的索引进行计算。

接下来,我们要更换场景,每个时段仅可以被一个人预订,用户每一次下订单完成之后,形成一条记录,这些记录以上述形式存储,得到如下订单数据表:

tablename = userorder

id

user_id

object_id

day

hours

1

2

5

2015-08-23

011000

2

3

8

2015-08-24

100000

3

2

5

2015-08-24

000001

类似这样的订单记录,hours字段中每个位置上的1最多出现1次,怎么样确定某一天的所有票都已经定出去了呢?

其实这是最简单的,就是对该字段进行求和,例如:

SELECT SUM(hours) FROM user_order WHERE object_id=8 AND day='2015-08-20';

如果最终得到的值为111111,也就是十进制的63,则说明该天各个时段已订满,不能再进行预订。

最后一种情况则是对上面两张场景的结合,也就是每个时段最多可以被预订20张票,数据库中记录的是单个用户的订单。

当然,遇到这种情况,其实我们可以准备两张表,一张是用户的订单表:

tablename = userorder

id

user_id

object_id

day

hours

1

2

5

2015-08-23

011000

2

3

8

2015-08-24

100000

3

2

5

2015-08-24

000001

(第一条记录表示用户2在2015-08-23这天预订了5这个活动的11点13点这两个时段的票)

一张用来在每次用户订单完成时,对该时段进行判断,如果这个时段已经卖出20张,就改为1,进行更新操作的场次预订情况表:

tablename = objectorder

id

object_id

day

sale_over

1

5

2015-08-23

011000

2

8

2015-08-24

100101

3

5

2015-08-25

010001

但是这样的话,我们通过该表,仅能判断是否卖完,而不知道已经卖了多少张。为了解决这个问题,我们夸张的做法是,直接在这个表的基础上进行扩展,增加20个字段,每个字段对应一个时段,用来记录所卖出的票数,但是这样实在太蠢了。由于二进制方式,无法在每个位置上表示实际的值,例如在第2个位置上用3来表示卖出3张,这是我们无法做到的,所以,我们可以通过前面一张用户下的订单列表来进行计算,从而找出某个位置上是否已经存在20个1.

实际上,我们现在要解决的,就是查出每个时段已经订出了多少张票。

我们可以用

SELECT COUNT(id) FROM user_order WHERE object_id=8 AND day='2015-08-20' AND (hours ^ 2 + 2)=hours;

这种方法就可以查出来某个时段的被订数量,如果返回值等于20,则说明该时段已经被定完了。但是,我们如何从所有的记录中,找出那些天的席位被全部定光呢?因为我们不打算使用objectorder表来记录,而是想直接通过userorder进行查询,所以我们不仅要判断某个位置上的为1的记录数是否为20,而且要判断所有的位置。

最笨的方法就是连续判断6次,对每个位置都进行统计,最终进行判断。但是这明显不符合我们的要求。

实际上,我们仍然使用求和即可完成,我们在前面进行求和时,只需要用111111进行对比,也就是十进制的63进行对比,而这次,我们用20个111111进行对比,也就是63*20 = 1260进行对比即可。

SELECT SUM(hours) FROM user_order WHERE object_id=8 AND day='2015-08-20';

如果得到的返回值等于1260,说明这一天的所有场次已经完全订出去了。

用这种方法处理数据库中保存有规律的多种情况保存,就变得轻松有趣了。

mysql按位存储_数据库中用一个值来保存多种情况:二进制和按位异或相关推荐

  1. mysql+取字符串前两位小数_数据库截取字符串前两位小数点

    Oracle的函数 函数对字段进行处理: 一.字符函数 lower(char):将字符串转换为小写的格式 upper(char):将字符串转换为大写的格式 length(char):返回字符串的长度 ...

  2. mysql 散列存储_什么是数据库散列存储? - 蚂蚁吞大象的个人空间 - 51Testing软件测试网 51Testing软件测试网-软件测试人的精神家园...

    什么是数据库散列存储? 上一篇 / 下一篇  2012-11-30 17:25:03 / 个人分类:数据库 (转载自百度空间http://hi.baidu.com/pplboy/item/2d7a26 ...

  3. mysql 查找相似数据_数据库存储引擎大揭秘,不看不知道这里面的骚操作可真多!...

    吊打各种树这篇文章 带大家学习一遍数据结构中的各种树,对数据结构还不够熟悉的同学,那篇文章可以作为基础入门,我画了很多图理解起来不困难,建议回头先学习下那篇文章,更容易理解本文要讲的内容. 文章里有提 ...

  4. 【MySQL 第10章_数据库的设计规范】

    第10章_数据库的设计规范 1. 为什么需要数据库设计 2.范式 2.1范式简介 2.2范式都包括哪些 2.3 键和相关属性的概念 2.4第一范式(1st NF) 2.5 第二范式(2nd NF) 2 ...

  5. 从文件中读取并进行树的存储_数据库中的面试题你能接几招

    (附答案,不带答案的面试题都是耍流氓) 1. 事务的特性 ACID: 原子性, 一致性, 隔离性, 持久性 2. innodb如何结果幻读 在不可重复读的隔离级别下使用间隙锁 3. 什么是间隙锁 In ...

  6. 电脑mysql是什么意思_数据库是什么意思

    展开全部 数据库需要从以下几个方面去了解: 一.数据库功能: 数据库(Database)是按照数据结构来组织.存储和管理数e5a48de588b6323131333532363134313032313 ...

  7. mysql 字段名称规范_数据库表及字段命名规范

    数据库设计表及字段命名规范(我整理的,望大家多多提建议) 1.数据库表命名规范: (1)表名前应该加上前缀,表的前缀一个用系统或模块的英文名称缩写,前缀全部大写或首字母大写,表名中包含的单词首字母大写 ...

  8. MySQL删除空值语句_数据库语句sql 删除空记录

    最简单删除SQL Server中所有数据的方法 原文:最简单删除SQL Server中所有数据的方法 最简单删除SQL Server中所有数据的方法   编写人:CC阿爸   2014-3-14 其实 ...

  9. mysql 列式存储_[转]几张图看懂列式存储

    最近看到一篇很好资料,里面三言两语配上几个图就把列式存储(Column-based Storage)讲明白了,牛啊!最喜欢的就是这种浅显易懂就把背景知识讲得明明白白,而不是长篇大论的讲概念. 1 为什 ...

最新文章

  1. Revit初学者完整指南 The Complete Revit Guide for Beginners
  2. 机器学习初学者都应该知道的5类回归损失函数
  3. oracle回收ddl权限,查看oracle用户所有权限,并获取授权的DDL-Oracle
  4. Qualcomm式创新融入中国 有何深层逻辑?
  5. Linux报错nginx: [emerg] unexpected “}”
  6. count(id)count(1)count(*)count(字段)
  7. python在汽车上的应用_python实现图片识别汽车功能
  8. mysql下一个版本号_mysql下一个版本应该且实现并不复杂增加的常用功能
  9. 2018年下半年《软件评测师》下午试卷及答案
  10. python语言标识符命名规则_Python标识符
  11. 开票接口系统能够解决的十大问题
  12. pc用c语言控制三菱plc,上位PC机控制三菱PLC
  13. 回顾:程序设计方法——结构化设计
  14. html ur是什么意思_url是什么意思?
  15. [NPUCTF2020]碰上彩虹,吃定彩虹
  16. Fusion 360 以及 API 快速了解
  17. utsc的计算机科学,天啊撸:中国留学生淘汰率竟比加拿大学生高出一倍
  18. Linux删除文件之后磁盘空间没有被释放
  19. selenium自动化爬取Boss直聘职位数据 按照热门城市
  20. 在window下查看占用tomcat进程,杀死进程并启用tomcat

热门文章

  1. c 编程替换语言,巧用typedef替换类型名称
  2. 看到一片好文章,同享
  3. C++结构体中定义虚拟函数
  4. 电脑win10计算机图标,win10系统桌面的计算机图标没了的详细技巧
  5. 【武汉万象奥科】瑞芯微RK3568芯片
  6. centos基础镜像做mysql镜像_使用docker 基于centos7制作mysql镜像
  7. ffmpeg命令分析-ac
  8. 软考中级哪个通过率高且简单?
  9. 【Vue学习-element-ui】用户信息列表的实现
  10. react+redux+webpack+git技术栈