メモ

シャーディング振り分け
シャーディングされたデータベースでの効率的なデータ取得には、各データベースシャードに対して適切なクエリを分配し、SQLの発行回数を最小限に抑えることが重要です。`user_id`をもとにシャーディングされている場合、各`user_id`がどのシャードに属するかを計算し、それに応じて分類してクエリを実行します。
 
シャーディングされたDBへのアクセスを最適化するために、まずは`follow_user_id`をシャーディングのルールに基づいて適切なグループに分ける必要があります。このプロセスは、前述した`GetUserProfiles`関数内で、`follow_user_id`を取得した後に行います。
 
以下にそのプロセスを明示的に示す実装例を提供します。この例では、シャーディングのルールとして`user_id % 5`を使用し、これによって得られる値を基に各`user_id`を適切なデータベースシャードに分類します。
 
```go
func (s *server) GetUserProfiles(ctx context.Context, req *pb.UserProfilesRequest) (*pb.UserProfilesResponse, error) {
// 仮のデータベースDSN(接続情報)のリスト
dsns := string{
"user:password@tcp(db0.example.com)/dbname?charset=utf8&parseTime=True&loc=Local",
"user:password@tcp(db1.example.com)/dbname?charset=utf8&parseTime=True&loc=Local",
"user:password@tcp(db2.example.com)/dbname?charset=utf8&parseTime=True&loc=Local",
"user:password@tcp(db3.example.com)/dbname?charset=utf8&parseTime=True&loc=Local",
"user:password@tcp(db4.example.com)/dbname?charset=utf8&parseTime=True&loc=Local",
}
 
// g_likesからfollow_user_idを取得
// ここではすべてのfollow_user_idを取得していますが、実際には条件に基づいて絞り込む必要があります
var gLikes GLike
db, err := gorm.Open(mysql.Open(dsns[0]), &gorm.Config{}) // この例では単純化のために1つのDBから取得しています
if err != nil {
return nil, err
}
db.Find(&gLikes)
 
// follow_user_idのリストを作成
var followUserIDs int
for _, gLike := range gLikes {
followUserIDs = append(followUserIDs, gLike.FollowUserID)
}
 
// シャーディングされたDBごとにクエリを実行するための準備
shardedUserIDs := make(map[int]int)
for _, id := range followUserIDs {
shard := id % 5 // シャーディングのルールに基づいてシャードを決定
shardedUserIDs[shard] = append(shardedUserIDs[shard], id)
}
 
// シャーディングされたDBごとにクエリを実行
userProfiles := make(*pb.UserProfile, 0)
for shard, ids := range shardedUserIDs {
shardDb, err := gorm.Open(mysql.Open(dsns[shard]), &gorm.Config{})
if err != nil {
continue // 接続失敗時は次のDBへ
}
 
var users User
shardDb.Where("user_id IN ?", ids).Find(&users)
 
for _, user := range users {
userProfiles = append(userProfiles, &pb.UserProfile{
UserId:   int32(user.UserID),
UserName: user.UserName,
})
}
}
```
 
`shardedUserIDs`は、シャーディングされたデータベースに対してクエリを実行する前に、`follow_user_id`をシャーディングのルール(この場合は`user_id % 5`)に基づいて分類した結果を格納するマップです。このマップは、各キーがシャーディングされたデータベースのインデックス(またはシャード番号)を表し、各値がそのシャードにクエリを発行する必要がある`user_id`のリストを含みます。
 
 
<b>shardedUserIDsの例</b>
以下に、`shardedUserIDs`の具体的な例を示します。
 
仮定:
- シャーディングのルールは`user_id % 5`です。
- `follow_user_id`には[1, 2, 3, 10, 12, 15, 18, 20, 23, 25]が含まれているとします。
 
この時、`shardedUserIDs`の構造は次のようになります:
 
