为什么突然开博客

1. 分享

一直想将自己学到的东西分享出来,不管是什么方式,博客文章也好,视频也好,一个是记录自己的成长供自己在往后的生活中回顾曾经那个傻小子,另外一个就是渴望得到他人的认同吧。

曾经开过博客,分享别人的文章,别人的故事,别人的技术,渐渐没有了更新下去的兴趣,但是现在的自己在这个社会上成长,感觉有些东西还是要表达出来。

2. 相互学习

学习和工作中总会有自己无法解决的问题,第一想法自然是 百度一下,百度不行就 google,虽然很多时候都能依赖别人的经验方法,找到解决方案,但是还是会有一些问题是很难找到答案,甚至没有答案的。

如果这类问题自己想方设法解决了,或者自己通过更优的方法实现,而没有记录下来提供给大家相互学习就太过遗憾了。

3. 记录

其实工作以后已经很少写那么长的文字,感觉自己的想法很凌乱,需要一个地方整理下来,特别是经常有灵感,不写下来真的是转瞬即逝。

4. 成长

一直想在技术上有一些突破,工作的时间越久,越感觉自己被一些东西束缚禁锢。

但是偷懒真的是一个人的本性,下班或者周末躺在床上,都不知道自己在做什么,刷知乎、B站、游戏、看小说、甚至刷购物网站,这些也有一个度,过了这个度就会感觉毫无意义,虚度光阴。

但是就像在人前要保持体面,其实一个人生活的时候实际也会经常是蓬头垢面,邋里邋遢,一旦将一些行为展现在人前,总会注意点形象,人毕竟是社交动物,也是希望自己能够通过这种方式自律,慢慢戒除一些不好的习惯。

5. 决心

另外就是丢三落四,服务器已经买了两年了,一直有写笔记的习惯,所以也一直打算建博客,原本准备自己一点点码代码,但是经常被一些琐碎小事打断,就放弃了,而且前端没有学好的我,设计一个赏心悦目的界面也是不容易。

看到GitbubDaily推荐的Halo也是犹豫了两天,但是还是下定决心要先把博客做起来,于是将服务器重装了Ubuntu,使用Docker安装了Halo,顺便接触了一下Nginx,一个小破站就这样诞生了。但是昨天还是因为主题安装上的一些问题郁闷了良久,看到爪哇的报错眉头紧锁。

或许也该抽时间了解了解Java了,什么才能实现当初说过的,实现学好一门编程语言转行只要俩月的“豪言壮语”。

后记

好的,1234列举完了,其实就是瞎写,这样写下来,排版也会比较好看吧。哈哈。😄

后续也要把一些笔记分享上来,填充一下博客空白的版面了,还是要加油啊。这里要感谢一下 GitHubDailyHalo

实验室信息系统实验仪器接口(lis-ii)专题开篇

回顾

工作三年,其中有两年都有和 LIS 仪器接口打交道,接触开发的仪器接口型号已有百余。

但是前期由于经验不足,个人能力也欠缺,都是仿照前辈们的写法,每个接口单独拆开,每个接口就是一个运行于客户机的程序,复用性其实不佳,且基本都是 copy 不明白其中的原理,很难真能领悟学习到其中的知识。

后期虽然有做改进,对接口主要的通信方案进行了整理,进行了封装,但是由于设计思路不明确,现在回看仍是一锅乱炖,越是调整越显杂乱,遂想重新设计重新开发出一个真正高可用、高性能、插件式的接口方案。

这是对过去的学习和工作的归纳总结,同时也想在这个过程中,接触一下平时工作中很少用到的一些编程思想、设计模式、开发工具等,提升自己的技术水平开阔视野。

lis-ii 介绍

lis-ii 是实验室信息系统仪器接口(Laboratory Information Management System - Instrument Interface)的缩写,是实验室管理系统非常重要的一环。

其主要负责将实验室仪器内的实验分析数据传输给 LIS ,方便检验医师统一的从实验室信息系统分析检验结果,出具检验报告。并且可以将 LIS 中患者需要做的检验项目,自动传输给实验室仪器,减少检验医师的工作量,以及避免出现人工录入检验项目出现错误遗漏等情况。

并且现在主要的实验室系统以及实验室仪器厂家都在推广“流水线”这一概念,其主要目的也是减少检验科的工作量,以及实现检验系统的自动化,而这其中实验仪器和检验信息系统的通讯也就是其中非常重要的一环,而 lisii 就是这个桥梁,实现检验信息系统与实验仪器的互通。

计划

  1. 暂定使用 Visual Studio 2017 开发,因为客户电脑可能是 Windows XP 系统,所以 dotnet 最低支持 .NET Framework 4.0
  2. 之前内部维护的一个工具类项目,需要继续完善,在该项目中使用。
  3. 因为大部分接口都是对文本的解析,有计划支持PythonJavaScript等脚本语言。
  4. 工作原因,可能比较少的时间做这个项目,计划项目开发一年,并且项目将在 github 上开源。

解决Oracle数据库查询“ORA-01772:无效的数字”问题

