你确定 LENGTH('中国') = 2吗?

本文揭示了Oracle数据库中LENGTH函数的一个隐藏Bug,在特定情况下该函数会返回错误的字符串长度。通过对比直接使用LENGTH函数与将其封装到自定义函数中的结果差异,展示了如何触发此Bug,并提供了一种临时解决方案。

简单描述    LENGTH这个获得字符串长度的函数存在BUG

LENGTH这个PLSQL的基本函数相信大家都很熟悉了,他的功能就是获得指定字符串的字符数。一个中文也算是一个字符。
比如:
SQL> select LENGTH('中国') from dual;

LENGTH('中国')
----------------
               2

SQL> select LENGTH('人才168') from dual;

LENGTH('人才168')
-----------------
                5

但是,真的 LENGTH('中国')就等于2吗?其实不然,这里面有一个小BUG,大家在程序开发的时候要加以注意。
我使用的数据库版本是Oracle 9.2i.
LENGTH  SUBSTR这些函数,我想Oracle内部是有一个判断标识的,用来处理国际字符,但是这些函数在函数内部调用的时候,这些标识某些时候不起作用,下面举例说明:
create table T_CODE_COUNTRY
(
  ID          VARCHAR2(10) not null,
  DESCRIPT    VARCHAR2(50) not null,
  CHAR_LENGTH NUMBER
);
--添加测试数据
prompt Loading T_CODE_COUNTRY...
insert into T_CODE_COUNTRY (ID, DESCRIPT, CHAR_LENGTH)
values ('1', '塞内加尔', null);
insert into T_CODE_COUNTRY (ID, DESCRIPT, CHAR_LENGTH)
values ('2', '芬兰', null);
insert into T_CODE_COUNTRY (ID, DESCRIPT, CHAR_LENGTH)
values ('3', '1111111111', null);
insert into T_CODE_COUNTRY (ID, DESCRIPT, CHAR_LENGTH)
values ('4', '中国', null);
insert into T_CODE_COUNTRY (ID, DESCRIPT, CHAR_LENGTH)
values ('5', '意大利', null);
commit;
prompt 5 records loaded
--执行UPDATE 操作,更新CHAR_LENGTH字段
UPDATE T_CODE_COUNTRY SET CHAR_LENGTH = TO_CHAR(LENGTH(DESCRIPT));
COMMIT;
SELECT T.* FROM T_CODE_COUNTRY T;
---RESULT LIST
1塞内加尔4
2芬兰2
3111111111110
4中国2
5意大利3

这是正常的结果,但是如果在程序中不可能只用这么简单的标准函数,通常需要自定义函数来处理复杂的逻辑,所以,现在我们把这个LENGTH封装在一个自定义函数中,函数的代码如下:
CREATE OR REPLACE FUNCTION FN_TEST_GET_LENGTH(i_vString IN VARCHAR2)
    RETURN VARCHAR2 IS
    o_vResult VARCHAR2(300);
BEGIN
    o_vResult := TO_CHAR(LENGTH(i_vString));

    RETURN o_vResult;
END;
/

再次运行UPDATE语句:
UPDATE T_CODE_COUNTRY SET  CHAR_LENGTH =FN_TEST_GET_LENGTH(DESCRIPT);
COMMIT;
SELECT T.* FROM T_CODE_COUNTRY T;
ID         DESCRIPT          CHAR_LENGTH
---------- ----------------- -----------
1          塞内加尔                    4
2          芬兰                        2
3          1111111111                 10
4          中国                        4
5          意大利                      6

看到了吧,中国的长度变成了4,BUG出现了,LENGTH的结果不再是字符数,而变成了字节数。大家在编写自定义函数的时候一定要多加小心了。
那么这个问题怎么解决呢?如何避免这个BUG的发生呢?
我目前没有找到特别好的办法,不过还是被我发现了一个比较搞笑的、哭笑不得的解决方案,那就是,把自定义函数的残书中,加一个莫名的,没有用途的参数就可以了,如下:
CREATE OR REPLACE FUNCTION FN_TEST_GET_LENGTH(i_vString IN VARCHAR2,
                                              i_vUnUsed IN VARCHAR2)
    RETURN VARCHAR2 IS
    o_vResult VARCHAR2(300);