```go
shardedUserIDs := map[int]int{
    0: int{5, 10, 15, 20, 25},  // user_id % 5 == 0
    1: int{1, 6, 11, 16, 21},   // user_id % 5 == 1
    2: int{2, 7, 12, 17, 22},   // user_id % 5 == 2
    3: int{3, 8, 13, 18, 23},   // user_id % 5 == 3
    4: int{4, 9, 14, 19, 24},   // user_id % 5 == 4
}
```
 
このマップを使って、各シャードに対して適切な`user_id`のリストを含むクエリを発行できます。例えば、シャード0に対しては`user_id`が[5, 10, 15, 20, 25]のユーザを検索するクエリを実行します。
 
この方法により、シャーディングされたデータベース環境内でのデータ取得処理を効率化でき、各シャードに対するクエリの数を最適化し、全体のパフォーマンスを向上させることが可能になります。
 
<b>usersの例</b>
`shardDb.Where("user_id IN ?", ids).Find(&users)`のコード行は、特定のシャードデータベースに対して、指定された`user_id`のリストにマッチする全てのユーザレコードを検索します。このクエリは、GORMの`Where`メソッドを使用して`IN` SQLクエリを構築し、結果を`users`スライスに格納します。
 
このコード行が実行された後の`users`スライスの内容は、クエリにマッチしたユーザレコードの集合です。各ユーザレコードは`User`型のインスタンスで、この例では少なくとも`UserID`と`UserName`フィールドを含んでいます。
 
以下に具体的な結果の例を示します。この例では、あるシャードデータベースが`user_id`が[1, 6, 11]のユーザを保持していると仮定します。
 
仮定:
- シャードデータベースには、以下のユーザレコードが存在します:
  - `user_id = 1`のユーザは`UserName`が"Alice"
  - `user_id = 6`のユーザは`UserName`が"Bob"
  - `user_id = 11`のユーザは`UserName`が"Charlie"
 
クエリ実行後の`users`スライスの内容は以下のようになります:
 
```go
users := User{
    {UserID: 1, UserName: "Alice"},
    {UserID: 6, UserName: "Bob"},
    {UserID: 11, UserName: "Charlie"},
}
```
この結果は、指定された`user_id`のリストにマッチするユーザ情報を含む`User`型のスライスです。このスライスは後にレスポンスの構築に使用され、クライアントに返されます。
このように、GORMを使用すると、複雑なSQLクエリを簡単に構築し、結果を効率的に操作できるようになります。
 
時間経過記載
gRPCを使用する場合でも、時間差を計算して結果を振り分けるロジックは基本的に変わりません。gRPCのコンテキストでは、サーバー側でデータベースからデータを取得し、それをクライアントに送信する際に、このロジックを適用して結果をクライアントに送り返すことになります。
 
以下の例は、gRPCサーバー側で時間差を計算し、それに基づいて結果をクライアントに返す処理を実装する方法を示しています。この例では、gRPCのメッセージ定義やセットアップに関する部分は省略しており、主にロジック部分に焦点を当てています。
 
まずは、プロトコルバッファ定義ファイル(`.proto`)で、必要なメッセージとサービスを定義します。例えば、ユーザー情報とそのログイン時間からの経過時間に関する情報を返すサービスがあるとします。
 
```proto
syntax = "proto3";
 
package example;
 
// ログイン時間からの経過時間情報を含むユーザー情報
message UserInfo {
  int32 id = 1;
  string user_id = 2;
  string user_name = 3;
  string elapsed_time = 4; // 経過時間の文字列
}
 
// ユーザー情報を取得するためのサービス定義
service UserLoginService {
  rpc GetUserLoginInfo (Request) returns (UserInfo);
}
 
message Request {
  int32 user_id = 1;
}
```
 
次に、gRPCサーバー側での実装を考えます。データベースからユーザー情報を取得し、時間差に基づいて経過時間の文字列を生成する部分は以下のようになります。
 