问题起源于我们有一个数据表有一列数据类型为varchar2,但是其中一部分为数字的格式,需要将为数字格式的内容取出,过滤结果大于某特定值的结果。

但是实际测试发现,无论怎么调整,这个函数都会在最后执行,导致查询报错:ORA-00920:无效的数字

准备数据

首先,可以根据我们的需求创建一个表用于以上测试,重现整个问题处理过程遇到的问题,以及最终的解决方案。

创建一个数据表有主键ID列与结果列,并添加一些数据,具体内容如下:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
-- Create table
create table test_table
(
test_id varchar2(36) not null,
test_result varchar2(100)
);
-- Add comments to the columns
comment on column test_table.test_id
is '主键';
comment on column test_table.test_result
is '结果';
-- Create/Recreate primary, unique and foreign key constraints
alter table test_table
add constraint pk_test_table_test_id primary key (TEST_ID);

--测试数据
insert into test_table (TEST_ID, TEST_RESULT)
values ('25c3cb1a-063f-4b70-88af-25a5439ed79a', '10.5');

insert into test_table (TEST_ID, TEST_RESULT)
values ('5577ee74-118c-44e4-95aa-1a25424d5fb5', null);

insert into test_table (TEST_ID, TEST_RESULT)
values ('b65b288d-7760-4c4f-b8e0-58891ad2703e', '600');

insert into test_table (TEST_ID, TEST_RESULT)
values ('fff2017c-a287-411f-b0a7-6e10ed91a9dd', '阴性');

insert into test_table (TEST_ID, TEST_RESULT)
values ('a488616e-e6dc-4a7c-ad0e-7d1601f87200', '未知');

insert into test_table (TEST_ID, TEST_RESULT)
values ('076952fa-a53a-4f57-8171-2d335b75a69d', '0.01');

insert into test_table (TEST_ID, TEST_RESULT)
values ('ed8ded33-1dde-452b-a9e9-a9a14abf9618', '100');

insert into test_table (TEST_ID, TEST_RESULT)
values ('7f4ec9d4-f6f2-42c1-8711-8cfe7836d7d3', ' 7.56');

insert into test_table (TEST_ID, TEST_RESULT)
values ('9156cb89-5742-4b6f-9377-467ed5fd0de8', '19');

insert into test_table (TEST_ID, TEST_RESULT)
values ('9962b5b3-611b-4cde-a8ef-a84fba9f8e20', '213.7');

insert into test_table (TEST_ID, TEST_RESULT)
values ('bb12b750-251f-4004-9243-e9cc05790220', '1887');

insert into test_table (TEST_ID, TEST_RESULT)
values ('f3638fa6-edbe-4ab5-b231-53351c9915d1', '阴性');

insert into test_table (TEST_ID, TEST_RESULT)
values ('25aae635-95dc-4632-8682-5fef1f95bfda', '阳性');

insert into test_table (TEST_ID, TEST_RESULT)
values ('32dd89e2-4b06-4479-82a7-aa3af367c97a', '20.8');

insert into test_table (TEST_ID, TEST_RESULT)
values ('0c78b29b-17d8-4fe9-afb0-74c6dd65e627', '1625');

insert into test_table (TEST_ID, TEST_RESULT)
values ('ddc2857f-0eec-48a9-8a62-74866842ff00', '17.73');

insert into test_table (TEST_ID, TEST_RESULT)
values ('cc848ac7-9599-4139-ab4b-32cefb25d791', '54.6');

insert into test_table (TEST_ID, TEST_RESULT)
values ('498118bb-36a6-4ac0-8ee0-3c83c052625a', null);

insert into test_table (TEST_ID, TEST_RESULT)
values ('2cbb9ff1-34de-493f-9075-c3c598bdadb3', '80.4');

insert into test_table (TEST_ID, TEST_RESULT)
values ('d89832da-177d-4ee0-9a6d-25e8af276e2c', '399');

数据类型的判断

首先,从网上我们获取了找到了一个如何判断varchar2类型结果是否是数值的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CREATE OR REPLACE FUNCTION ISNUMERIC (STR IN VARCHAR2)
RETURN NUMBER
IS
V_STR FLOAT;
BEGIN
IF STR IS NULL
THEN
RETURN 0;
ELSE
BEGIN
SELECT TO_NUMBER (STR)
INTO V_STR
FROM DUAL;
EXCEPTION
WHEN INVALID_NUMBER
THEN
RETURN 0;
END;
RETURN 1;
END IF;
END ISNUMERIC;

因为最终我们在做判断,也是利用了Oracle中的TO_NUMBER函数,所以这里直接使用该函数实现数值类型的判断。

错误的查询示例

直接查询

正常来说,Oracle数据库应该会根据我们书写的条件顺序,分析语句并依次执行。其实这种写法,在旧版Oracle中没有问题,但是可能因为数据库版本的问题,实际上Oracle并不是按照我们书写的顺序执行。

例如我们执行以下SQL:

1
2
select * from test_table where to_number(test_result) > 100 and isnumeric(test_result) = 1;
select * from test_table where isnumeric(test_result) = 1 and to_number(test_result) > 100;

以上执行的提示并没有不同,均提示ORA-00920:无效的数字

