在 sql 插入语句中添加前导零
Add leading zeros on an sql insert statement
我有一组字段(全部为字符),我正在从另一个 table(table(new_table) 写入一个 table( old_table)。 四个字段 old_table - 月、日、世纪、年(同样,在字符中)。我将这些日期作为一个字段添加到 new_table - invdat(char)。客户想 向日期 添加前导零,因为它们当前未存储在 old_table.
中
这是一个示例:
INSERT INTO new_table
SELECT month || oedy01 || oecc01 || oeyr01 || as invdat
FROM old_table
因此,我需要在这些字段中添加前导零,然后再将它们放入 new_table 中的 invdat 字段。
如有任何建议,我们将不胜感激。
具有较旧软件的系统将日期存储为字符串的原因有很多。很可能是因为该软件是在日期数据类型在 RPG 中很容易获得之前编写的。上面的评论提到它已经这样做了 20 年。它周围可能有很多代码并且更改它以满足 "newer" 约定是成本高昂的。尽管 DB2/400 和 RPG IV 在今天处理日期相当不错,但谁愿意在有其他工作要做的情况下重新编码和重新测试一个已经运行了几十年的大型系统?也就是说,这是一种简单的方法,使用 AS/400-happy DB2 SQL,将您的字段零填充到单个日期字段中:
INSERT INTO new_table
SELECT Right('00'||month,2) || Right('00'||oedy01,2) || Right('00'||oecc01,2) || Right('00'||oeyr01,2) as invdat
FROM old_table
我建议与 AS/400 开发人员一起验证该日期格式。通常,您会看到以 ccyymmdd 格式存储的日期,但对此没有硬性规定。
通常,OP 没有提供他们的 DDL。
FWiW,存储日期的单独组成部分会有很大的优势,if 大多数查询 activity 取决于这些组成部分并且它们已被索引;尽管具有派生索引的附加功能,诸如 YEAR(date_field) 或 SUBSTR(mmddyyyy_string_field, 5, 4) 之类的表达式作为键可以改善 运行 时间派生的问题针对其执行这些选择。以所选格式转换为所描述的 INVDAT 并不是选择 IMO 的所有 good,即使大多数选择不针对特定的日期组件;我稍后再评论。
虽然人们可能期望接受的答案应该给出大致正确的结果,但由于对数据域的期望是至少一位数字 [字符串或数字] 的明显日期值,结果在很大程度上取决于数据类型;很明显,根据旧值中的 "leading zeros … are not currently stored",数据类型明显是字符串与数字,并且根据“(再次,在 char 中)”,人们可能期望 CHAR 与 VARCHAR。而作为string-data,也没有提到non-zero-padded\prepended存储的值是左调还是右调。
那个和 other\future 答案和评论可能不适合实际的和未知的\未说明的 DDL,因此这些回复应该以有效的 "with this DDL as a given";然而,通常情况下,回应者很少会提供这种澄清:-(
例如,已经在评论中提到的 Left Pad (LPAD) 标量 [自 IBM DB2 for i 7.1 起可用]。虽然 LPAD 可能比使用存在时间更长且功能强大的 RIGHT 标量更明显地适合预期效果的标量请求[当与一个或多个零的字符串组合时,根据实际数据的需要在 OP 中也留下了 undescribed,但肯定只有在连接到 stripped 值时才有效],LPAD 也可能会因 OP 而失败,因为他们的原始数据可能是 CHAR [per "(again, in char)"] 而不是 VARCHAR;即 LPAD 未通过 常识 测试,因为未能实现大多数 likely\rational 意图,而是仅作为针对固定长度 CHAR 数据的有效子字符串运行,尽管一个 SUBSTR 函数已经存在很长时间了。
当然,对于 OP 对 DDL 的推断,即使是表达式 RIGHT('00' CONCAT char_2_field, 2)
也无法产生理想的结果;例如使用字段中的左调整值 '1 '
,该表达式有效地要求 RIGHT(CHAR('001 ', 4), 2)
结果是 '1 '
而不是假定的 '01'
并且在字段中使用右调整值 ' 1'
,该表达式有效地要求 RIGHT(CHAR('00 1', 4), 2)
结果是 ' 1'
而不是假定的 '01'
. 那个答案被接受了,我真的有点惊讶;也就是说,令我震惊的是,显然,原始 DDL 将字段声明为 VARCHAR,并且这些值显然在存储或正确投射时也被正确剥离。
如果这些字段是 VARCHAR 或被 STRIP [或 LTRIM 或类似] 作用以影响不同字符串的数据类型[根据含义,似乎可选地使用 left-trimmed 结果有关 LPAD 效果的文档,但我的经验表明建议使用 left-trim 或完全剥离],那么对于 answer[=] 中提供的表达式,可以按预期生成字符串71=] 和 评论.
注释中注明的 DATE 标量可以省略,日期组件的顺序可以重新排序。但是作为字符串而不是 DATE 数据类型的结果,OP 中显示的格式化为 MMDDYYYY 的字符串不是 有序 [缺少词典编纂质量]因此在排序和选择性方面是无用的,除了[不]相等的谓词;格式为 YYYYMMDD 的字符串 [几乎像该评论试图提供的内容] is 有序,因此非常适合排序和相对 [rather比只匹配|不匹配]的选择性;没有对 DATE 算法和验证的固有支持是一个较小的负面影响。
注意:以下用于 DDL 和填充数据的脚本操作假定 "cc" 指的是简单的计数世纪数字两位数值;即 "cc" 忽略零世纪,因此 '19' 表示 1900 年代而不是 1800 年代,并且 "cc" 不是替代映射,例如 '00'->'19','01 '->'20'等
启用给定查询和备用查询测试的设置:
create table old_table
( month char(2) not null with default
, oedy01 char(2) not null with default
, oecc01 char(2) not null with default
, oeyr01 char(2) not null with default
)
;
create table new_table
( invdat char(8) not null with default
)
;
insert into old_table /* mm, dd, cc, yy */ values
( ' 1', ' 1', '19', '40' ), ( '3 ', '3 ', '19', '40' )
, ( ' 1', ' 1', '20', ' 0' ), ( '3 ', '3 ', '20', '0 ' )
, ( ' 2', ' 2', '20', ' ' ), ( '4 ', '4 ', '20', ' ' )
, ( ' 1', ' 1', '20', ' 1' ), ( '3 ', '3 ', '20', '1 ' )
, ( ' 1', ' 1', '20', '39' ), ( '3 ', '3 ', '20', '39' )
, ( ' 1', ' 1', ' 0', ' 1' ), ( '3 ', '3 ', '0 ', '1 ' )
, ( ' 2', ' 2', ' ', ' 1' ), ( '4 ', '4 ', ' ', '1 ' )
; -- mm(1,2):right-adj, mm(3,4):left-adj, mm(2,4):blank-elems
-- 14 rows inserted in OLD_TABLE
现在查询上面的设置:
SELECT Right('00'||month, 2) || Right('00'||oedy01, 2)
|| Right('00'||oecc01, 2) || Right('00'||oeyr01, 2) as invdat
from old_table
where month not in (2, 4)
; -- report of above query follows; no leading zeroes anywhere:
INVDAT
1 11940
3 3 1940
1 120 0
3 3 200
1 120 1
3 3 201
1 12039
3 3 2039
1 1 0 1
3 3 0 1
-- end of data --
select
lpad( month , 2, '0') concat lpad( oedy01, 2, '0') concat
lpad( oecc01 , 2, '0') concat lpad( oeyr01, 2, '0') as invdat
from old_table
where month not in (2, 4)
; -- report of above query follows; no leading zeroes anywhere:
INVDAT
1 11940
3 3 1940
1 120 0
3 3 200
1 120 1
3 3 201
1 12039
3 3 2039
1 1 0 1
3 3 0 1
-- end of data; a duplicate of the prior query report --
因此对于给定的设置,上述查询不是很有帮助。通过对每个查询进行微小修改以确保首先去除 CHAR 数据中多余的空白,结果会好得多:
select
lpad( strip( month ) , 2, '0') concat
lpad( strip( oedy01) , 2, '0') concat
lpad( strip( oecc01) , 2, '0') concat
lpad( strip( oeyr01) , 2, '0') as invdat
from old_table
/* where month not in (2, 4) -- this *fixes* blank values too */
; -- report of above query follows; expected leading zeroes appear:
INVDAT
01011940
03031940
01012000
03032000
02022000
04042000
01012001
03032001
01012039
03032039
01010001
03030001
02020001
04040001
-- end of data --
SELECT
Right('00' concat strip( month ) , 2) concat
Right('00' concat strip( oedy01) , 2) concat
Right('00' concat strip( oecc01) , 2) concat
Right('00' concat strip( oeyr01) , 2) as invdat
from old_table
/* where month not in (2, 4) -- this *fixes* blank values too */
; -- report of above query matches the prior; not repeated here
如果 DDL 是数字并且已知为值存储了有效的十进制数据,那么以下可能是一个代表性的脚本:
drop table old_table
;
create table old_table
( month dec (2) not null with default
, oedy01 dec (2) not null with default
, oecc01 dec (2) not null with default
, oeyr01 dec (2) not null with default
)
;
insert into old_table /* mm, dd, cc, yy */ values
( ' 1', ' 1', '19', '40' ), ( '3 ', '3 ', '19', '40' )
, ( ' 1', ' 1', '20', ' 0' ), ( '3 ', '3 ', '20', '0 ' )
, ( ' 1', ' 1', '20', ' 1' ), ( '3 ', '3 ', '20', '1 ' )
, ( ' 1', ' 1', '20', '39' ), ( '3 ', '3 ', '20', '39' )
, ( ' 1', ' 1', ' 0', ' 1' ), ( '3 ', '3 ', '0 ', '1 ' )
; -- notably, no blank values per inability to cast to decimal
select
digits(month ) concat digits(oedy01) concat
digits(oecc01) concat digits(oeyr01) as invdat
from old_table
; -- report from above query follows; DIGITS casting gives leading zeros:
INVDAT
01011940
03031940
01012000
03032000
01012001
03032001
01012039
03032039
01010001
03030001
-- end of data --
两个查询都提供了[我在上述示例中包含的变体;来自 answer 和 comment 的 RIGHT() 表达式和 LPAD()] 将给出相同的报告如上。这是因为从 CHAR 到 DECIMAL 的隐式转换的结果是数值的左调整可变字符串表示形式,因此隐式地为 left-trimmed 和可变长度,因此这些标量函数将产生所需的结果输出。
如果上述使用数字数据类型的脚本[而不是提到的 "all char" 数据类型] 是 answer 被 OP 选择为可以接受,那么这有助于强调问题场景的解释是多么糟糕,并暗示通过包含 DDL 可以很容易地避免这种情况;更好的是,额外包含示例数据。
我有一组字段(全部为字符),我正在从另一个 table(table(new_table) 写入一个 table( old_table)。 四个字段 old_table - 月、日、世纪、年(同样,在字符中)。我将这些日期作为一个字段添加到 new_table - invdat(char)。客户想 向日期 添加前导零,因为它们当前未存储在 old_table.
中这是一个示例:
INSERT INTO new_table
SELECT month || oedy01 || oecc01 || oeyr01 || as invdat
FROM old_table
因此,我需要在这些字段中添加前导零,然后再将它们放入 new_table 中的 invdat 字段。
如有任何建议,我们将不胜感激。
具有较旧软件的系统将日期存储为字符串的原因有很多。很可能是因为该软件是在日期数据类型在 RPG 中很容易获得之前编写的。上面的评论提到它已经这样做了 20 年。它周围可能有很多代码并且更改它以满足 "newer" 约定是成本高昂的。尽管 DB2/400 和 RPG IV 在今天处理日期相当不错,但谁愿意在有其他工作要做的情况下重新编码和重新测试一个已经运行了几十年的大型系统?也就是说,这是一种简单的方法,使用 AS/400-happy DB2 SQL,将您的字段零填充到单个日期字段中:
INSERT INTO new_table
SELECT Right('00'||month,2) || Right('00'||oedy01,2) || Right('00'||oecc01,2) || Right('00'||oeyr01,2) as invdat
FROM old_table
我建议与 AS/400 开发人员一起验证该日期格式。通常,您会看到以 ccyymmdd 格式存储的日期,但对此没有硬性规定。
通常,OP 没有提供他们的 DDL。
FWiW,存储日期的单独组成部分会有很大的优势,if 大多数查询 activity 取决于这些组成部分并且它们已被索引;尽管具有派生索引的附加功能,诸如 YEAR(date_field) 或 SUBSTR(mmddyyyy_string_field, 5, 4) 之类的表达式作为键可以改善 运行 时间派生的问题针对其执行这些选择。以所选格式转换为所描述的 INVDAT 并不是选择 IMO 的所有 good,即使大多数选择不针对特定的日期组件;我稍后再评论。
虽然人们可能期望接受的答案应该给出大致正确的结果,但由于对数据域的期望是至少一位数字 [字符串或数字] 的明显日期值,结果在很大程度上取决于数据类型;很明显,根据旧值中的 "leading zeros … are not currently stored",数据类型明显是字符串与数字,并且根据“(再次,在 char 中)”,人们可能期望 CHAR 与 VARCHAR。而作为string-data,也没有提到non-zero-padded\prepended存储的值是左调还是右调。
那个和 other\future 答案和评论可能不适合实际的和未知的\未说明的 DDL,因此这些回复应该以有效的 "with this DDL as a given";然而,通常情况下,回应者很少会提供这种澄清:-(
例如,已经在评论中提到的 Left Pad (LPAD) 标量 [自 IBM DB2 for i 7.1 起可用]。虽然 LPAD 可能比使用存在时间更长且功能强大的 RIGHT 标量更明显地适合预期效果的标量请求[当与一个或多个零的字符串组合时,根据实际数据的需要在 OP 中也留下了 undescribed,但肯定只有在连接到 stripped 值时才有效],LPAD 也可能会因 OP 而失败,因为他们的原始数据可能是 CHAR [per "(again, in char)"] 而不是 VARCHAR;即 LPAD 未通过 常识 测试,因为未能实现大多数 likely\rational 意图,而是仅作为针对固定长度 CHAR 数据的有效子字符串运行,尽管一个 SUBSTR 函数已经存在很长时间了。
当然,对于 OP 对 DDL 的推断,即使是表达式 RIGHT('00' CONCAT char_2_field, 2)
也无法产生理想的结果;例如使用字段中的左调整值 '1 '
,该表达式有效地要求 RIGHT(CHAR('001 ', 4), 2)
结果是 '1 '
而不是假定的 '01'
并且在字段中使用右调整值 ' 1'
,该表达式有效地要求 RIGHT(CHAR('00 1', 4), 2)
结果是 ' 1'
而不是假定的 '01'
. 那个答案被接受了,我真的有点惊讶;也就是说,令我震惊的是,显然,原始 DDL 将字段声明为 VARCHAR,并且这些值显然在存储或正确投射时也被正确剥离。
如果这些字段是 VARCHAR 或被 STRIP [或 LTRIM 或类似] 作用以影响不同字符串的数据类型[根据含义,似乎可选地使用 left-trimmed 结果有关 LPAD 效果的文档,但我的经验表明建议使用 left-trim 或完全剥离],那么对于 answer[=] 中提供的表达式,可以按预期生成字符串71=] 和 评论.
注释中注明的 DATE 标量可以省略,日期组件的顺序可以重新排序。但是作为字符串而不是 DATE 数据类型的结果,OP 中显示的格式化为 MMDDYYYY 的字符串不是 有序 [缺少词典编纂质量]因此在排序和选择性方面是无用的,除了[不]相等的谓词;格式为 YYYYMMDD 的字符串 [几乎像该评论试图提供的内容] is 有序,因此非常适合排序和相对 [rather比只匹配|不匹配]的选择性;没有对 DATE 算法和验证的固有支持是一个较小的负面影响。
注意:以下用于 DDL 和填充数据的脚本操作假定 "cc" 指的是简单的计数世纪数字两位数值;即 "cc" 忽略零世纪,因此 '19' 表示 1900 年代而不是 1800 年代,并且 "cc" 不是替代映射,例如 '00'->'19','01 '->'20'等
启用给定查询和备用查询测试的设置:
create table old_table
( month char(2) not null with default
, oedy01 char(2) not null with default
, oecc01 char(2) not null with default
, oeyr01 char(2) not null with default
)
;
create table new_table
( invdat char(8) not null with default
)
;
insert into old_table /* mm, dd, cc, yy */ values
( ' 1', ' 1', '19', '40' ), ( '3 ', '3 ', '19', '40' )
, ( ' 1', ' 1', '20', ' 0' ), ( '3 ', '3 ', '20', '0 ' )
, ( ' 2', ' 2', '20', ' ' ), ( '4 ', '4 ', '20', ' ' )
, ( ' 1', ' 1', '20', ' 1' ), ( '3 ', '3 ', '20', '1 ' )
, ( ' 1', ' 1', '20', '39' ), ( '3 ', '3 ', '20', '39' )
, ( ' 1', ' 1', ' 0', ' 1' ), ( '3 ', '3 ', '0 ', '1 ' )
, ( ' 2', ' 2', ' ', ' 1' ), ( '4 ', '4 ', ' ', '1 ' )
; -- mm(1,2):right-adj, mm(3,4):left-adj, mm(2,4):blank-elems
-- 14 rows inserted in OLD_TABLE
现在查询上面的设置:
SELECT Right('00'||month, 2) || Right('00'||oedy01, 2)
|| Right('00'||oecc01, 2) || Right('00'||oeyr01, 2) as invdat
from old_table
where month not in (2, 4)
; -- report of above query follows; no leading zeroes anywhere:
INVDAT
1 11940
3 3 1940
1 120 0
3 3 200
1 120 1
3 3 201
1 12039
3 3 2039
1 1 0 1
3 3 0 1
-- end of data --
select
lpad( month , 2, '0') concat lpad( oedy01, 2, '0') concat
lpad( oecc01 , 2, '0') concat lpad( oeyr01, 2, '0') as invdat
from old_table
where month not in (2, 4)
; -- report of above query follows; no leading zeroes anywhere:
INVDAT
1 11940
3 3 1940
1 120 0
3 3 200
1 120 1
3 3 201
1 12039
3 3 2039
1 1 0 1
3 3 0 1
-- end of data; a duplicate of the prior query report --
因此对于给定的设置,上述查询不是很有帮助。通过对每个查询进行微小修改以确保首先去除 CHAR 数据中多余的空白,结果会好得多:
select
lpad( strip( month ) , 2, '0') concat
lpad( strip( oedy01) , 2, '0') concat
lpad( strip( oecc01) , 2, '0') concat
lpad( strip( oeyr01) , 2, '0') as invdat
from old_table
/* where month not in (2, 4) -- this *fixes* blank values too */
; -- report of above query follows; expected leading zeroes appear:
INVDAT
01011940
03031940
01012000
03032000
02022000
04042000
01012001
03032001
01012039
03032039
01010001
03030001
02020001
04040001
-- end of data --
SELECT
Right('00' concat strip( month ) , 2) concat
Right('00' concat strip( oedy01) , 2) concat
Right('00' concat strip( oecc01) , 2) concat
Right('00' concat strip( oeyr01) , 2) as invdat
from old_table
/* where month not in (2, 4) -- this *fixes* blank values too */
; -- report of above query matches the prior; not repeated here
如果 DDL 是数字并且已知为值存储了有效的十进制数据,那么以下可能是一个代表性的脚本:
drop table old_table
;
create table old_table
( month dec (2) not null with default
, oedy01 dec (2) not null with default
, oecc01 dec (2) not null with default
, oeyr01 dec (2) not null with default
)
;
insert into old_table /* mm, dd, cc, yy */ values
( ' 1', ' 1', '19', '40' ), ( '3 ', '3 ', '19', '40' )
, ( ' 1', ' 1', '20', ' 0' ), ( '3 ', '3 ', '20', '0 ' )
, ( ' 1', ' 1', '20', ' 1' ), ( '3 ', '3 ', '20', '1 ' )
, ( ' 1', ' 1', '20', '39' ), ( '3 ', '3 ', '20', '39' )
, ( ' 1', ' 1', ' 0', ' 1' ), ( '3 ', '3 ', '0 ', '1 ' )
; -- notably, no blank values per inability to cast to decimal
select
digits(month ) concat digits(oedy01) concat
digits(oecc01) concat digits(oeyr01) as invdat
from old_table
; -- report from above query follows; DIGITS casting gives leading zeros:
INVDAT
01011940
03031940
01012000
03032000
01012001
03032001
01012039
03032039
01010001
03030001
-- end of data --
两个查询都提供了[我在上述示例中包含的变体;来自 answer 和 comment 的 RIGHT() 表达式和 LPAD()] 将给出相同的报告如上。这是因为从 CHAR 到 DECIMAL 的隐式转换的结果是数值的左调整可变字符串表示形式,因此隐式地为 left-trimmed 和可变长度,因此这些标量函数将产生所需的结果输出。
如果上述使用数字数据类型的脚本[而不是提到的 "all char" 数据类型] 是 answer 被 OP 选择为可以接受,那么这有助于强调问题场景的解释是多么糟糕,并暗示通过包含 DDL 可以很容易地避免这种情况;更好的是,额外包含示例数据。