```go
package main
 
import (
"fmt"
"log"
"net"
"time"
// gRPCとデータベース関連のパッケージをインポート
)
 
// 実装するgRPCサービス
type server struct {
// gRPCサービスを実装するための構造体
}
 
// GetUserLoginInfoはユーザーのログイン情報を取得するメソッド
func (s *server) GetUserLoginInfo(ctx context.Context, req *example.Request) (*example.UserInfo, error) {
var loginAt time.Time
// ここでデータベースからユーザー情報とlogin_atを取得する処理を実装
// 例: loginAt = データベースから取得したログイン時間
 
// 現在時刻との差を計算
now := time.Now()
diff := now.Sub(loginAt)
 
// 時間差に基づいて結果を振り分ける
var result string
if diff.Hours() < 1 {
result = "1時間以内"
} else if diff.Hours() < 24 {
result = fmt.Sprintf("1時間以上 %d時間前", int(diff.Hours()))
} else {
r        days := int(diff.Hours() / 24)
        if days >= 100 {
            result = "100日"
        } else {
            result = fmt.Sprintf("%d日以上前", days)
        }
    }
}
 
// UserInfoのインスタンスを作成して結果を返す
return &example.UserInfo{
Id:          req.UserId,
// User_idとUser_nameはデータベースから取得する必要があります
User_id:     "取得したユーザーID",
User_name:   "取得したユーザー名",
Elapsed_time: result,
}, nil
}
 
func main() {
// gRPCサーバーのセットアップと起動のコード
}
```

 

テストケース

もちろんです。表現を明確にして、読み手が理解しやすいように「ブロックされている(相手にブロックされているが、フォローはしていない場合)」の説明を変更します。

### 表現を変更したテストケースマトリックス

| ユーザーの状態                                     | アクションの説明                                           | リストの種類             | 期待される結果                                                 |
|--------------------------------------------------|--------------------------------------------------------|--------------------|-----------------------------------------------------------|
| 自分が他のアクティブなユーザーをフォローしている場合                 | 自分がアクションを起こして相手をフォローしている                                | フォローリスト             | 相手ユーザーが自分のフォローリストに表示される                     |
| 他のアクティブなユーザーが自分をフォローしている場合                 | 相手がアクションを起こして自分をフォローしている                                | フォロワーリスト           | 相手ユーザーが自分のフォロワーリストに表示される                   |
| 相互フォロー状態(アクティブなユーザー同士)                | 自分と相手が互いにフォローしている                                              | フォローリスト/フォロワーリスト | 相手ユーザーが自分のフォローリストとフォロワーリストの両方に表示される           |
| 自分が相手をブロックしている場合                               | 自分がアクションを起こして相手をブロックしている                                | ブロックリスト             | 相手ユーザーが自分のブロックリストに表示される                     |
| 自分と相手が互いにブロックしている場合                         | 自分と相手が互いにブロックのアクションを起こしている                            | ブロックリスト             | 相手ユーザーが自分のブロックリストに表示される                     |
| 自分が相手をブロックしているが、相手からフォローされている場合     | 相手が自分をフォローしているが、自分はその相手をブロックしている                | ブロックリスト/フォロワーリスト | 相手ユーザーが自分のブロックリストに表示され、フォロワーリストには表示されない   |
| 自分がフォローしているが、相手からブロックされている場合             | 相手がアクションを起こして自分をブロックしているが、自分はフォローし続けている     | フォローリスト             | 相手ユーザーはフォローリストに表示されない(アプリによる)               |
| **相手からブロックされているが、自分はフォローしていない場合** | 相手によってブロックされているが、自分からのフォローはない                      | フォローリスト/フォロワーリスト | 相手ユーザーはどちらのリストにも表示されない                         |
| 入会拒否されたユーザー                                   | 相手がサービスの利用を拒否され、アカウントが機能していない                        | 任意のリスト             | 相手ユーザーはどのリストにも表示されない                             |

この変更で、「相手からブロックされているが、自分はフォローしていない場合」の表現を、「相手に

