[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"academy-blogs-th-1-1-all-golang-ep159-hybrid-search-sql-vector-qdrant-all--*":3,"academy-blog-translations-ftwq5mc0lm5y4sp":94},{"data":4,"page":80,"perPage":80,"totalItems":80,"totalPages":80},[5],{"alt":6,"collectionId":7,"collectionName":8,"content":9,"cover_image":10,"cover_image_path":11,"created":12,"created_by":13,"expand":14,"id":88,"keywords":89,"locale":60,"published_at":90,"scheduled_at":76,"school_blog":84,"short_description":91,"status":82,"title":92,"updated":93,"updated_by":13,"slug":85,"views":87},"ภาพปกบทความ 'Golang The Series EP.159: Hybrid Search ผสมผสาน SQL และ Vector Search ใน Go' บนพื้นหลังกราฟิกโครงข่ายข้อมูล AI สีน้ำเงิน","sclblg987654321","school_blog_translations","\u003Cp>สวัสดีครับชาว Gophers! ต้อนรับเข้าสู่ EP.159\u003C\u002Fp>\u003Cp>ต่อจากตอนที่แล้วที่เราลุยเรื่อง Context Injection จนระบบ RAG ของเราสามารถดึงคำตอบจากเอกสารได้แบบเนียนๆ กันไปแล้ว แต่พอเอาไปขึ้นระบบใช้งานจริงบน Production ผมเชื่อว่าหลายคนน่าจะเคยเจอ \"Pain point\" เดียวกันครับ\u003C\u002Fp>\u003Cp>ปัญหาคือข้อมูลในองค์กรส่วนใหญ่มันไม่ได้มีแค่ไฟล์เอกสารดิบๆ (Unstructured Data) ที่เราจับมาแปลงเป็นเวกเตอร์เพียงอย่างเดียว แต่มันยังมีข้อมูลแบบมีโครงสร้าง (Structured Data) อีกมหาศาลที่นอนอยู่ใน Relational Database แบบดั้งเดิมอย่าง PostgreSQL หรือ MySQL ไม่ว่าจะเป็น สังกัดแผนกของพนักงาน, สิทธิ์การเข้าถึง (Permission Level), วันที่อัปเดตระบบ หรือสถานะของเอกสาร (Active\u002FDraft)\u003C\u002Fp>\u003Cp>ลองนึกภาพ Requirement จากฝั่ง Business ดูครับ: \u003Cem>\"อยากให้พนักงานค้นหาคู่มือด้วย AI ได้นะ แต่ล็อกเงื่อนไขว่าต้องดึงมาเฉพาะเอกสารที่เป็นเวอร์ชันล่าสุด และผู้ถามต้องมีสิทธิ์ (Role = Admin) เท่านั้น\"\u003C\u002Fem>\u003C\u002Fp>\u003Cp>ถ้าเราพึ่งพา Vector DB โดดๆ โดยไม่สนเงื่อนไขอื่น โอกาสที่ข้อมูลความลับจะรั่วไหลข้ามแผนกก็มีสูงมาก นี่แหละครับคือจุดที่ \u003Cstrong>Hybrid Search\u003C\u002Fstrong> หรือเทคนิคการผสานโลกของ SQL เข้ากับ Vector Search ต้องออกโรงในแอปพลิเคชัน Go ของเรา!\u003C\u002Fp>\u003Ch2>แนวคิดการออกแบบ Hybrid Search ในโปรเจกต์ Go\u003C\u002Fh2>\u003Cp>เวลาที่เราต้องการกรองข้อมูลข้าม 2 สถาปัตยกรรมนี้ โดยทั่วไปเราสามารถวางโครงสร้างได้ 2 ท่าหลักๆ ครับ:\u003C\u002Fp>\u003Col>\u003Cli>\u003Cp>\u003Cstrong>Two-Stage Query (ท่าคลาสสิก: SQL ก่อน แล้วค่อย Vector)\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>ท่านี้คือการใช้ Go Backend วิ่งไปคิวรีที่ SQL Database ก่อน เพื่อดึงลิสต์ของ \u003Ccode>Document ID\u003C\u002Fcode> ที่ผ่านเงื่อนไข (เช่น สิทธิ์ถูกต้อง, สถานะผ่าน) ออกมากองไว้ จากนั้นค่อยนำลิสต์ ID เหล่านั้นส่งไปเป็น Filter ให้ Vector DB ค้นหาต่ออีกทอดหนึ่ง ท่านี้แยกการทำงานชัดเจน แต่ถ้าลิสต์ ID มีขนาดใหญ่มาก อาจทำให้เกิดความหน่วง (Latency) ได้\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>Integrated Filtering หรือ Metadata Pre-filtering (ท่าแนะนำ: จบในดาบเดียว)\u003C\u002Fstrong>\u003Cbr>ท่านี้คือการย้ายข้อมูลเงื่อนไขที่จำเป็น (Metadata) ไปบันทึกลงในส่วน Payload ของ Vector Database (เช่น Qdrant) ควบคู่ไปตั้งแต่ตอนสกัดข้อมูลเลย จากนั้นใช้ฟีเจอร์ Payload Indexing ของ Qdrant สั่งค้นหาเวกเตอร์พร้อมกับแนบเงื่อนไขไปในคิวรีเดียว\u003C\u002Fp>\u003C\u002Fli>\u003C\u002Fol>\u003Cblockquote>\u003Cp>\u003Cem>ซึ่ง Go SDK ของ Qdrant มีฟังก์ชันรองรับเรื่องนี้ได้มีประสิทธิภาพสูงมากครับ ช่วยลด Network Latency ลงไปได้เยอะสุดๆ\u003C\u002Fem>\u003C\u002Fp>\u003C\u002Fblockquote>\u003Ch2>ตัวอย่างการเขียน Go ทำ Hybrid Search (Vector Search + Metadata Filter)\u003C\u002Fh2>\u003Cp>แทนที่เราจะเขียนโค้ดทุกอย่างรวมกันใน \u003Ccode>main()\u003C\u002Fcode> เรามาแบ่งโครงสร้างการทำงานออกเป็น 2 ส่วนหลักๆ กันครับ เพื่อให้โค้ดคลีนและนำไปต่อยอดได้ง่ายขึ้น:\u003C\u002Fp>\u003Col>\u003Cli>\u003Cp>\u003Cstrong>ส่วนแปลงข้อความ (Embedding)\u003C\u002Fstrong>\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>ส่วนค้นหาและกรองข้อมูล (Hybrid Search)\u003C\u002Fstrong>\u003C\u002Fp>\u003C\u002Fli>\u003C\u002Fol>\u003Ch3>1. ฟังก์ชันสำหรับแปลงคำถามเป็น Vector (Embedding)\u003C\u002Fh3>\u003Cp>เราจะแยกหน้าที่การคุยกับ OpenAI API ออกมาเป็นฟังก์ชันต่างหาก เพื่อรับข้อความ (Text) และคืนค่ากลับมาเป็นพิกัดเวกเตอร์ (\u003Ccode>[]float32\u003C\u002Fcode>)\u003C\u002Fp>\u003Cp>Go\u003C\u002Fp>\u003Cpre>\u003Ccode>\u002F\u002F generateEmbedding รับคำถามและแปลงเป็นพิกัดเวกเตอร์ผ่าน OpenAI\nfunc generateEmbedding(ctx context.Context, client *openai.Client, text string) ([]float32, error) {\n\tembReq := openai.EmbeddingRequest{\n\t\tInput: []string{text},\n\t\tModel: openai.SmallEmbedding3Small, \u002F\u002F ใช้พิกัด 1,536 มิติ มาตรฐาน\n\t}\n\tresp, err := client.CreateEmbeddings(ctx, embReq)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"embedding failed: %w\", err)\n\t}\n\treturn resp.Data[0].Embedding, nil\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch3>2. ฟังก์ชันสำหรับยิงคิวรี Hybrid Search ไปที่ Qdrant\u003C\u002Fh3>\u003Cp>ฟังก์ชันนี้จะรับพิกัดเวกเตอร์จากข้อ 1 พร้อมกับ \"เงื่อนไข\" (Metadata) ที่เราต้องการกรอง เช่น แผนกและสถานะเอกสาร เพื่อประกอบร่างเป็นคำสั่งค้นหา\u003C\u002Fp>\u003Cp>Go\u003C\u002Fp>\u003Cpre>\u003Ccode>\u002F\u002F searchDocuments ค้นหาเอกสารด้วยความหมาย พร้อมกรอง Metadata (Department, Status)\nfunc searchDocuments(ctx context.Context, client *qdrant.Client, queryVector []float32, dept, status string) ([]*qdrant.ScoredPoint, error) {\n\tsearchLimit := uint64(3)\n\t\n\t\u002F\u002F สร้างสเปกตัวกรองโดยการ Match คีย์เวิร์ดใน Payload\n\tsearchFilters := &amp;qdrant.Filter{\n\t\tMust: []*qdrant.Condition{\n\t\t\tqdrant.NewMatchKeyword(\"department\", dept),\n\t\t\tqdrant.NewMatchKeyword(\"status\", status),\n\t\t},\n\t}\n\n\t\u002F\u002F ยิงคิวรีค้นหาทั้งพิกัดความหมายและกรองเงื่อนไข Metadata ในลูปเดียว\n\treturn client.Query(ctx, &amp;qdrant.QueryPoints{\n\t\tCollectionName: \"ai_knowledge_base\",\n\t\tQuery:          qdrant.NewQuery(queryVector...),\n\t\tFilter:         searchFilters,\n\t\tLimit:          &amp;searchLimit,\n\t})\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch3>3. ประกอบร่างในฟังก์ชัน main()\u003C\u002Fh3>\u003Cp>พอเราแยกฟังก์ชันออกมาแล้ว โค้ดใน \u003Ccode>main()\u003C\u002Fcode> ของเราจะอ่านง่ายขึ้นเหมือนอ่านหนังสือเลยครับ เป็นการจำลองลำดับการทำงาน (Workflow) ได้อย่างชัดเจน\u003C\u002Fp>\u003Cp>Go\u003C\u002Fp>\u003Cpre>\u003Ccode>package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com\u002Fqdrant\u002Fgo-client\u002Fqdrant\"\n\t\"github.com\u002Fsashabaranov\u002Fgo-openai\"\n)\n\nfunc main() {\n\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\tdefer cancel()\n\n\t\u002F\u002F --- [Setup Clients] ---\n\tapiKey := os.Getenv(\"OPENAI_API_KEY\")\n\tif apiKey == \"\" {\n\t\tlog.Fatal(\"Please set your OPENAI_API_KEY environment variable\")\n\t}\n\topenaiClient := openai.NewClient(apiKey)\n\n\tqdrantClient, err := qdrant.NewClient(&amp;qdrant.Config{\n\t\tHost: \"localhost\",\n\t\tPort: 6334,\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"Qdrant connection failed: %v\", err)\n\t}\n\tdefer qdrantClient.Close()\n\n\t\u002F\u002F --- [Define Query &amp; Filters] ---\n\tuserQuery := \"ระเบียบการเบิกค่ารักษาพยาบาลมีอะไรบ้าง\"\n\t\n\t\u002F\u002F จำลองค่าที่ดึงมาจาก SQL Database ของระบบหลัก\n\ttargetDepartment := \"Human Resources\"\n\tdocumentStatus := \"active\"\n\n\t\u002F\u002F --- [Step 1: Embedding] ---\n\tqueryVector, err := generateEmbedding(ctx, openaiClient, userQuery)\n\tif err != nil {\n\t\tlog.Fatalf(\"Error generating embedding: %v\", err)\n\t}\n\n\t\u002F\u002F --- [Step 2: Hybrid Search] ---\n\tsearchResp, err := searchDocuments(ctx, qdrantClient, queryVector, targetDepartment, documentStatus)\n\tif err != nil {\n\t\tlog.Fatalf(\"Hybrid Search query failed: %v\", err)\n\t}\n\n\t\u002F\u002F --- [Step 3: Display Results] ---\n\tfmt.Printf(\"🔍 ผลลัพธ์ Hybrid Search สำหรับคำถาม: '%s'\\n\", userQuery)\n\tfmt.Printf(\"📋 [เงื่อนไขระบบ] แผนก: %s | สถานะ: %s\\n\\n\", targetDepartment, documentStatus)\n\t\n\tfor i, point := range searchResp {\n\t\tpayloadMap := point.Payload\n\t\t\n\t\t\u002F\u002F ป้องกัน Panic โดยตรวจสอบคีย์ก่อนดึงค่า\n\t\tvar content, dept, status string\n\t\tif val, ok := payloadMap[\"content\"]; ok { content = val.GetStringValue() }\n\t\tif val, ok := payloadMap[\"department\"]; ok { dept = val.GetStringValue() }\n\t\tif val, ok := payloadMap[\"status\"]; ok { status = val.GetStringValue() }\n\t\t\n\t\tfmt.Printf(\"[%d] ความแม่นยำ (Score): %.4f | แผนก: %s | สถานะ: %s\\n\", i+1, point.Score, dept, status)\n\t\tfmt.Printf(\"   เนื้อหาอ้างอิง: %s\\n\\n\", content)\n\t}\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch2>ทำไม Go ถึงตอบโจทย์สถาปัตยกรรม Hybrid Search?\u003C\u002Fh2>\u003Cp>การที่ Backend ต้องวิ่งไปคุยกับ Database ถึงสองที่ (ทั้ง SQL และ Vector) แน่นอนว่าต้องมีเรื่องของความหน่วง (Latency) ตามมาหลอกหลอนแน่ๆ ถ้าเราจัดการกระบวนการทำงานได้ไม่ดีพอ แต่โชคดีที่เราเขียน Go ครับ! เพราะ Go มีของดีที่ช่วยให้เรื่องนี้รับมือได้ง่ายขึ้นมาก:\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cp>\u003Cstrong>Goroutines สำหรับ Concurrent Querying:\u003C\u002Fstrong> สมมติว่า Requirement บังคับให้คุณต้องใช้ท่า Two-Stage Query (ดึงข้อมูลแยกคลังกัน) เราก็แค่จับมันรันคู่ขนาน (Parallel) ไปพร้อมกันซะเลยด้วย \u003Ccode>Goroutines\u003C\u002Fcode> และคุมจังหวะด้วย \u003Ccode>sync.WaitGroup\u003C\u002Fcode> พอได้ผลลัพธ์จากทั้งสองฝั่งค่อยเอามาทำ Data Intersection กันใน Go ท่านี้ช่วยหั่นเวลาการทำงานลงไปได้เยอะมากเมื่อเทียบกับการต้องมารอ Query ทีละตัว (Sequential)\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>ความ Type-Safe ของ Go Struct:\u003C\u002Fstrong> เวลาที่เราได้ข้อมูลกลับมาจากฝั่ง Vector Payload (ที่มักจะเป็น JSON หรือ Map) รวมกับข้อมูลแบบ Record จาก SQL Driver เราสามารถจับมันมา Unmarshal เข้าสู่ \u003Ccode>Go Struct\u003C\u002Fcode> ที่มีการระบุ Type ไว้อย่างชัดเจนตั้งแต่อยู่ใน Memory ทำให้เราจัดระเบียบ Data Transformation ได้อย่างมั่นใจ ช่วยตัดปัญหา Runtime Error ยิบย่อยที่มักจะเจอกับภาษาแบบ Dynamic Typed ไปได้อย่างเด็ดขาดครับ\u003C\u002Fp>\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>🎯 ท้าให้ลอง (Daily Mission)\u003C\u002Fh2>\u003Cp>ลองนำโครงสร้างการคิวรีแบบพ่วงฟิลเตอร์ในบทความนี้ ไปต่อยอดกับโปรเจกต์ของคุณดูครับ โดยโจทย์คือ \u003Cstrong>\"การดึงเฉพาะเอกสารที่มีอายุไม่เกินระยะเวลาที่กำหนด\"\u003C\u002Fstrong> (ใช้ตัวกรองประเภทช่วงข้อมูล หรือ Range Filter)\u003C\u002Fp>\u003Cp>\u003Cstrong>การบ้านชวนคิด:\u003C\u002Fstrong> ลองแก้ตัวแปร \u003Ccode>searchFilters\u003C\u002Fcode> ให้รองรับการเปรียบเทียบข้อมูลตัวเลขหรือ Unix Timestamp (เช่น หาเอกสารที่ฟิลด์ \u003Ccode>created_at\u003C\u002Fcode> มีค่ามากกว่าเมื่อ 30 วันที่แล้ว) \u003Cem>Hint:\u003C\u002Fem> ลองไปส่อง Document ของ Qdrant Go Client ดูครับว่าการใช้ \u003Ccode>qdrant.NewRange(...)\u003C\u002Fcode> สำหรับเทียบค่าตัวเลขนั้นต้องเขียนส่งค่าพารามิเตอร์แบบไหน แล้วลองรันคิวรีดูผลลัพธ์ใน Terminal กันได้เลย!\u003C\u002Fp>\u003Ch2>💡 FAQ (คำถามที่พบบ่อย)\u003C\u002Fh2>\u003Ch3>การทำ Hybrid Search จำเป็นต้องใช้ Qdrant เสมอไปหรือไม่?\u003C\u002Fh3>\u003Cp>ไม่จำเป็นครับ Vector Database ชั้นนำตัวอื่นๆ เช่น Milvus, Pinecone, Weaviate หรือแม้แต่ \u003Ccode>pgvector\u003C\u002Fcode> ใน PostgreSQL ก็รองรับการทำ Metadata Filtering เช่นกัน แต่ในซีรีส์นี้เราเลือก Qdrant เพราะมี Go SDK ที่ใช้งานง่าย ประสิทธิภาพสูง และรองรับ Payload Indexing ได้ดีเยี่ยมครับ\u003C\u002Fp>\u003Ch3>ถ้าข้อมูล Metadata (เช่น สิทธิ์การเข้าถึง) มีการเปลี่ยนแปลงบ่อยมาก ควรใช้ท่าไหนดี?\u003C\u002Fh3>\u003Cp>หากสิทธิ์การเข้าถึงเปลี่ยนบ่อยระดับวินาที (Highly Dynamic) การใช้ท่า \u003Cstrong>Two-Stage Query\u003C\u002Fstrong> (เช็คสิทธิ์ที่ SQL ก่อน แล้วค่อยเอา ID ไปหา Vector) อาจจะตอบโจทย์เรื่อง Data Consistency มากกว่าครับ แต่ถ้าไม่ได้เปลี่ยนบ่อยมาก ท่า \u003Cstrong>Metadata Pre-filtering\u003C\u002Fstrong> ที่เราเขียนในบทความนี้จะให้ Performance ด้านความเร็วที่ดีกว่ามาก (เพียงแค่ต้องเขียนระบบอัปเดต Payload ใน Qdrant ให้ซิงค์กับ SQL เสมอ)\u003C\u002Fp>\u003Ch3>การทำ Payload Indexing กรองข้อมูล ทำให้เปลืองทรัพยากร (Resource) ระบบมากขึ้นไหม?\u003C\u002Fh3>\u003Cp>การสร้าง Index จะใช้พื้นที่ RAM และ Storage เพิ่มขึ้นเล็กน้อยครับ แต่สิ่งที่ได้กลับมาคือความเร็วในการค้นหา (Query Speed) แบบก้าวกระโดด ซึ่งสำหรับการรันบน Production การยอมเสียพื้นที่จัดเก็บเล็กน้อยเพื่อแลกกับ Latency ที่ต่ำลง ถือเป็นการลงทุนที่คุ้มค่ามากครับ\u003C\u002Fp>\u003Cdiv data-type=\"horizontalRule\">\u003Chr>\u003C\u002Fdiv>\u003Ch2>สรุป\u003C\u002Fh2>\u003Cp>ในบทความนี้เราได้เรียนรู้ \u003Cstrong>วิธีผสานการทำงานระหว่าง Relational Database (SQL) และ Vector Database เข้าด้วยกันผ่านเทคนิค Hybrid Search\u003C\u002Fstrong> ซึ่งเป็นหัวใจสำคัญในการทำระบบ AI ค้นหาข้อมูลองค์กรให้มีความปลอดภัย ถูกต้องตามสิทธิ์ (Permission) และช่วยดึงเฉพาะข้อมูลที่สถานะพร้อมใช้งานจริงๆ โดยใช้ท่า Metadata Pre-filtering ที่จบครบในคิวรีเดียวผ่าน Go SDK ได้อย่างมีประสิทธิภาพครับ\u003C\u002Fp>\u003Cp>\u003Cstrong>ในตอนต่อไป (EP.160): \u003C\u002Fstrong>ยินดีด้วยครับ! ตอนนี้โครงสร้างภาคทฤษฎีและองค์ประกอบระดับ Advanced ในการพัฒนา AI Integration ของพวกเราพร้อมใช้งานแบบ 100% แล้ว ตอนหน้าจะเป็นการปิดซีซันอย่างยิ่งใหญ่ด้วยการลงมือทำ \u003Cstrong>\"Workshop 2: ระบบถาม-ตอบจากเอกสารบริษัทแบบ Internal Tool\"\u003C\u002Fstrong> เราจะนำทั้ง Gin Web Framework, PDF Extraction, RAG Pipeline และ Hybrid Search มารวมพลังสร้างแอปพลิเคชันสำหรับใช้งานในองค์กรจริงๆ ห้ามพลาดเด็ดขาดครับ Gophers!\u003C\u002Fp>\u003Cp>\u003Cstrong>ฝากกดติดตามพวกเราได้ที่ Superdev Academy\u003C\u002Fstrong> ในทุกช่องทางนะครับ!\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cp>\u003Cstrong>🔵 Facebook: \u003C\u002Fstrong>\u003Ca target=\"_blank\" rel=\"noopener\" class=\"ng-star-inserted\" href=\"https:\u002F\u002Fwww.facebook.com\u002Fsuperdev.academy.th\">\u003Cstrong>Superdev Academy Thailand\u003C\u002Fstrong>\u003C\u002Fa>\u003Cstrong> \u003C\u002Fstrong>(อัปเดตข่าวสารและบทความใหม่)\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>🎬 YouTube: \u003C\u002Fstrong>\u003Ca target=\"_blank\" rel=\"noopener\" class=\"ng-star-inserted\" href=\"https:\u002F\u002Fwww.youtube.com\u002F@SuperdevAcademy\">\u003Cstrong>Superdev Academy Channel\u003C\u002Fstrong>\u003C\u002Fa>\u003Cstrong> \u003C\u002Fstrong>(ติวเข้มแบบวิดีโอ)\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>📸 Instagram: \u003C\u002Fstrong>\u003Ca target=\"_blank\" rel=\"noopener\" class=\"ng-star-inserted\" href=\"https:\u002F\u002Fwww.instagram.com\u002Fsuperdevacademy\u002F\">\u003Cstrong>@superdevacademy\u003C\u002Fstrong>\u003C\u002Fa>\u003Cstrong> \u003C\u002Fstrong>(เกร็ดความรู้สั้นๆ และเบื้องหลังการทำงาน)\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>🎬 TikTok: \u003C\u002Fstrong>\u003Ca target=\"_blank\" rel=\"noopener\" class=\"ng-star-inserted\" href=\"https:\u002F\u002Fwww.tiktok.com\u002F@superdevacademy?lang=th-TH\">\u003Cstrong>@superdevacademy\u003C\u002Fstrong>\u003C\u002Fa>\u003Cstrong> \u003C\u002Fstrong>(Tips &amp; Tricks ฉบับย่อยง่าย)\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>🌐 Website: \u003C\u002Fstrong>\u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"http:\u002F\u002Fsuperdevacademy.com\">\u003Cstrong>superdevacademy.com\u003C\u002Fstrong>\u003C\u002Fa>\u003Cstrong> \u003C\u002Fstrong>(คลังบทความและคอร์สเรียนฉบับเต็ม)\u003C\u002Fp>\u003C\u002Fli>\u003C\u002Ful>\u003Cp>\u003C\u002Fp>","37uy7or0h71l_b3q86nqy3y.png","https:\u002F\u002Ftwsme-r2.tumwebsme.com\u002Fsclblg987654321\u002Fg4ldhm8lhjzgywr\u002F37uy7or0h71l_b3q86nqy3y.png","2026-07-13 01:22:49.056Z","76qprkevbgfdps8",{"keywords":15,"locale":54,"school_blog":64},[16,23,28,32,36,41,45,50],{"collectionId":17,"collectionName":18,"created":19,"created_by":13,"id":20,"name":21,"updated":22,"updated_by":13},"sclkey987654321","school_keywords","2026-03-04 08:20:14.253Z","ah6lvy4x8qe08l5","Golang","2026-06-07 06:45:08.193Z",{"collectionId":17,"collectionName":18,"created":24,"created_by":13,"id":25,"name":26,"updated":27,"updated_by":13},"2026-03-04 08:20:11.547Z","ey3puyme01a9bsw","Go","2026-06-07 06:45:07.798Z",{"collectionId":17,"collectionName":18,"created":29,"created_by":13,"id":30,"name":31,"updated":29,"updated_by":13},"2026-07-13 01:22:24.796Z","jakeeeq3nrsn8q6","Hybrid Search",{"collectionId":17,"collectionName":18,"created":33,"created_by":13,"id":34,"name":35,"updated":33,"updated_by":13},"2026-07-13 01:22:28.308Z","p1xcuxg9elg8xnt","Vector Search",{"collectionId":17,"collectionName":18,"created":37,"created_by":13,"id":38,"name":39,"updated":40,"updated_by":13},"2026-03-04 08:49:14.997Z","o07u79nqhhz3zbn","SQL","2026-06-07 06:48:09.540Z",{"collectionId":17,"collectionName":18,"created":42,"created_by":13,"id":43,"name":44,"updated":42,"updated_by":13},"2026-06-30 03:40:49.549Z","lz4vxehenzo8oum","Qdrant",{"collectionId":17,"collectionName":18,"created":46,"created_by":13,"id":47,"name":48,"updated":49,"updated_by":13},"2026-03-04 08:31:29.142Z","hrqdq7kjl5lzjmi","AI","2026-06-07 06:45:27.147Z",{"collectionId":17,"collectionName":18,"created":51,"created_by":13,"id":52,"name":53,"updated":51,"updated_by":13},"2026-07-13 01:22:42.953Z","hcbz61drlwxlj06","สอน Golang",{"code":55,"collectionId":56,"collectionName":57,"created":58,"flag":59,"id":60,"is_default":61,"label":62,"updated":63},"th","pbc_1989393366","locales","2026-01-22 10:59:55.832Z","twemoji:flag-thailand","s8wri3bt4vgg2ji",true,"Thai","2026-04-10 15:42:46.614Z",{"category":65,"collectionId":66,"collectionName":67,"created":68,"expand":69,"id":84,"slug":85,"updated":86,"views":87},"wqxt7ag2gn7xcmk","pbc_2105096300","school_blogs","2026-07-13 01:22:48.450Z",{"category":70},{"blogIds":71,"collectionId":72,"collectionName":73,"created":74,"created_by":13,"id":65,"image":75,"image_alt":76,"image_path":77,"label":78,"name":79,"priority":80,"publish_at":81,"scheduled_at":76,"status":82,"updated":83,"updated_by":13},[],"sclcatblg987654321","school_category_blogs","2026-03-04 08:33:53.210Z","59ty92ns80w_15oc1implw.png","","https:\u002F\u002Ftwsme-r2.tumwebsme.com\u002Fsclcatblg987654321\u002Fwqxt7ag2gn7xcmk\u002F59ty92ns80w_15oc1implw.png",{"en":79,"th":79},"Golang The Series",1,"2026-03-16 04:39:38.440Z","published","2026-06-07 06:45:03.856Z","ftwq5mc0lm5y4sp","golang-ep159-hybrid-search-sql-vector-qdrant","2026-07-13 05:58:38.243Z",135,"g4ldhm8lhjzgywr",[20,25,30,34,38,43,47,52],"2026-07-13 03:49:00.799Z","ค้นหาด้วย AI อย่างปลอดภัยและตรงเงื่อนไข! เจาะลึกเทคนิค Hybrid Search บนภาษา Go ที่ผสานขุมพลัง Relational SQL เข้ากับ Vector Database (Qdrant) พร้อมสอนทำ Metadata Pre-filtering","Golang The Series EP.159: Hybrid Search ผสมผสาน SQL และ Vector Search ใน Go","2026-07-13 03:49:00.800Z",{"th":85,"en":85}]