web-dev-qa-db-ja.com

SQL Serverの先行ゼロをトリミングするためのより良い技術ですか?

私は this をしばらく使用しています:

SUBSTRING(str_col, PATINDEX('%[^0]%', str_col), LEN(str_col))

しかし最近、「00000000」などの「0」文字がすべて含まれる列に問題があることがわかりました。一致する非「0」文字が見つからないからです。

私が見た代替手法は、TRIMを使用することです。

REPLACE(LTRIM(REPLACE(str_col, '0', ' ')), ' ', '0')

埋め込みスペースがある場合、スペースが「0」に戻されると「0」に変わるため、これには問題があります。

スカラーUDFを回避しようとしています。 SQL Server 2005のUDFで多くのパフォーマンスの問題を発見しました。

134
Cade Roux
SUBSTRING(str_col, PATINDEX('%[^0]%', str_col+'.'), LEN(str_col))
236
Arvo

値をINTEGERにキャストしてからVARCHARに戻すだけではどうですか?

SELECT  CAST(CAST('000000000' AS INTEGER) AS VARCHAR)

--------
       0
31
Quassnoi

ここでの他の答えは、すべてゼロ(または単一のゼロ)がある場合に考慮しないことです。
空の文字列を常にゼロに設定する場合がありますが、これは空白のままにすることになっている場合は間違っています。
元の質問を読み直してください。これは、質問者が望むものに答えます。

解決策1:

--This example uses both Leading and Trailing zero's.
--Avoid losing those Trailing zero's and converting embedded spaces into more zeros.
--I added a non-whitespace character ("_") to retain trailing zero's after calling Replace().
--Simply remove the RTrim() function call if you want to preserve trailing spaces.
--If you treat zero's and empty-strings as the same thing for your application,
--  then you may skip the Case-Statement entirely and just use CN.CleanNumber .
DECLARE @WackadooNumber VarChar(50) = ' 0 0123ABC D0 '--'000'--
SELECT WN.WackadooNumber, CN.CleanNumber,
       (CASE WHEN WN.WackadooNumber LIKE '%0%' AND CN.CleanNumber = '' THEN '0' ELSE CN.CleanNumber END)[AllowZero]
 FROM (SELECT @WackadooNumber[WackadooNumber]) AS WN
 OUTER APPLY (SELECT RTRIM(RIGHT(WN.WackadooNumber, LEN(LTRIM(REPLACE(WN.WackadooNumber + '_', '0', ' '))) - 1))[CleanNumber]) AS CN
--Result: "123ABC D0"

解決策2(サンプルデータを使用):

SELECT O.Type, O.Value, Parsed.Value[WrongValue],
       (CASE WHEN CHARINDEX('0', T.Value)  > 0--If there's at least one zero.
              AND LEN(Parsed.Value) = 0--And the trimmed length is zero.
             THEN '0' ELSE Parsed.Value END)[FinalValue],
       (CASE WHEN CHARINDEX('0', T.Value)  > 0--If there's at least one zero.
              AND LEN(Parsed.TrimmedValue) = 0--And the trimmed length is zero.
             THEN '0' ELSE LTRIM(RTRIM(Parsed.TrimmedValue)) END)[FinalTrimmedValue]
  FROM 
  (
    VALUES ('Null', NULL), ('EmptyString', ''),
           ('Zero', '0'), ('Zero', '0000'), ('Zero', '000.000'),
           ('Spaces', '    0   A B C '), ('Number', '000123'),
           ('AlphaNum', '000ABC123'), ('NoZero', 'NoZerosHere')
  ) AS O(Type, Value)--O is for Original.
  CROSS APPLY
  ( --This Step is Optional.  Use if you also want to remove leading spaces.
    SELECT LTRIM(RTRIM(O.Value))[Value]
  ) AS T--T is for Trimmed.
  CROSS APPLY
  ( --From @CadeRoux's Post.
    SELECT SUBSTRING(O.Value, PATINDEX('%[^0]%', O.Value + '.'), LEN(O.Value))[Value],
           SUBSTRING(T.Value, PATINDEX('%[^0]%', T.Value + '.'), LEN(T.Value))[TrimmedValue]
  ) AS Parsed

結果:

MikeTeeVee_SQL_Server_Remove_Leading_Zeros

概要:

先行ゼロの1回限りの除去には、上記の方法を使用できます。
再利用を計画している場合は、インラインテーブル値関数(ITVF)に配置します。
UDFのパフォーマンスの問題に関する懸念は理解できます。
ただし、この問題はAll-Scalar-FunctionsおよびMulti-Statement-Table-Functionsにのみ適用されます。
ITVFの使用はまったく問題ありません。

サードパーティのデータベースにも同じ問題があります。
英数字フィールドでは、先頭にスペースを入れずに多くのフィールドが入力されます。
これにより、欠落している先行ゼロをクリーンアップしないと結合が不可能になります。

結論:

先行ゼロを削除する代わりに、結合を行うときに、トリミングされた値に先行ゼロをパディングすることを検討することもできます。
さらに良いことに、先行ゼロを追加してからインデックスを再構築することで、テーブル内のデータをクリーンアップします。
これは、はるかに高速で複雑ではないと思います。

SELECT RIGHT('0000000000' + LTRIM(RTRIM(NULLIF(' 0A10  ', ''))), 10)--0000000A10
SELECT RIGHT('0000000000' + LTRIM(RTRIM(NULLIF('', ''))), 10)--NULL --When Blank.
13
MikeTeeVee

スペースの代わりに、0を通常は列のテキストにない「まれな」空白文字に置き換えます。このような列には、おそらくラインフィードで十分です。次に、通常のLTrimを実行し、特殊文字を0に再度置き換えます。

5
Joel Coehoorn

文字列が完全にゼロで構成されている場合、以下は「0」を返します。

CASE WHEN SUBSTRING(str_col, PATINDEX('%[^0]%', str_col+'.'), LEN(str_col)) = '' THEN '0' ELSE SUBSTRING(str_col, PATINDEX('%[^0]%', str_col+'.'), LEN(str_col)) END AS str_col
3
Scott

これにより、素敵な関数が作成されます。

DROP FUNCTION [dbo].[FN_StripLeading]
GO
CREATE FUNCTION [dbo].[FN_StripLeading] (@string VarChar(128), @stripChar VarChar(1))
RETURNS VarChar(128)
AS
BEGIN
-- http://stackoverflow.com/questions/662383/better-techniques-for-trimming-leading-zeros-in-sql-server
    DECLARE @retVal VarChar(128),
            @pattern varChar(10)
    SELECT @pattern = '%[^'+@stripChar+']%'
    SELECT @retVal = CASE WHEN SUBSTRING(@string, PATINDEX(@pattern, @string+'.'), LEN(@string)) = '' THEN @stripChar ELSE SUBSTRING(@string, PATINDEX(@pattern, @string+'.'), LEN(@string)) END
    RETURN (@retVal)
END
GO
GRANT EXECUTE ON [dbo].[FN_StripLeading] TO PUBLIC
2
user2600313

文字列が数値の場合、cast(value as int)は常に機能します

2
tichra

Snowflake SQLを使用している場合、これを使用できます。

ltrim(str_col,'0')

Ltrim関数は、指定された文字セットのすべてのインスタンスを左側から削除します。

したがって、 '00000008A'のltrim(str_col、 '0')は '8A'を返します。

また、「$ 125.00」のrtrim(str_col、 '0。')は、「$ 125」を返します。

1
JJFord3

これの私のバージョンは、Arvoの作業を修正したもので、他の2つのケースを確実にするためにもう少し追加されています。

1)すべて0の場合、数字の0を返す必要があります。

2)空白がある場合でも、空白文字を返す必要があります。

CASE 
    WHEN PATINDEX('%[^0]%', str_col + '.') > LEN(str_col) THEN RIGHT(str_col, 1) 
    ELSE SUBSTRING(str_col, PATINDEX('%[^0]%', str_col + '.'), LEN(str_col))
 END
1
Brisbe
replace(ltrim(replace(Fieldname.TableName, '0', '')), '', '0')

Thomas Gからの提案は、私たちのニーズに合っていました。

この例のフィールドはすでに文字列であり、先頭のゼロのみを削除する必要がありました。ほとんどはすべて数値ですが、場合によっては文字があるため、前のINT変換がクラッシュします。

1
Randy
SELECT CAST(CAST('000000000' AS INTEGER) AS VARCHAR)

これには、INTに変換できる文字列の長さに制限があります

0
Curt Ehrhart

Intに変換したくない場合は、以下のロジックをお勧めします。IFNULL(field、LTRIM(field、 '0'))のNULLを処理できるためです。

0
shockwave