***メモ1***

 
## データinsert
GoとGORMを使用して、具体的なデータベースファイル名を指定してデータを挿入する方法を以下に示します。この例では、データベースファイル名として`test_shard1.db`を使用します。
 
```go
package main
 
import (
"fmt"
"gorm.io/driver/sqlite" // SQLiteドライバをインポート
"gorm.io/gorm"
)
 
// Follower テーブルを表す構造体
type Follower struct {
Id         int `gorm:"primaryKey"`
UserId     int
FollowerId int
BanFlg     int
}
 
func main() {
// 指定したファイル名のSQLiteデータベースに接続
db, err := gorm.Open(sqlite.Open("test_shard1.db"), &gorm.Config{})
if err != nil {
panic("データベースへの接続に失敗しました")
}
 
// `followers` テーブルを自動で作成
err = db.AutoMigrate(&Follower{})
if err != nil {
panic("テーブルのマイグレーションに失敗しました")
}
 
// テーブル内の既存データを削除
err = db.Where("1 = 1").Delete(&Follower{}).Error
if err != nil {
panic("データの削除に失敗しました")
}
 
// 新しいデータを挿入
followers := Follower{
{UserId: 21, FollowerId: 5, BanFlg: 0},
{UserId: 5, FollowerId: 21, BanFlg: 1},
{UserId: 21, FollowerId: 30, BanFlg: 0},
{UserId: 21, FollowerId: 35, BanFlg: 0},
{UserId: 10, FollowerId: 21, BanFlg: 0},
{UserId: 15, FollowerId: 21, BanFlg: 1},
{UserId: 10, FollowerId: 15, BanFlg: 0}, // 新しいデータ
{UserId: 11, FollowerId: 14, BanFlg: 0}, // 新しいデータ
{UserId: 21, FollowerId: 11, BanFlg: 0}, // 新しいデータ
{UserId: 11, FollowerId: 21, BanFlg: 0}, // 新しいデータ
}
 
for _, follower := range followers {
result := db.Create(&follower) // レコードを挿入
if result.Error != nil {
fmt.Println("データ挿入時にエラーが発生しました:", result.Error)
return
}
}
 
fmt.Println("データの挿入が完了しました")
}
 
```
 
このコードは、`test_shard1.db`という名前のSQLiteデータベースファイルに対して動作します。もしファイルが存在しなければ、GORMは新たにファイルを作成し、`Follower`構造体に基づいて`followers`テーブルをマイグレーションします。その後、指定したデータを`followers`テーブルに挿入します。
 
このプログラムを実行する前に、GORMとSQLiteのドライバがプロジェクトにインストールされていることを確認してください。GORMとSQLiteドライバをインストールするには、以下のコマンドを実行します(プロジェクトのルートディレクトリで実行):
 
```sh
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite
```
 
データベースファイル`test_shard1.db`に対する操作を行った後は、`sqlite3 test_shard1.db`コマンドを使って、SQLiteの対話型シェルからデータベースの内容を確認することができます。
 
## xxxx
`createLists` 関数を使って、取得したフォロー関係のデータからフォローリストとフォロワーリストを生成し、それらを出力するコードの例を以下に示します。この関数では、ログインユーザー(この例では`user_id=21`)がフォローしているユーザーのリスト(フォローリスト)と、ログインユーザーをフォローしているユーザーのリスト(フォロワーリスト)を作成します。
 
### `createLists`関数の実装
 
この関数は、フォロー関係のリストを引数として受け取り、ログインユーザーに関連するフォローリストとフォロワーリストを生成して出力します。
 
