9 位邮政编码报告的 LEN 为 12

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()构造。这样做可以让你做以下事情来改善这种情况:

  1. ZipCode字段创建为VARCHAR(10)
  2. 在使用STR函数的过程中转换值,对于初始FLOAT432013256,将返回 432013256而不是4.32013e+008(这是转换为 时得到的结果VARCHAR)。
  3. 使用以下内容修复任何丢失的前导零:

    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)


Ken*_*her 8

显然,此时您已经意识到邮政编码应该存储为字符串而不是数字。如果没有其他原因,外国(非美国)邮政编码中经常包含字母。

也就是说,让我们回到你最初的问题。为什么当你有 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 个字符。