BEGIN
    o_vResult := TO_CHAR(LENGTH(i_vString));

    RETURN o_vResult;
END;
/
再次运行UPDATE:
UPDATE T_CODE_COUNTRY SET  CHAR_LENGTH =FN_TEST_GET_LENGTH(DESCRIPT,NULL);
COMMIT;
SELECT T.* FROM T_CODE_COUNTRY T;
ID         DESCRIPT          CHAR_LENGTH
---------- ----------------- -----------
1          塞内加尔                    4
2          芬兰                        2
3          1111111111                 10
4          中国                        2
5          意大利                      3

问题解决了,但是知其然,不知其所以然,知情者请多多指教! 

import torch from torch.utils.data import Dataset, DataLoader from transformers import BartForConditionalGeneration, BartTokenizer, AdamW from tqdm import tqdm import os print(os.getcwd()) # 自定义数据集类 class SummaryDataset(Dataset): def __init__(self, texts, summaries, tokenizer, max_length=1024): self.texts = texts self.summaries = summaries self.tokenizer = tokenizer self.max_length = max_length def __len__(self): return len(self.texts) def __getitem__(self, idx): text = self.texts[idx] summary = self.summaries[idx] # 对文本和摘要进行编码 inputs = self.tokenizer( text, max_length=self.max_length, truncation=True, padding="max_length", return_tensors="pt" ) labels = self.tokenizer( summary, max_length=self.max_length, truncation=True, padding="max_length", return_tensors="pt" ) # 返回输入和标签 return { "input_ids": inputs["input_ids"].squeeze(), "attention_mask": inputs["attention_mask"].squeeze(), "labels": labels["input_ids"].squeeze(), } # 数据加载函数 def load_data(): # 示例数据(替换为你的数据集) texts = [ "人工智能(AI)是计算机科学的一个分支,旨在创建能够执行通常需要人类智能的任务的系统。", "近年来,人工智能技术取得了显著进展,尤其是在深度学习和神经网络领域。", ] summaries = [ "人工智能是计算机科学的一个分支,旨在创建能够执行需要人类智能的任务的系统。", "AI 技术近年来取得了显著进展,尤其是在深度学习和神经网络领域。", ] return texts, summaries # 训练函数 def train(model, dataloader, optimizer, device, epochs=3): model.train() for epoch in range(epochs): epoch_loss = 0 for batch in tqdm(dataloader, desc=f"Epoch {epoch + 1}/{epochs}"): # 将数据移动到设备 input_ids = batch["input_ids"].to(device) attention_mask = batch["attention_mask"].to(device) labels = batch["labels"].to(device) # 前向传播 outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=labels) loss = outputs.loss
03-11
<template> <div class="asset-overview-container container"> <div class="title"> {{ t('Overview') }} </div> <div class="asset-overview-body"> <div class="body-item"> <div class="body-item-top"> <div class="top-icon"></div> <div class="top-body-comp"> <div class="asset-type-title"> {{ t('设备总数') }} </div> <div class="asset-number-title"> {{ handelNumber(props.assetOverviewData.equipmenttotalcount) }} </div> </div> </div> <div class="body-item-bottom"> <div class="body-item-bottom-online"> <div class="online-icon-title"> <div class="online-icon"></div> <div class="online-title"> {{ t('在线') }} </div> </div> <div class="online-number"> {{ handelNumber(props.assetOverviewData.onlineequipmenttotalcount) }} </div> </div> <div class="body-item-bottom-online"> <div class="online-icon-title"> <div class="online-icon offline-icon"></div> <div class="online-title"> {{ t('离线') }} </div> </div> <div class="online-number underline" @click="showDialog('equipment')"> {{ handelNumber(props.assetOverviewData.offlineequipmenttotalcount) }} </div> </div> </div> </div> <div class="body-item"> <div class="body-item-top"> <div class="top-icon fintting-icon"></div> <div class="top-body-comp"> <div class="asset-type-title"> {{ t('配件总数') }} </div> <div class="asset-number-title"> {{ handelNumber(props.assetOverviewData.componenttotalcount) }} </div> </div> </div> <div class="body-item-bottom"> <div class="body-item-bottom-online"> <div class="online-icon-title"> <div class="online-icon"></div> <div class="online-title"> {{ t('在线') }} </div> </div> <div class="online-number"> {{ handelNumber(props.assetOverviewData.onlinecomponenttotalcount) }} </div> </div> <div class="body-item-bottom-online"> <div class="online-icon-title"> <div class="online-icon offline-icon"></div> <div class="online-title"> {{ t('离线') }} </div> </div> <div class="online-number underline" @click="showDialog('accessory')"> {{ handelNumber(props.assetOverviewData.offlinecomponenttotalcount) }} </div> </div> </div> </div> </div> </div> <el-dialog v-model="dialogVisible" :title="dialogTitle" width="80%" top="10vh" :before-close="handleClose" > <!-- 添加按钮 --> <div class="table-toolbar"> <img src="@/assets/images/setup.png" alt="" @click="openColumnSelection" /> </div> <el-table v-loading="loading" :data="tableData" border :max-height="tableMaxHeight"> <el-table-column :label="t('序号')" type="index" :index="indexMethod" width="100" fixed ></el-table-column> <el-table-column v-for="(column, index) in visibleColumns" :key="index" :label="t(column.title)" :prop="column.field" :width="column.width" show-overflow-tooltip > <template v-if="column.title === t('入库时间')" #default="scope"> {{ scope?.row?.inventoryTime ? dayjs(scope?.row?.inventoryTime).format('YYYY-MM-DD HH:mm:ss') : '--' }} </template> <template v-else-if="column.title === t('盘点时间')" #default="scope"> {{ scope?.row?.countingTime ? dayjs(scope?.row?.countingTime).format('YYYY-MM-DD HH:mm:ss') : '--' }} </template> <template v-else #default="scope"> {{ [null, undefined, ''].includes(scope.row[column.field]) ? '--' : scope.row[column.field] }} </template> </el-table-column> </el-table> <div class="table-page"> <Pagination layout="total, sizes, prev, pager, next" :pageSizes="[5, 10, 15, 20, 50]" :total="page.total" :currentPage="page.pageNo" :pageSize="page.pageSize" @changePage="tableChange" ></Pagination> </div> </el-dialog> <!-- 列选择对话框 --> <el-dialog v-model="columnDialogVisible" :title="t('列表字段筛选')" width="38%" @close="onColumnDialogClose" > <div style="max-height: 400px; overflow-y: auto"> <el-checkbox v-model="checkedAll" :indeterminate="isIndeterminate" :label="t('全部')" @change="checkAllFun" /> <!-- 复选框容器(使用 flex 布局) --> <div class="checkbox-container"> <el-checkbox-group v-model="selectedColumns"> <el-checkbox v-for="(column, index) in allColumns" :key="index" :label="column.title" ></el-checkbox> </el-checkbox-group> </div> </div> <template #footer> <span class="dialog-footer"> <el-button @click="columnDialogVisible = false">{{ t('取消') }}</el-button> <el-button type="primary" @click="onColumnConfirm">{{ t('确定') }}</el-button> </span> </template> </el-dialog> </template> <script lang="ts" setup> import { computed, defineProps, onMounted, reactive, ref, watch } from 'vue'; import { useI18n } from 'vue-i18n'; import Pagination from '@/components/Pagination.vue'; import { queryOfflineAssetInfo } from '@/apis/dcPhysicalAPIS'; import { usePropertyMetricsManager } from '@/application/core/infra-layout/composables/usePropertyMetricsManager'; import dayjs from 'dayjs'; import { useMapLevelDataManager } from '@/application/core/infra-layout/composables/useMapLevelDataManager'; let tableMaxHeight = ref(450); const mapLevelDataManagerStore = useMapLevelDataManager(); const mapLevelDataManager = computed(() => mapLevelDataManagerStore); const mapLevelCode = computed(() => mapLevelDataManager.value.getMapLevelCode()); const currentSelectOption = computed(() => { const { areaCode, cityCode, campusCode, dcCode, roomBuilding, floorCode } = mapLevelCode.value; return [areaCode, cityCode, campusCode, dcCode, roomBuilding, floorCode].filter(Boolean); }); const loading = ref(false); const propertyMetricsManager = usePropertyMetricsManager(); const { t } = useI18n(); const props = defineProps({ assetOverviewData: { type: Object, default: () => ({}), }, }); const handelNumber = (num, fixed) => { if (num || num === 0) { return fixed ? Number(num).toFixed(fixed) : num; } else { return '--'; } }; // 弹窗 const dialogVisible = ref(false); const dialogType = ref<string | null>(null); const tableData = ref([]); const page = reactive({ total: 0, pageNo: 1, pageSize: 10, }); // 列相关状态 const columnDialogVisible = ref(false); const selectedColumns = ref<string[]>([]); // 用户选择的列标题 const allColumns = ref<Array<{ title: string; field: string; width: number; visible: boolean }>>( [], ); const visibleColumns = ref< Array<{ title: string; field: string; width: number; visible: boolean }> >([]); const checkedAll = computed({ get: () => { const total = allColumns.value.length; const selected = allColumns.value.filter(col => col.visible).length; return selected === total; }, set: (val: boolean) => { checkAllFun(val); }, }); // 半选状态 const isIndeterminate = computed(() => { const total = allColumns.value.length; const selected = allColumns.value.filter(col => col.visible).length; return selected > 0 && selected < total; }); // 列配置映射 const columnsMap = { equipment: [ { title: t('国家'), field: 'country', width: 80 }, { title: t('大区'), field: 'area', width: 80 }, { title: t('城市'), field: 'city', width: 120 }, { title: t('园区名称'), field: 'campusName', width: 170 }, { title: t('园区编码'), field: 'campusCode', width: 110 }, { title: t('楼栋(DC)'), field: 'dcName', width: 270 }, { title: t('DC编码'), field: 'dcCode', width: 130 }, { title: t('楼层'), field: 'floor', width: 80 }, { title: t('房间'), field: 'roomName', width: 260 }, { title: t('房间编码'), field: 'roomCode', width: 110 }, { title: t('机柜/货架'), field: 'rackId', width: 150 }, { title: t('云业务'), field: 'cloudName', width: 140 }, { title: t('云业务编码'), field: 'cloudId', width: 110 }, { title: t('资产所有权类型'), field: 'assetOwnership', width: 150 }, { title: t('资产所有权'), field: 'assetOwnershipName', width: 120 }, { title: t('资产所有权类型编码'), field: 'assetOwnershipCode', width: 160 }, { title: t('场地类型'), field: 'machineRoomTypeName', width: 100 }, { title: t('场地类型编码'), field: 'machineRoomTypeCode', width: 130 }, { title: t('资产类型'), field: 'assetTypeName', width: 120 }, { title: t('资产类型编码'), field: 'assetTypeCode', width: 120 }, { title: t('入库时间'), field: 'inventoryTime', width: 170 }, { title: t('库存时长(天)'), field: 'inventoryDuration', width: 130 }, { title: t('是否库存超期'), field: 'isOverdue', width: 110 }, { title: t('盘点结果'), field: 'countingResult', width: 100 }, { title: t('序列号'), field: 'sn', width: 200 }, { title: t('日期'), field: 'date', width: 120 }, { title: t('年月'), field: 'yearMonth', width: 80 }, { title: t('盘点时间'), field: 'countingTime', width: 110 }, { title: t('大类'), field: 'category', width: 150 }, { title: t('小类'), field: 'subcategory', width: 150 }, { title: t('发货SN'), field: 'shipmentSn', width: 200 }, { title: t('业务标签'), field: 'label', width: 110 }, { title: t('申购BOM'), field: 'purchaseBom', width: 110 }, { title: t('BOM编码'), field: 'bomCode', width: 110 }, { title: t('DC全称'), field: 'dcFullName', width: 290 }, { title: t('资产状态'), field: 'status', width: 110 }, ], }; // 根据dialogType获取列配置 const getColumnsByType = (type: string | null) => columnsMap[type] || columnsMap.equipment; // 初始化列数据 const initColumns = () => { const columns = getColumnsByType(dialogType.value); allColumns.value = columns.map(col => ({ title: col.title, field: col.field, width: col.width, visible: true, })); // 初始化 selectedColumns selectedColumns.value = allColumns.value.filter(col => col.visible).map(col => col.title); }; const indexMethod = index => { const indexNum = (index + 1 + (page.pageNo - 1) * page.pageSize).toString().padStart(2, '0'); return indexNum; }; // 弹窗标题 const dialogTitle = computed(() => dialogType.value === 'equipment' ? t('离线设备明细') : t('离线配件明细'), ); const showDialog = (type: string) => { dialogType.value = type; initColumns(); // 初始化列数据 getTableData(type); dialogVisible.value = true; }; // 处理多选 const handleCondition = condition => { const multipleNames = [...propertyMetricsManager.multipleNames].map(i => i.value); const cityMultipleNames = [...propertyMetricsManager.multipleNames].map(i => i.name); if (!multipleNames.length) { return; } if (['China', 'Abroad', 'global'].includes(mapLevelDataManager.value.level)) { Reflect.set(condition.value, 'area', cityMultipleNames); } if (mapLevelDataManager.value.level === 'area') { Reflect.set(condition.value, 'city', cityMultipleNames); } if (mapLevelDataManager.value.level === 'city') { Reflect.set(condition.value, 'campusCode', multipleNames); } if (mapLevelDataManager.value.level === 'campus') { Reflect.set(condition.value, 'buildingCode', multipleNames); } }; const getDetailCondition = condition => { if (mapLevelCode.value.country === 'China') { Reflect.set(condition.value, 'country', '中国'); } else if (mapLevelCode.value.country === 'Abroad') { Reflect.set(condition.value, 'country', '海外'); } else { Reflect.set(condition.value, 'country', ''); } }; // 单选 const getDetailReflect = condition => { if (currentSelectOption.value && currentSelectOption.value.length) { if (mapLevelDataManager.value.level === 'area') { Reflect.set(condition.value, 'area', [currentSelectOption.value[0]]); } else if (mapLevelDataManager.value.level === 'city') { let city = ''; for (let item of mapLevelDataManager.value.campusData) { if (item.city_code === currentSelectOption.value[1]) { city = item.city; } } Reflect.set(condition.value, 'city', [city]); } else if (mapLevelDataManager.value.level === 'campus') { Reflect.set(condition.value, 'campusCode', [currentSelectOption.value[2]]); } else if (mapLevelDataManager.value.level === 'dc') { Reflect.set(condition.value, 'dcCode', [currentSelectOption.value[3]]); Reflect.set(condition.value, 'building', currentSelectOption.value[4]); } else { } } else { Reflect.set(condition.value, 'area', []); Reflect.set(condition.value, 'city', []); Reflect.set(condition.value, 'campusCode', []); Reflect.set(condition.value, 'dcCode', []); Reflect.set(condition.value, 'building', ''); } }; const getTableData = async type => { let condition = ref({}); getDetailCondition(condition); getDetailReflect(condition); handleCondition(condition); let params = { condition: condition.value, pageNo: page.pageNo, pageSize: page.pageSize, type: type === 'equipment' ? 'offline_equipment' : 'offline_component', }; condition.value.date = propertyMetricsManager.timeValue; condition.value.machineRoomTypeCode = propertyMetricsManager.propertyValue.machineroomTypeList; condition.value.assetOwnershipTypeCode = propertyMetricsManager.propertyValue.assetownershipList; condition.value.cloudId = propertyMetricsManager.propertyValue.cloudTypeList; loading.value = true; const res = await queryOfflineAssetInfo(params); loading.value = false; tableData.value = res?.data; page.total = res?.total; }; const handleClose = () => { dialogVisible.value = false; }; const tableChange = (pages: object) => { page.pageNo = pages.currentPage; page.pageSize = pages.pageSize; getTableData(dialogType.value || 'equipment'); }; // 列选择功能 const openColumnSelection = () => { columnDialogVisible.value = true; }; const onColumnDialogClose = () => { columnDialogVisible.value = false; }; // 确认列选择 const onColumnConfirm = () => { visibleColumns.value = allColumns.value.filter(col => col.visible); columnDialogVisible.value = false; }; // 全选功能处理 const checkAllFun = (checked: boolean) => { allColumns.value.forEach(col => { col.visible = checked; }); selectedColumns.value = checked ? allColumns.value.map(col => col.title) : []; }; watch( () => selectedColumns.value, newSelected => { allColumns.value.forEach(col => { col.visible = newSelected.includes(col.title); }); // 更新全选状态 checkedAll.value = newSelected.length === allColumns.value.length; isIndeterminate.value = newSelected.length > 0 && newSelected.length < allColumns.value.length; }, { deep: true }, ); onMounted(() => { let offHeight = document.getElementsByClassName('aside-content')[0].offsetHeight; tableMaxHeight.value = offHeight - 260; }); </script> <style lang="less" scoped> .asset-overview-container { width: 100%; height: 200px; overflow: hidden; background-color: #fff; border-radius: 10px; border: 1px solid #f1f1f3; .title { margin: 20px; font-weight: 600; font-size: 14px; } .asset-overview-body { width: 100%; display: flex; .body-item { width: 50%; .body-item-top { display: flex; margin-left: 16px; .top-icon { width: 50px; height: 50px; background-color: #f2f5fc; background-image: url('../assets/equip.png'); background-repeat: no-repeat; background-size: 100%; border-radius: 50%; } .fintting-icon { background-image: url('../assets/fitting.png'); } .top-body-comp { margin-left: 10px; .asset-type-title { font-size: 14px; font-weight: 600; } .asset-number-title { font-size: 16px; font-weight: 600; margin-top: 12px; } } } .body-item-bottom { margin-top: 12px; display: flex; justify-content: space-around; width: 100%; .body-item-bottom-online { width: calc(50% - 20px); background-color: #f2f5fc; padding: 12px 0; .online-icon-title { display: flex; margin-left: 12px; align-items: center; .online-icon { background-image: url('../assets/online.png'); width: 16px; height: 16px; background-repeat: no-repeat; background-size: 100%; } .online-icon.offline-icon { background-image: url('../assets/offline.png'); } .online-title { font-size: 14px; font-weight: 600; margin-left: 4px; } } .online-number { font-weight: 600; font-size: 14px; margin-top: 8px; margin-left: 12px; } .underline { text-decoration: underline; text-decoration-color: #0a0a0a; /* 下划线颜色 */ text-decoration-thickness: 1px; /* 下划线粗细 */ cursor: pointer; } } } } } } .table-toolbar { text-align: right; margin-bottom: 10px; } .checkbox-container { display: flex; flex-wrap: wrap; } .checkbox-container .el-checkbox { flex: 0 0 25%; } /* 可选:调整复选框的最小宽度 */ .checkbox-container .el-checkbox { min-width: 130px; } :deep(.common-pagination) { justify-content: flex-start; } .dark { .asset-overview-container { background: #282b33; border-color: rgba(223, 225, 230, 0.1); color: #dfe1e6; .asset-overview-body { .body-item { .body-item-bottom { .body-item-bottom-online { background: #282b33; } } } } } } .el-table { --el-table-header-bg-color: #f2f5fc; } </style> 要正确显示半选状态,看看哪里有问题
10-15
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值