分析执行计划可以看到:

1
2
3
4
5
6
7
8
9
10
11
12
 Plan Hash Value  : 3979868219 

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 72 | 3 | 00:00:01 |
| * 1 | TABLE ACCESS FULL | TEST_TABLE | 1 | 72 | 3 | 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
------------------------------------------
* 1 - filter(TO_NUMBER("TEST_RESULT")>100 AND "ISNUMERIC"("TEST_RESULT")=1)

无论条件顺序怎么写,均是先执行范围的判断,再执行ISNUMERIC函数,函数的执行优先级都在最后。

加一层或使用查询临时表

首先想到的方案,是否可以先将是数值结果的查询出来,然后再在这个基础上判断结果的范围。

1
2
select * from (select * from test_table where isnumeric(test_result) = 1) where to_number(test_result) > 100;
with temp as (select * from test_table where isnumeric(test_result) = 1) select * from temp where to_number(test_result) > 100;

结果仍然是提示ORA-00920:无效的数字。同样的我们分析执行计划,执行顺序仍然被“优化”为先执行范围判断:

1
2
3
4
5
6
7
8
9
10
11
12
 Plan Hash Value  : 3979868219 

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 72 | 3 | 00:00:01 |
| * 1 | TABLE ACCESS FULL | TEST_TABLE | 1 | 72 | 3 | 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
------------------------------------------
* 1 - filter(TO_NUMBER("TEST_TABLE"."TEST_RESULT")>100 AND "ISNUMERIC"("TEST_RESULT")=1)

使用视图

首先我们可以确认的是:

1
select * from test_table where isnumeric(test_result) = 1;

可以顺利执行,那么我们是否可以将该部分数据设置一个视图,然后从视图中进行过滤,想到了就试一下吧,首先创建一个视图:

1
create or replace view v_test_table as select * from test_table where isnumeric(test_result) = 1;

然后查询视图:

1
select * from v_test_table where to_number(test_result) > 100;

很遗憾的是,执行查询语句,无效视图的窗体再次弹出,而执行计划没有任何变化。

1
2
3
4
5
6
7
8
9
10
11
12
 Plan Hash Value  : 3979868219 

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 72 | 3 | 00:00:01 |
| * 1 | TABLE ACCESS FULL | TEST_TABLE | 1 | 72 | 3 | 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
------------------------------------------
* 1 - filter(TO_NUMBER("TEST_RESULT")>100 AND "ISNUMERIC"("TEST_RESULT")=1)

解决方案

其实写到这里,我已经钻进了死胡同,因为我查看了自己历史提交记录,有一个报表就是通过这种方式查询并统计数据的,所以可以确定在某个版本的Oracle中,这个方法是可行的。

但是现在的问题是,Oracle在现在测试的这个版本中,会“自作聪明”的将我们所设计的SQL语句进行“优化”,优化成Oracle理解的性能最优的状态去执行,而且我们没有办法通过设置的方法去改变这个执行顺序。

那么是否有方法能将函数的执行顺序提前呢?

同事刚好看到我处理的问题,然后提出他处理过类似的字符串类型作为数值型,进行范围的判断的报表,不过他使用的方法是case when then。分两次查询,如果字符串可以转换为数字,那么返回这个字符串本身,否则返回一个数字。

那么我们将该方案使用decode函数来实现:

1
2
3
4
5
6
7
8
select *
from (select test_id,
decode(isnumeric(test_result),
1,
to_number(test_result),
null) test_result_num
from test_table)
where test_result_num > 100;

成功执行,查看执行计划:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 Plan Hash Value  : 3979868219 

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 72 | 3 | 00:00:01 |
| * 1 | TABLE ACCESS FULL | TEST_TABLE | 1 | 72 | 3 | 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
------------------------------------------
* 1 - filter(DECODE("ISNUMERIC"("TEST_RESULT"),1,TO_NUMBER("TEST_RESULT"),NULL)>100)


Note
-----
- dynamic sampling used for this statement

执行是将decode函数返回的结果再做比较,所以不会存在以上问题。

如果我们需要对以上数据做更新,可以这样写SQL语句:

1
2
3
4
5
6
7
8
9
10
11
update test_table
set test_result = '阳性'
where test_id in (select test_id
from (select test_id,
decode(isnumeric(test_result),
1,
to_number(test_result),
null) test_result_num
from test_table)
where test_result_num > 100);

ORA-01461:仅能绑定要插入的 LONG 列的 LONG 值

问题

使用 NHibernate 插入实体,发生实体字段类型为 CLOB时 实体插入报错:ORA-01461

排查发现字段长度较短时没有该问题,但是内容超过某一限定长度后就会报错,怀疑长度在 2000 - 4000 时由于将字段类型识别为 LONG 导致。

解决方案

针对该问题暂定的解决方案,如果有更好的方案欢迎补充:

  • 存储较长内容,使用 BLOB 格式;
  • 存储为 CLOB 格式,将内容长度固定补空格到 4000+,避免被转换为 LONG 类型;
    1
    2
    string text = "test";
    text = text.PadRight(4001, ' ');