```go
package main
 
import (
"fmt"
 
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
 
type Follower struct {
Id         int `gorm:"primaryKey"`
UserId     int
FollowerId int
BanFlg     int
}
 
func main() {
db, err := gorm.Open(sqlite.Open("test_shard1.db"), &gorm.Config{})
if err != nil {
panic("データベースへの接続に失敗しました")
}
 
var followers Follower
// ログインユーザーに関連するフォロー関係を取得
if err := db.Where("user_id = ? OR follower_id = ?", 21, 21).Find(&followers).Error; err != nil {
panic("フォロー関係の取得に失敗しました")
}
 
// フォローリストとフォロワーリストを生成して出力
createLists(followers)
}
 
func createLists(followers Follower) {
const loginUser = 21
 
following := make(map[int]bool)
followedBy := make(map[int]bool)
bannedUsers := make(map[int]bool)
 
for _, f := range followers {
// ログインユーザーがフォローしているユーザー
if f.UserId == loginUser {
following[f.FollowerId] = true
// ban_flg=1であれば記録
if f.BanFlg == 1 {
bannedUsers[f.FollowerId] = true
}
}
// ログインユーザーをフォローしているユーザー
if f.FollowerId == loginUser {
followedBy[f.UserId] = true
// ban_flg=1であれば記録
if f.BanFlg == 1 {
bannedUsers[f.UserId] = true
}
}
}
 
// フォローリストを生成し、ban_flg=1であるユーザーを除外
finalFollowing := int{}
for userId := range following {
if !bannedUsers[userId] { // ban_flg=1であるユーザーを除外
finalFollowing = append(finalFollowing, userId)
}
}
 
// フォロワーリストを生成し、ban_flg=1であるユーザーを除外
finalFollowedBy := int{}
for userId := range followedBy {
if !bannedUsers[userId] { // ban_flg=1であるユーザーを除外
finalFollowedBy = append(finalFollowedBy, userId)
}
}
 
fmt.Println("フォローリスト:", finalFollowing)
fmt.Println("フォロワーリスト:", finalFollowedBy)
}
 
```
 
このコードでは、`ban_flg`が`0`のユーザーだけをフォローリストとフォロワーリストに含めています。つまり、`ban_flg`が`1`であるユーザーはこれらのリストから除外されます。これにより、`ban_flg`の値に応じてリストの絞り込みが可能となります。
 
関数`createLists`は、ログインユーザーがフォローしているユーザーとフォローされているユーザーの両方のリストを生成し、それらをコンソールに出力します。この方法で、特定のユーザーのフォローリストとフォロワーリストを簡単に取得し、確認することができます。
 
 
### xxx
`ban_flg=1`であるユーザー`15`がフォロワーリストに含まれている問題を解決するために、フォロワーリスト生成時のロジックを再確認し、修正する必要があります。ユーザー`15`がフォロワーリストから適切に除外されるようにするため、`ban_flg=1`のユーザーを正しく識別し、除外するロジックを強化します。
 
以下の修正版では、フォロワーリストから`ban_flg=1`のユーザーを正確に除外します。特に、`ban_flg=1`でフォロワーとなっているユーザーは、`bannedUsers`マップを使用して追跡し、最終的なフォロワーリストからこれらのユーザーを除外することに注意してください。
 
### 修正版: フォロワーリストから`ban_flg=1`のユーザーを除外
 
```go
func createLists(followers Follower) {
    const loginUser = 21
 
    following := make(map[int]bool)
    followedBy := make(map[int]bool)
    bannedUsers := make(map[int]bool)
 
    for _, f := range followers {
        // ログインユーザーがフォローしているユーザー
        if f.UserId == loginUser {
            following[f.FollowerId] = true
            // ログインユーザーがban_flg=1でフォローしているユーザーを記録
            if f.BanFlg == 1 {
                bannedUsers[f.FollowerId] = true
            }
        }
        // ログインユーザーをフォローしているユーザー
        if f.FollowerId == loginUser {
            followedBy[f.UserId] = true
            // フォロワーがban_flg=1の場合も記録
            if f.BanFlg == 1 {
                bannedUsers[f.UserId] = true
            }
        }
    }
 
    // 相互フォローとban_flg=1のユーザーを除外したフォローリストを生成
    finalFollowing := int{}
    for userId := range following {
        if !bannedUsers[userId] {
            finalFollowing = append(finalFollowing, userId)
        }
    }
 
    // フォロワーリストからban_flg=1のユーザーを除外して生成
    finalFollowedBy := int{}
    for userId := range followedBy {
        if !bannedUsers[userId] {
            finalFollowedBy = append(finalFollowedBy, userId)
        }
    }
 
    fmt.Println("フォローリスト:", finalFollowing)
    fmt.Println("フォロワーリスト:", finalFollowedBy)
}
```
 
