Adobe Campaignクエリー道場(12)〜ランダムにクーポン

今回はさっそく、お客様事例[^1]から:

::: note
ランダムに選択した受信者に対して、別途発行済のクーポンコードをユニークに割り当てたい。
:::
![クーポン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516149/b97d45fc-549f-245c-7c89-36a0a9a7e29f.png)
たとえば受信者から10名、無条件に抽出します。その抽出した受信者に対し、以下のクーポンコードを割り当てます:

|クーポンコード|
|:-:|
|AkwElyie87|
|pLeoqlWc3|
|xkR1ET4mTS|
|L9kYyn1DN9|
|U84uuQXxxB|
|SyCZXzJZ9|
|QdAe2cSGUZ|
|B68WWFh6gf|
|JLxp3EDPn|
|Bje4tkMWpJ|

誰にどのコードを割り当ててもいいのですが、かならず１対１、つまり受信者１名に対し１クーポンコードとしなければなりません。
さあ、どう取り組みましょうか。
***
じつはCampaignは製品として[クーポン機能](https://experienceleague.adobe.com/docs/campaign-classic/using/sending-messages/personalizing-deliveries/personalized-coupons.html?lang=ja "パーソナライズされたクーポン")を持ち、高度なクーポン管理が可能ですが、追加パッケージが必要です。今回は単純にクーポンコードさえ割り当てられればいいので、基本機能だけでやってみましょう。
まず、ワークフローの全体像です:
![2024-02-20_13-01-40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516149/8f619d25-bbd9-8415-902c-9743d8066e99.png)
以下、主要な点に絞って説明していきます。
## データ読み込み (ファイル)
![2024-02-20_13-01-40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516149/fdad491c-129c-1055-3415-68752469f1bf.png)
まず、割り当てるべきクーポンコードを取り込みます。クーポンコードは冒頭の内容そのままローカルファイルで持っているとしましょう。ただし、ファイルの１行めは見出しとして「クーポンコード」と記述、そしてファイルの文字コードはBOMつきUTF-8であることが前提です。このとき、「データ読み込み (ファイル)」アクティビティを作成し、以下画像のように設定します:
![2024-02-20_14-31-26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516149/cfe384eb-a8c1-031c-3d49-d231b634832b.png)
ここでのポイントは、「ファイルのフォーマット」の「ライン番号を識別子として使用」。チェックが入っていることを確認してください。プレビューが上掲画像のように正しくなっていれば[^2]、「サーバーにアップロード」をクリックしてファイルを読み込ませます。
## クエリー
![2024-02-20_13-01-40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516149/8a5c271c-5b25-3154-4166-31d1b521a1df.png)
クエリーのフィルター条件は要件に応じて適宜設定してください──たとえば「キャンペーン期間中の購入者」とか「メルマガ登録者」とかですね[^3]。
「クエリ」タブの「データを追加...」から「追加データ」画面へいつもの調子で遷移し、「出力列」ペインの「追加」ボタンをクリック。「式」に以下をコピペしてください:
```
RowNum(, OrderBy(Random()))
```
![2024-02-21_9-34-10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516149/21311c4b-a367-4161-8426-5d8ad2f1d2bb.png)
RowNum()は前３回[^4]しつこく取り上げている、連番を振る関数ですね。OrderBy()もそれとセットで、連番を振るときの順番を指定する関数でした。ではその引数のRandom()は？ やはりCampaignの組み込み関数で、乱数を返すものです。これをOrderBy()の引数として与えると、ランダムにソートせよ、という意味になります。ということは、この式全体では、受信者をランダムに並べ替えて連番を振ることになるのです[^5]。
上記を「式」に入力したら、「エイリアス」に「lineNum」と指定します[^6]:
![2024-02-21_9-41-11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516149/7b163959-ae8a-6e01-c7cb-77f2e5a1008c.png)
エイリアスはアレンジせずこのとおり「lineNum」としてくださいね。あとで必要になります。
クエリーの設定は以上です。
## 積集合
![2024-02-21_10-04-17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516149/dddfedeb-e433-1caf-75b2-64b3c06515a6.png)
「積集合」アクティビティで、ファイルで読み込んだクーポンコードと受信者とを紐付けます。
「紐付け」は「列の選択」、「プライマリセット」は「クエリ」アクティビティを選択。「結合に使用する列」でペイン右「追加」ボタンクリックで現れた入力行に「lineNum」と入力します:
![2024-02-21_11-05-11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516149/9b5abbcd-4a67-1ea6-a57b-1dd85ef6eec6.png)
「lineNum」はクエリーで指定したエイリアスでしたね。しかしそれだけではないのです。「データ読み込み (ファイル)」　で「ライン番号を識別子として使用」を指定しました。このとき、キーとしてクーポンコード各行に「ライン番号」が振られます。そのカラムも、内部名が「lineNum」なのです。クーポンコードと受信者のクエリー結果とが共に「lineNum」という連番カラムを持つことになり、それを用いて両者を紐づけることができるというわけですね。
このアクティビティも以上で設定完了です。
***
ではワークフローを実行してみましょう。
「データ読み込み (ファイル)」は、クーポンコードを読み込んでライン数を振ります:
![2024-02-27_17-00-29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516149/ff7fe934-2848-0508-11f6-518ee168a9f7.png)
クエリーは受信者をランダムに並べ替えてライン数を振ります:
![2024-02-28_16-01-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516149/3587a126-c4e7-f8dc-141d-0944cb731f18.png)
そして積集合。[前々回](https://qiita.com/JJSlider/items/1e85787d7244d0d1f9f5 "Adobe Campaignクエリー道場(10)〜同着を考慮した順位")・[前回](https://qiita.com/JJSlider/items/6a5a0ade4dd1f671b4d8 "Adobe Campaignクエリー道場(11)〜行間比較で間隔を取得")で紹介済みのとおり集合と集合との共通部分ですが、クーポンコードは10行で1から10までのライン数が振られているので、受信者からも共通するライン数1から1０までを取り出して結合します:
![2024-02-28 8.58.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516149/ab58ff4a-1814-61a4-1e6b-fda883fdf0e5.png)
というわけで、要件であった「ランダムに選択した受信者に対して、別途発行済のクーポンコードをユニークに割り当て」が実現しました。
後続アクティビティで配信のコンテンツ内にクーポンコードを差し込むには、ツールバーのボタンをクリックし「ターゲット式」から選択します[^7]:
![2024-02-28_16-49-51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516149/8d96cb08-acd7-fabd-3bd7-83f989f289ec.png)
***
今回はクーポンコードを割り当てるという要件にRowNum()で対応してみました[^8]。RowNum()　なんて正直、使うイメージ湧かない──とか思っていませんでしたか。しかしここ４回[^4]の記事を通じて、その応用範囲の広さを垣間見てもらえたかと思います。実戦に活かしていただけたらうれしいです。

:::note warn
本稿の内容は筆者のオンプレミス型デモ環境(Adobe Campaign Classic 9359@c636bf3 PostgreSQL 14.9)上で実施した検証に基づきます。別環境における同様の動作を保証するものではありません。またデータは架空のものであり、既存の配信や実在の組織とはいっさい関係がありません。
:::
[^1]: [以前](https://qiita.com/JJSlider/items/c85ed2abf4e836daaa7a "Adobe Campaignクエリー道場(6)〜CASE式で推し活?!")にも触れましたが、アドビ カスタマーサポートがお客様ご要件の実装をサポートケースから提供することはありません。サポートケースで個別ご要件のご相談をいただいても対応できかねますのでご承知おきください。
[^2]: 見出しの「クーポンコード」が文字化けしていたら、ファイルにUTF-8のBOMがありません。ファイルにBOMを付してやり直してください。
[^3]: ここでは簡便のため、受信者に重複はないものと仮定します。
[^4]: 以下を復習してください:
 [Adobe Campaignクエリー道場(9)〜RowNum()関数でザ・ベストテン！](https://qiita.com/JJSlider/items/4def108dfba3a522a9d7 "Adobe Campaignクエリー道場(9)〜RowNum()関数でザ・ベストテン！")
 [Adobe Campaignクエリー道場(10)〜同着を考慮した順位](https://qiita.com/JJSlider/items/1e85787d7244d0d1f9f5 "Adobe Campaignクエリー道場(10)〜同着を考慮した順位")
 [Adobe Campaignクエリー道場(11)〜行間比較で間隔を取得](https://qiita.com/JJSlider/items/6a5a0ade4dd1f671b4d8 "Adobe Campaignクエリー道場(11)〜行間比較で間隔を取得")
[^5]: ここでは関数[RowId()](https://experienceleague.adobe.com/docs/campaign-standard/using/managing-processes-and-data/filtering-data/list-of-functions.html?lang=ja#others "関数のリスト")を使うこともできます。RowId()はRowNum()と同様に連番を振る関数ですが、PartitionBy()やOrderBy()で範囲や順序を指定することができません。自由度がない割にバックエンドのSQLは同じなのでパフォーマンスも変わりませんが、シンプルで見やすいという利点はあります。「[分割](https://experienceleague.adobe.com/docs/campaign-classic/using/automating-with-workflows/targeting-activities/split.html?lang=ja "分割")」アクティビティを前に置いてすでに母集団をランダムにしている場合には、RowId()を代わりに使うといいでしょう。
[^6]: 「ラベル」「式」がRowNum()に対応して「ライン数」と表示されることも併せて確認してください。
[^7]: パーソナライゼーションのスクリプトを直接書き込むことも可能です。本稿の場合「<%= targetData.col1 %>」と記述できます。ヘルプ記事「[パーソナライゼーションフィールドの挿入](https://experienceleague.adobe.com/docs/campaign-classic/using/sending-messages/personalizing-deliveries/personalization-fields.html?lang=ja#inserting-a-personalization-field "パーソナライゼーションフィールドの挿入")」もご覧ください。
[^8]: 実は今回のお題、海外顧客からもよく聞かれるものなんですが、このように解いているのは世界でも本稿だけかもしれません。通常は[注5](#fn-5)で触れたように、「分割」アクティビティでランダム抽出してからRowId()で連番を振ることが多いと思います。「クエリー道場」ということで、クエリーに比重を寄せた解法にしてみました。ちなみに「分割」アクティビティもバックエンドで発行するSQLは本稿のクエリーとほとんど変わりありません。