きり丸の技術日記

技術検証したり、資格等をここに残していきます。

FILTER関数で足りない行を見つける(Google スプレッドシート)

始めに

小ネタ。

更新系処理にて1000レコードあるemailが含まれているCSVをアップロードしたときに、DB側には999レコードしかない時には1レコード足りないために処理が失敗するとします。その場合どのレコードがDBに足りないかを調査する必要があります。

今回の記事では、その足りない1レコードを調査するためにGoogle スプレッドシートのFILTER関数を使えば一発で差分を検索できる方法を知ったのでブログにします。

環境

  • Google スプレッドシート
    • 20240721 時点

実装

比較したいレコードをA列、B列に記載している状態で、D3セル、E3セルに次の数式を入力します。そうすると、D4セル以降、E4セル以降に差分が表示されます。

# D3
=FILTER(A:A, NOT(COUNTIF(B:B, A:A)))
# E3
=FILTER(B:B, NOT(COUNTIF(A:A, B:B)))

今回の例では、A列から見てB列は2@example.com, 5@example.comが不足しており、B列から見てA列は3@example.comが不足していることが分かります。

ソースコード

なし

終わりに

もっと簡単に比較する方法があれば教えていただきたいです。

DB側で一時テーブルを作成する方法もあるかもしれませんが、結局のところ大量のデータ投入にはCSVを使用することが多く、CSVを扱えるツール側で差分を見る方法の方が楽でした。これが、1万レコード、1億レコードとなると簡単に比較できないとは思っていますが、もしそうなっても簡単に比較できる方法があれば教えていただけると助かります。

【詳細不明】NoInspectionAvailable を回避する

始めに

※ 自宅で検証した際には実装できなかったので詳細は不明です。


テスト実装中に次のエラーが発生しました。

E sqlalchemy.exc.NoInspectionAvailable: No inspection system is available for object of type <class 'models.User'>

発生原因が不明ですが、発生しないように対応できたのでその件を記事にします。

環境

  • Python
    • 3.12.3
  • SQLAlchemy
    • 2.0.27
      • 発生したバージョン
    • 2.0.31
      • 再現できず

対応

インスタンスをcopyして対応しました。または、commitでも対応はできそうです。

もともとやりたかったことはDBの値をHTTPリクエストで更新できるかどうかをチェックすることでしたが、項目数が多かったのでDBにinsertしたインスタンスを元に加工していました。その加工方法が誤っており、エラーが発生した模様です。

import copy

def test_01(session: AsyncSession)
  user = User(name="test")
  session.add(user)
  await session.flush()

  # NOTE: この処理が非常に重要だった
  # DBを元に入力値で更新するテストのために加工
  copied_user = copy.copy(user.__dict__)
  _ = copied_user.pop("_sa_instance_state")
  copied_user['name'] = "testUpd"
  # copied_userをJSON化したりして、なんやかんやで入力用に加工

  # DBからuserを取得しようとしてエラー
  query: select = select(User).where(User.name == "test")
  actual = (await db.execute(query)).scalars().first()

原因

自宅で再現できなかったので想像です。


次のエラーはmodels.Userを操作したときにSQLAlchemyのメタデータが正しく取得できず、インスタンスとDBを紐づける操作ができなかった時に発生します。

E sqlalchemy.exc.NoInspectionAvailable: No inspection system is available for object of type <class 'models.User'>

今回、DBに登録したインスタンスを元に入力値を加工しようとしたので、HTTPリクエスト時のJSONBodyに不要だと判断したSQLAlchemyのMetadataの_sq_instance_stateを削除しています。

copied_user = user.__dict__
_ = copied_user.pop("_sa_instance_state")

SQLAlchemyを使用して同一トランザクション内で処理する場合、処理結果をキャッシュに保持していて、処理結果に紐づいたインスタンスを返却します。今回の例でいうと、useractualが別名ですが同一インスタンスを指しています。

user = User(name="test")
session.add(user)

...

query: select = select(User).where(User.name == "test")
actual = (await db.execute(query)).scalars().first()

そして、__dict__を使用したらインスタンスをDeepCopyできていると勘違いしたので、このcopied_userも同じインスタンスです。

copied_user = user.__dict__
_ = copied_user.pop("_sa_instance_state")

そのため、次のタイミングでインスタンスにマッピングしようとしたところ、本来マッピングできるはずのuserインスタンスが存在せずにメタデータのエラーが発生したと思われます。

query: select = select(User).where(User.name == "test")
actual = (await db.execute(query)).scalars().first()

だからこそ、copyでインスタンスをDeepCopyしたことにより、今回の事象を回避できたと推測してます。

copied_user = copy.copy(user.__dict__)

ソースコード

再現できなかったのでなし。

終わりに

起こった事象自体はかなりニッチな内容で、一般的に役に立たない記事かもしれませんが、NoInspectionAvailableで調べた際に私の記事が参考になって解決できると幸いです。

参考情報

類似情報

Ngrokで無料プランでドメインを固定できるようになっていた(2023/08/16 以降できたらしい)

2023年8月16日からできるようになっていたのを、単純に知らなかったので共有するだけのブログです。


ローカルで外部からAPIを実行する検証をしたい時があります。たとえばWebhookの検証だったり、Outlook連携の検証だったり。

そのときにローカルのAPIをHTTPSで外部公開できて有用なのがNgrokでした。ただ、昔はNgrokを無料プランで使用する場合は公開されるAPIが常にランダムになり、Ngrokを検証するたびにいろんな箇所を修正する必要がありました。

ngrok http 3000

https://7a55-221-117-101-49.ngrok-free.app

それが、2023年8月16日の更新により、ドメインを固定できるようになりました。上のURLを次のようなURLで固定できます。

ngrok http --domain=helpful-crack-imp.ngrok-free.app 3000

https://helpful-crack-imp.ngrok-free.app

設定するには、サイドメニューのCloud EdgeDomainsからCreate Domainをワンボタンでクリックするだけで、ランダムなドメインが払い出されるので、それを使用するだけです。

ランダムといっても、Ngrokが保有するドメインのサブドメインになります。また、記載しているとおり、自分が所有するドメインへの固定はできません。もし固定したい場合は有料プランで契約する必要があります。

参考情報