この修正により、フォローリストとフォロワーリストの生成時に、`ban_flg=1`であるユーザーを適切に除外することができます。`bannedUsers`マップを活用して、フォローしているが`ban_flg=1`のユーザー、及びフォロワーが`ban_flg=1`のユーザーを追跡し、これらのユーザーが最終的なリストに含まれないようにしています。これにより、ユーザー`15`のように`ban_flg=1`であるにもかかわらずリストに含まれてしまう問題を解決することができます。
 
### xxxx

***メモ2***

 
## insert
```go
package main
 
import (
"fmt"
"time"
 
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
 
type BanRecord struct {
ID      uint `gorm:"primaryKey"`
UserID  int
BanType int
BanDate time.Time
}
 
func main() {
db, err := gorm.Open(sqlite.Open("ban_records.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
 
var banRecords BanRecord
now := time.Now()
 
// 現在時刻よりも前のban_dateを持つレコードを取得
err = db.Table("ban_records").
Where("ban_date < ?", now).
Order("ban_date DESC").
Find(&banRecords).Error
 
if err != nil {
fmt.Println("Query failed:", err)
return
}
 
// 結果からBanTypeが2のレコードを除外して、各user_idについて最も近いban_dateを持つレコードを選択
closestBans := make(map[int]BanRecord)
for _, record := range banRecords {
// BanTypeが2のレコードはスキップ
if record.BanType == 2 {
continue
}
 
// 同じUserIDのレコードがまだマップにない、またはより古いban_dateを持つ場合にのみ、マップに追加/更新
if existingRecord, exists := closestBans[record.UserID]; !exists || existingRecord.BanDate.Before(record.BanDate) {
closestBans[record.UserID] = record
}
}
 
// 最終的な結果の表示
for _, ban := range closestBans {
fmt.Printf("ID: %d, UserID: %d, BanType: %d, BanDate: %s\n", ban.ID, ban.UserID, ban.BanType, ban.BanDate)
}
}
 
```
 
## 結果
理解しました。まず、現在時刻よりも前の`ban_date`を持つレコードを取得し、その後で`BanType`が2のレコードを除外するように処理を修正します。このステップでは、取得したレコードのリストをループ処理し、`BanType`が2以外のレコードのみを次のフィルタリングステップに進めるようにします。
 
以下のコードは、その処理を実装したものです。
 
```go
package main
 
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"time"
)
 
type BanRecord struct {
ID      uint `gorm:"primaryKey"`
UserID  int
BanType int
BanDate time.Time
}
 
func main() {
db, err := gorm.Open(sqlite.Open("ban_records.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
 
var banRecords BanRecord
now := time.Now()
 
// 現在時刻よりも前のban_dateを持つレコードを取得
err = db.Table("ban_records").
Where("ban_date < ?", now).
Order("ban_date DESC").
Find(&banRecords).Error
 
if err != nil {
fmt.Println("Query failed:", err)
return
}
 
// 結果からBanTypeが2のレコードを除外して、各user_idについて最も近いban_dateを持つレコードを選択
closestBans := make(map[int]BanRecord)
for _, record := range banRecords {
// BanTypeが2のレコードはスキップ
if record.BanType == 2 {
continue
}
 
// 同じUserIDのレコードがまだマップにない、またはより古いban_dateを持つ場合にのみ、マップに追加/更新
if existingRecord, exists := closestBans[record.UserID]; !exists || existingRecord.BanDate.Before(record.BanDate) {
closestBans[record.UserID] = record
}
}
 
// 最終的な結果の表示
for _, ban := range closestBans {
fmt.Printf("ID: %d, UserID: %d, BanType: %d, BanDate: %s\n", ban.ID, ban.UserID, ban.BanType, ban.BanDate)
}
}
```
 
