Geo*_*wdy 3 sql-server sql-server-2012 openrowset
我正在尝试解决让邮政编码正确显示的一些困难。
原始电子表格具有混合 5 位和 9 位格式的邮政编码。导入过程后,这些 9 位邮政编码报告的长度为 12 位。现在,当我尝试在 9 位邮政编码中添加连字符“-”时,由于长度错误和各种数据类型转换问题,我得到了异常结果和错误。
导入是使用 openrowset 方法从电子表格导入数据执行的。
当我查询新导入的数据时,我看到邮政编码显示与电子表格中的相同,但长度错误。
SELECT ZIP,
LEN(LTRIM(RTRIM(Zip))) AS ZIPLENGTH
FROM XLS_IMPORT
Run Code Online (Sandbox Code Playgroud)
SELECT ZIP,
LEN(LTRIM(RTRIM(Zip))) AS ZIPLENGTH
FROM XLS_IMPORT
Run Code Online (Sandbox Code Playgroud)
如果我选择数据的左侧 9 个字符,则所有内容都将转换为浮点数,并且邮政编码现在无法读取。
SELECT LEFT(ZIP,9) FROM XLS_IMPORT
WHERE LEN(LTRIM(RTRIM(ZIP))) = 12
Run Code Online (Sandbox Code Playgroud)
ZIP ZIPLENGTH
45750 5
432013256 12
441153221 12
44120 5
441351362 12
Run Code Online (Sandbox Code Playgroud)
如何将这些邮政编码恢复为正确的 9 位数字?或者如何在报告长度为 12 的 9 位邮政编码中添加连字符?我的最终目标是简单地让 9 位邮政编码在中间有一个连字符。
Zip
列的数据类型是float
.
我刚刚发现我的一些电子表格(如 NJ 和 NY)在邮政编码的前导 0 之前有一个撇号。我需要研究如何处理 '0xxxx 邮政编码,以使其在我的一些电子表格导入中工作。
Sol*_*zky 10
邮政编码是字符串,而不是数字。其中一些有 1 个甚至 2 个(但不超过 2 个)前导零。导入表中的数据类型应该VARCHAR(10)
能够容纳 5 位和 9 位 + 连字符邮政编码。即使您永远不必存储其他国家/地区的邮政编码,并且即使这些值只有数字(即 0 - 9),这些数据仍然是字符串数据,就像电话号码一样。
基于您的其他问题中显示的导入查询(自动导入和导出进程 EXCEL -> SQL SERVER -> EXCEL 而不使用 SSIS):
SELECT * INTO XLS_IMPORT
FROM OPENROWSET('Microsoft.ACE.OLEDB.12.0',
'Excel 12.0; Database=C:\RSG_ETL_Tool\Ohio\OH.xls; HDR=YES; IMEX=1',
'SELECT * FROM [OH$]');
Run Code Online (Sandbox Code Playgroud)
我建议不要依赖SELECT INTO
构造来创建XLS_IMPORT
表,而是手动创建导入表,然后使用INSERT INTO ... SELECT FROM OPENROWSET()
构造。这样做可以让你做以下事情来改善这种情况:
ZipCode
字段创建为VARCHAR(10)
FLOAT
值432013256
,将返回 432013256
而不是4.32013e+008
(这是转换为 时得到的结果VARCHAR
)。使用以下内容修复任何丢失的前导零:
CASE
WHEN LEN(LTRIM(STR(@ZipColumn))) BETWEEN 3 AND 4
THEN RIGHT('0000' + LTRIM(STR(@ZipColumn)), 5)
WHEN LEN(LTRIM(STR(@ZipColumn))) BETWEEN 7 AND 8
THEN RIGHT('0000' + LTRIM(STR(@ZipColumn)), 9)
WHEN LEN(LTRIM(STR(@ZipColumn))) IN (5, 9) THEN LTRIM(STR(@ZipColumn))
ELSE 'BadZipCode'
END
Run Code Online (Sandbox Code Playgroud)例子:
DECLARE @ZipColumn FLOAT = 032013256.000000;
SELECT CASE
WHEN LEN(LTRIM(STR(@ZipColumn))) BETWEEN 3 AND 4
THEN RIGHT('0000' + LTRIM(STR(@ZipColumn)), 5)
WHEN LEN(LTRIM(STR(@ZipColumn))) BETWEEN 7 AND 8
THEN RIGHT('0000' + LTRIM(STR(@ZipColumn)), 9)
WHEN LEN(LTRIM(STR(@ZipColumn))) IN (5, 9) THEN LTRIM(STR(@ZipColumn))
ELSE 'BadZipCode'
END;
Run Code Online (Sandbox Code Playgroud)
返回:
SELECT * INTO XLS_IMPORT
FROM OPENROWSET('Microsoft.ACE.OLEDB.12.0',
'Excel 12.0; Database=C:\RSG_ETL_Tool\Ohio\OH.xls; HDR=YES; IMEX=1',
'SELECT * FROM [OH$]');
Run Code Online (Sandbox Code Playgroud)
理想情况下,您可以将电子表格中的列定义固定为字符串。但即使你这样做,保留这些代码仍然是一个好主意。
我的最终目标是简单地让 9 位邮政编码在中间有一个连字符。
考虑到这一目标,以下内联 TVF 可用于将FLOAT
值转换为VARCHAR
,并为 ZIP + 4 值添加连字符。
iTVF 代码:
CREATE FUNCTION dbo.FormatZIPCode(@NumericZIPCode FLOAT)
RETURNS TABLE
WITH SCHEMABINDING
AS RETURN
WITH string AS
(
SELECT LTRIM(STR(@NumericZIPCode)) AS [Value],
LEN(LTRIM(STR(@NumericZIPCode))) AS [Size]
), converted AS
(
SELECT CASE
WHEN st.[Value] IS NULL THEN NULL
WHEN st.[Size] BETWEEN 3 AND 4
THEN RIGHT('0000' + st.[Value], 5)
WHEN st.[Size] BETWEEN 7 AND 8
THEN RIGHT('0000' + st.[Value], 9)
WHEN st.[Size] IN (5, 9)
THEN st.[Value]
ELSE 'BadZipCode'
END AS [ZIP],
st.[Size] AS [OriginalSize]
FROM string st
)
SELECT IIF(cnv.[OriginalSize] >= 7, STUFF(cnv.[ZIP], 6, 0, '-'), cnv.[ZIP])
AS [FormattedZIPCode]
FROM converted cnv;
Run Code Online (Sandbox Code Playgroud)
测试:
SELECT *
FROM (VALUES (CONVERT(FLOAT, NULL)), (1), (12), (123), (1234), (12345),
(123456), (1234567), (12345678), (123456789)) src(val)
CROSS APPLY dbo.FormatZIPCode(src.[val]) frmt;
Run Code Online (Sandbox Code Playgroud)
返回:
CASE
WHEN LEN(LTRIM(STR(@ZipColumn))) BETWEEN 3 AND 4
THEN RIGHT('0000' + LTRIM(STR(@ZipColumn)), 5)
WHEN LEN(LTRIM(STR(@ZipColumn))) BETWEEN 7 AND 8
THEN RIGHT('0000' + LTRIM(STR(@ZipColumn)), 9)
WHEN LEN(LTRIM(STR(@ZipColumn))) IN (5, 9) THEN LTRIM(STR(@ZipColumn))
ELSE 'BadZipCode'
END
Run Code Online (Sandbox Code Playgroud)
为了更清楚地说明所建议的内容,以下显示了上述所有建议的汇总:
CREATE TABLE dbo.XLS_IMPORT
(
Col1 DataTypeForCol1,
Col2 DataTypeForCol2,
ZIPCode VARCHAR(10),
...
);
INSERT INTO dbo.XLS_IMPORT (Col1, Col2, ZIPCode, ...)
SELECT xls.Col1, xls.Col2, zip.[FormattedZIPCode], ...
FROM OPENROWSET('Microsoft.ACE.OLEDB.12.0',
'Excel 12.0; Database=C:\RSG_ETL_Tool\Ohio\OH.xls; HDR=YES; IMEX=1',
'SELECT * FROM [OH$]')
CROSS APPLY dbo.FormatZIPCode(xls.[ZIP]) zip;
Run Code Online (Sandbox Code Playgroud)
一些电子表格“知道”有前导零,因此在 Excel 中的字段前加上一个撇号,以便 Excel 将值视为字符串而不是数字(例如'01234
)。在这种情况下,您可以使用该REPLACE
函数去除该撇号。
-- Test incoming string data (potentially prefixed with a single apostrophe)
SELECT src.[val], frmt.[FormattedZIPCode],CHARINDEX(N'''', src.[val])
FROM (VALUES (NULL), (N'''01234'), (N'''123456789'), (N'123'), (N'12345678')) src(val)
CROSS APPLY dbo.FormatZIPCode(REPLACE(src.[val], N'''', N'')) frmt;
Run Code Online (Sandbox Code Playgroud)
但是,您不能REPLACE
在所有电子表格上使用,因为ZIP
列为数字的电子表格会在将CONVERT_IMPLICIT
值传递给REPLACE
函数时执行 a ,并且转换后的值将采用科学记数法(例如1.23457e+008
)。因此,如果您不能确定 将返回哪种数据类型OPENROWSET
,那么您可以使用IIF
(或者CASE
如果使用 2012 年之前的 SQL Server 版本)并CHARINDEX
测试是否存在撇号。如果数据被返回为FLOAT
,那么在VARCHAR
将值传递到 时将发生的隐式转换CHARINDEX
将不是问题,因为没有撇号,1.23457e+008
并且转换后的值只会转到CHARINDEX
而不是到dbo.FormatZIPCode
。
-- Test handling incoming data as both numeric and string
SELECT src.[val], frmt.[FormattedZIPCode],CHARINDEX(N'''', src.[val])
FROM (VALUES (CONVERT(FLOAT, NULL)), (1), (12), (123), (1234), (12345),
(123456), (1234567), (12345678), (123456789)) src(val)
CROSS APPLY dbo.FormatZIPCode(
IIF(CHARINDEX(N'''', src.[val]) > 0, REPLACE(src.[val], N'''', N''), src.[val])
) frmt;
SELECT src.[val], frmt.[FormattedZIPCode]
FROM (VALUES (N'''01234'), (N'''123456789'), (N'123'), (N'12345678')) src(val)
CROSS APPLY dbo.FormatZIPCode(
IIF(CHARINDEX(N'''', src.[val]) > 0, REPLACE(src.[val], N'''', N''), src.[val])
) frmt;
Run Code Online (Sandbox Code Playgroud)
显然,此时您已经意识到邮政编码应该存储为字符串而不是数字。如果没有其他原因,外国(非美国)邮政编码中经常包含字母。
也就是说,让我们回到你最初的问题。为什么当你有 5 位数字时你的长度显示为 5,而当你只存储 9 位数字时,你的长度显示为 12。这与LEN 函数的工作方式有关。如果您查看此示例代码的 XML 执行计划:
CREATE TABLE #temp (MyFloat float, MyStr varchar(50))
INSERT INTO #temp VALUES (12345,'12345'),(123456789,'123456789')
SELECT len(MyFloat), len(MyStr) FROM #temp
Run Code Online (Sandbox Code Playgroud)
你会注意到这两行:
<ScalarOperator ScalarString="len(CONVERT_IMPLICIT(varchar(23),[tempdb].[dbo].[#temp].[MyFloat],0))">
<ScalarOperator ScalarString="len([tempdb].[dbo].[#temp].[MyStr])">
Run Code Online (Sandbox Code Playgroud)
你怎么看幕后有一个CONVERT_IMPLICIT
浮点值转换为varchar(23)
获得前LEN
。(据我所知,LEN
实际上只适用于字符串。)
因此,让我们CONVERT_IMPLICIT
通过进行显式转换来看看实际返回的是什么。
SELECT CONVERT(varchar(23), MyFloat) FROM #temp
Run Code Online (Sandbox Code Playgroud)
现在我们看到12345
转换干净了。 123456789
但是需要科学记数法。给我们1.23457e+008
。长度为 12 个字符。