0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PowerQueryでSQLのLIKE演算子を作ってみた

Posted at

こちらの投稿でLIKE演算子相当のものをPowerQueryで作りましたが、"%"や"_"の対応ができていなかったので、改めて作ってみました。

学び

  • @をつけると再帰呼び出しで関数が呼び出せるということ。

LikeText関数

MatchRecursive関数がLIKE演算子の本体。

LikeText
(input as any, pattern as any, escapeChar as nullable text) as logical =>
let 
  safeInput = ToText(input, ""),
  safePattern = ToText(pattern, ""),
  actualEscapeChar = if escapeChar = null then "\" else escapeChar,
  result = MatchRecursive(Text.ToList(safeInput), TokenizePattern(safePattern, actualEscapeChar), actualEscapeChar)
in 
  result

MatchRecursive関数

設計の考え方として、

  • iList(入力値)とpList(パターン)を1文字ずつ比較して同じならtrue、違うならfalseを返す。文字列の長さが異なる場合もfalse。
  • "_"はiListの先頭と一致したとみなして、iListの残りの文字を比較する。
  • "%"はiListの全要素と、pListの残りで、どれか1つでも一致すればtrue、違うならfalse。

という考え方です。

特筆すべきところは、@MatchRecursiveのところ。 @をつけると再帰呼び出しで関数が呼び出せるということ。

MatchRecursive
(iList as list, pList as list, escapeChar as text) as logical =>
if List.IsEmpty(pList) then 
  List.IsEmpty(iList)
else 
  let 
    p = List.First(pList),
    restP = List.Skip(pList, 1)
  in 
    // ゼロ文字以上のワイルドカード
    if p = "%" then 
      List.AnyTrue( 
        List.Transform( 
          {0..List.Count(iList)}, each @MatchRecursive(List.Skip(iList, _), restP, escapeChar)
        )    
      )
    // 1文字以上のワイルドカード
    else if p = "_" then 
      if not(List.IsEmpty(iList)) then 
         @MatchRecursive(List.Skip(iList, 1), restP, escapeChar)
      else 
         false 
    // エスケープ文字対応
    else if Text.StartsWith(p, escapeChar) then 
      let 
        literal = Text.End(p, Text.Length(p) -1)
      in 
        if not(List.IsEmpty(iList)) and List.First(iList)=literal then 
          @MatchRecursive(restP, List.Skip(iList, 1), escapeChar)
        else 
          false
    // 通常の文字
    else 
      if not(List.IsEmpty(iList)) and p = List.First(iList) then 
         @MatchRecursive(List.Skip(iList, 1), restP, escapeChar)
      else 
         false

ToText関数

ToText
(expr as any, fallback as nullable text) as text =>
let 
  result = try Text.From(if expr = null or expr = "" then fallback else expr) otherwise null 
in 
  result

TokenizePattern関数

エスケープ文字列処理用のラッパー関数。本体は_Tokenize関数。

TokenizePattern
(pattern as text, escapeChar as nullable text) as list =>
let
    // 指定が無かったらデフォルト値として"\"を使う
    actualEscapeChar = if escapeChar = null then "\" else escapeChar,
    // 文字列を文字のリストに変換
    chars = Text.ToList(pattern)
in 
    _Tokenize(chars, {}, actualEscapeChar)

_Tokenize関数

_Tokenize
(lst as list, acc as list, escapeChar as text) as list =>
    if List.IsEmpty(lst) then acc
    else 
        let 
            head = List.First(lst),
            rest = List.Skip(lst, 1)
        in 
            // エスケープ文字の次の文字を&で結合して1つのトークンにする
            if head = escapeChar and not List.IsEmpty(rest) then 
                let 
                    nextHead = List.First(rest),
                    restRest = List.Skip(rest, 1)
                in 
                    @_Tokenize(restRest, acc & {escapeChar & nextHead}, escapeChar)
            // エスケープ文字で終わってしまったら、不完全なエスケープなのでそれまでの累積文字列を返す
            else if head = escapeChar and List.IsEmpty(rest) then 
                acc 
            else 
            // それ以外の文字は連続文字として&でつなげる
                @_Tokenize(rest, acc & {head}, escapeChar)
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?