この修正により、まず現在時刻よりも前の`ban_date`を持つ全てのレコードを取得し、その後で`BanType`が2であるレコードを除外します。そして、残ったレコードから各`user_id`ごとに最も近い`ban_date`を持つレコードを選択します。
 
このアプローチでは、最初に条件を満たす全てのレコードを取得し、その中から特定の条件(この場合は`BanType`が2でない)に基づいてフィルタリングを行い、最終的に必要なレコードのみを選択するという流れで処理を行います。
 
## user_id削除
```go
package main
 
import (
"fmt"
"time"
 
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
 
type BanRecord struct {
ID      uint
UserID  int
BanType int
BanDate time.Time
}
 
func main() {
db, err := gorm.Open(sqlite.Open("ban_records.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
 
var banRecords BanRecord
now := time.Now()
err = db.Table("ban_records").
Where("ban_date < ?", now).
Order("ban_date DESC").
Find(&banRecords).Error
 
if err != nil {
fmt.Println("Query failed:", err)
return
}
 
// closestBansの生成
closestBans := make(map[int]BanRecord)
for _, record := range banRecords {
// BanTypeが2のレコードはスキップ
if record.BanType == 2 {
continue
}
 
// 同じUserIDのレコードがまだマップにない、またはより古いban_dateを持つ場合にのみ、マップに追加/更新
if existingRecord, exists := closestBans[record.UserID]; !exists || existingRecord.BanDate.Before(record.BanDate) {
closestBans[record.UserID] = record
}
}
 
// 必要なUserIDのリスト
requiredUserIDs := uint32{3, 4, 5, 8, 10}
 
// closestBansに含まれるUserIDをフィルタリングして除外
remainingUserIDs := uint32{}
for _, id := range requiredUserIDs {
if _, found := closestBans[int(id)]; !found {
remainingUserIDs = append(remainingUserIDs, id)
}
}
 
// 結果の表示
fmt.Println("残ったUserID:", remainingUserIDs)
}
 
```
 
## user_id削除2
```go
package main
 
import (
"fmt"
"time"
 
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
 
type BanRecord struct {
ID      uint
UserID  int
BanType int
BanDate time.Time
}
 
func main() {
db, err := gorm.Open(sqlite.Open("ban_records.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
 
var banRecords BanRecord
now := time.Now()
err = db.Table("ban_records").
Where("ban_date < ?", now).
Order("ban_date DESC").
Find(&banRecords).Error
 
if err != nil {
fmt.Println("Query failed:", err)
return
}
 
// closestBansの生成
tempClosestBans := make(map[int]BanRecord)
for _, record := range banRecords {
// BanTypeが2のレコードはスキップ
if record.BanType == 2 {
continue
}
 
// 同じUserIDのレコードがまだマップにない、またはより古いban_dateを持つ場合にのみ、マップに追加/更新
if existingRecord, exists := tempClosestBans[record.UserID]; !exists || existingRecord.BanDate.Before(record.BanDate) {
tempClosestBans[record.UserID] = record
}
}
closestBans := make(map[int]bool)
for userID := range tempClosestBans {
closestBans[userID] = true
}
fmt.Printf("%+v\n", closestBans)
 
// 必要なUserIDのリスト
requiredUserIDs := uint32{3, 4, 5, 8, 10}
 
// closestBansに含まれるUserIDをフィルタリングして除外
remainingUserIDs := []uint32{}
for _, id := range requiredUserIDs {
if _, found := closestBans[int(id)]; !found {
remainingUserIDs = append(remainingUserIDs, id)
}
}
 
// 結果の表示
fmt.Println("残ったUserID:", remainingUserIDs)
}
 
```