다음을 통해 공유


빠른 시작: Azure SDK를 사용하여 전체 텍스트 검색

이 빠른 시작에서는 Azure.Search.Documents 클라이언트 라이브러리를 사용하여 전체 텍스트 검색에 대한 샘플 데이터를 사용하여 검색 인덱스 만들기, 로드 및 쿼리를 수행합니다. 전체 텍스트 검색은 인덱싱 및 쿼리에 Apache Lucene을 사용하고 BM25 순위 지정 알고리즘을 사용하여 결과를 채점합니다.

이 빠른 시작에서는 호텔 4개에 대한 데이터가 포함된 작은 호텔 빠른 시작 인덱스를 만들고 쿼리합니다.

필수 조건

Microsoft Entra ID 필수 구성 요소

Microsoft Entra ID를 사용하는 권장 키 없는 인증의 경우 다음을 수행해야 합니다.

  • Microsoft Entra ID를 사용하여 키 없는 인증에 사용되는 Azure CLI 를 설치합니다.
  • 사용자 계정에 역할과 Search Service Contributor 역할을 모두 Search Index Data Contributor 할당합니다. Azure Portal의 액세스 제어(IAM)>역할을 할당할 수 있습니다. 자세한 내용은 역할을 사용하여 Azure AI Search에 연결을 참조 하세요.

리소스 정보 검색

Azure AI Search 서비스를 사용하여 애플리케이션을 인증하려면 다음 정보를 검색해야 합니다.

변수 이름
SEARCH_API_ENDPOINT 이 값은 Azure Portal에서 찾을 수 있습니다. 검색 서비스를 선택한 다음 왼쪽 메뉴에서 개요를 선택합니다. EssentialsURL 값은 필요한 엔드포인트입니다. 엔드포인트의 예는 다음과 같습니다. https://mydemo.search.windows.net

키 없는 인증 및 환경 변수 설정에 대해 자세히 알아봅니다.

설정

  1. 애플리케이션을 포함할 새 폴더 full-text-quickstart 를 만들고 다음 명령을 사용하여 해당 폴더에서 Visual Studio Code를 엽니다.

    mkdir full-text-quickstart && cd full-text-quickstart
    
  2. 다음 명령을 사용하여 새 콘솔 애플리케이션을 만듭니다.

    dotnet new console
    
  3. 다음을 사용하여 .NET용 Azure AI Search 클라이언트 라이브러리(Azure.Search.Documents)를 설치합니다.

    dotnet add package Azure.Search.Documents
    
  4. Microsoft Entra ID로 권장되는 키 없는 인증의 경우 다음을 사용하여 Azure.Identity 패키지를 설치합니다.

    dotnet add package Azure.Identity
    
  5. Microsoft Entra ID로 권장되는 키 없는 인증의 경우 다음 명령을 사용하여 Azure에 로그인합니다.

    az login
    

검색 인덱스 만들기, 로드 및 쿼리

이전 설정 섹션에서 새 콘솔 애플리케이션을 만들고 Azure AI Search 클라이언트 라이브러리를 설치했습니다.

이 섹션에서는 검색 인덱스를 만들고, 문서로 로드하고, 쿼리를 실행하는 코드를 추가합니다. 프로그램을 실행하여 콘솔에서 결과를 확인합니다. 코드에 대한 자세한 설명은 코드 설명 섹션을 참조하세요.

이 빠른 시작의 샘플 코드는 권장되는 키 없는 인증에 Microsoft Entra ID를 사용합니다. API 키를 사용하려는 경우 개체를 DefaultAzureCredential 개체로 AzureKeyCredential 바꿀 수 있습니다.

Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
DefaultAzureCredential credential = new();
  1. Program.cs 다음 코드를 붙여넣습니다. serviceName 검색 서비스 이름 및 apiKey 관리자 API 키를 사용하여 변수 및 변수를 편집합니다.

    using System;
    using Azure;
    using Azure.Identity;
    using Azure.Search.Documents;
    using Azure.Search.Documents.Indexes;
    using Azure.Search.Documents.Indexes.Models;
    using Azure.Search.Documents.Models;
    
    namespace AzureSearch.Quickstart
    
    {
        class Program
        {
            static void Main(string[] args)
            {    
                // Your search service endpoint
                Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
    
                // Use the recommended keyless credential instead of the AzureKeyCredential credential.
                DefaultAzureCredential credential = new();
                //AzureKeyCredential credential = new AzureKeyCredential("Your search service admin key");
    
                // Create a SearchIndexClient to send create/delete index commands
                SearchIndexClient searchIndexClient = new SearchIndexClient(serviceEndpoint, credential);
    
                // Create a SearchClient to load and query documents
                string indexName = "hotels-quickstart";
                SearchClient searchClient = new SearchClient(serviceEndpoint, indexName, credential);
    
                // Delete index if it exists
                Console.WriteLine("{0}", "Deleting index...\n");
                DeleteIndexIfExists(indexName, searchIndexClient);
    
                // Create index
                Console.WriteLine("{0}", "Creating index...\n");
                CreateIndex(indexName, searchIndexClient);
    
                SearchClient ingesterClient = searchIndexClient.GetSearchClient(indexName);
    
                // Load documents
                Console.WriteLine("{0}", "Uploading documents...\n");
                UploadDocuments(ingesterClient);
    
                // Wait 2 secondsfor indexing to complete before starting queries (for demo and console-app purposes only)
                Console.WriteLine("Waiting for indexing...\n");
                System.Threading.Thread.Sleep(2000);
    
                // Call the RunQueries method to invoke a series of queries
                Console.WriteLine("Starting queries...\n");
                RunQueries(searchClient);
    
                // End the program
                Console.WriteLine("{0}", "Complete. Press any key to end this program...\n");
                Console.ReadKey();
            }
    
            // Delete the hotels-quickstart index to reuse its name
            private static void DeleteIndexIfExists(string indexName, SearchIndexClient searchIndexClient)
            {
                searchIndexClient.GetIndexNames();
                {
                    searchIndexClient.DeleteIndex(indexName);
                }
            }
            // Create hotels-quickstart index
            private static void CreateIndex(string indexName, SearchIndexClient searchIndexClient)
            {
                FieldBuilder fieldBuilder = new FieldBuilder();
                var searchFields = fieldBuilder.Build(typeof(Hotel));
    
                var definition = new SearchIndex(indexName, searchFields);
    
                var suggester = new SearchSuggester("sg", new[] { "HotelName", "Category", "Address/City", "Address/StateProvince" });
                definition.Suggesters.Add(suggester);
    
                searchIndexClient.CreateOrUpdateIndex(definition);
            }
    
            // Upload documents in a single Upload request.
            private static void UploadDocuments(SearchClient searchClient)
            {
                IndexDocumentsBatch<Hotel> batch = IndexDocumentsBatch.Create(
                    IndexDocumentsAction.Upload(
                        new Hotel()
                        {
                            HotelId = "1",
                            HotelName = "Stay-Kay City Hotel",
                            Description = "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.",
                            Category = "Boutique",
                            Tags = new[] { "view", "air conditioning", "concierge" },
                            ParkingIncluded = false,
                            LastRenovationDate = new DateTimeOffset(2022, 1, 18, 0, 0, 0, TimeSpan.Zero),
                            Rating = 3.6,
                            Address = new Address()
                            {
                                StreetAddress = "677 5th Ave",
                                City = "New York",
                                StateProvince = "NY",
                                PostalCode = "10022",
                                Country = "USA"
                            }
                        }),
                    IndexDocumentsAction.Upload(
                        new Hotel()
                        {
                            HotelId = "2",
                            HotelName = "Old Century Hotel",
                            Description = "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts. The hotel also regularly hosts events like wine tastings, beer dinners, and live music.",
                            Category = "Boutique",
                            Tags = new[] { "pool", "free wifi", "concierge" },
                            ParkingIncluded = false,
                            LastRenovationDate = new DateTimeOffset(2019, 2, 18, 0, 0, 0, TimeSpan.Zero),
                            Rating = 3.60,
                            Address = new Address()
                            {
                                StreetAddress = "140 University Town Center Dr",
                                City = "Sarasota",
                                StateProvince = "FL",
                                PostalCode = "34243",
                                Country = "USA"
                            }
                        }),
                    IndexDocumentsAction.Upload(
                        new Hotel()
                        {
                            HotelId = "3",
                            HotelName = "Gastronomic Landscape Hotel",
                            Description = "The Gastronomic Hotel stands out for its culinary excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services.",
                            Category = "Suite",
                            Tags = new[] { "restaurant", "bar", "continental breakfast" },
                            ParkingIncluded = true,
                            LastRenovationDate = new DateTimeOffset(2015, 9, 20, 0, 0, 0, TimeSpan.Zero),
                            Rating = 4.80,
                            Address = new Address()
                            {
                                StreetAddress = "3393 Peachtree Rd",
                                City = "Atlanta",
                                StateProvince = "GA",
                                PostalCode = "30326",
                                Country = "USA"
                            }
                        }),
                    IndexDocumentsAction.Upload(
                        new Hotel()
                        {
                            HotelId = "4",
                            HotelName = "Sublime Palace Hotel",
                            Description = "Sublime Palace Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 19th century resort, updated for every modern convenience.",
                            Category = "Boutique",
                            Tags = new[] { "concierge", "view", "air conditioning" },
                            ParkingIncluded = true,
                            LastRenovationDate = new DateTimeOffset(2020, 2, 06, 0, 0, 0, TimeSpan.Zero),
                            Rating = 4.60,
                            Address = new Address()
                            {
                                StreetAddress = "7400 San Pedro Ave",
                                City = "San Antonio",
                                StateProvince = "TX",
                                PostalCode = "78216",
                                Country = "USA"
                            }
                        })
                    );
    
                try
                {
                    IndexDocumentsResult result = searchClient.IndexDocuments(batch);
                }
                catch (Exception)
                {
                    // If for some reason any documents are dropped during indexing, you can compensate by delaying and
                    // retrying. This simple demo just logs the failed document keys and continues.
                    Console.WriteLine("Failed to index some of the documents: {0}");
                }
            }
    
            // Run queries, use WriteDocuments to print output
            private static void RunQueries(SearchClient searchClient)
            {
                SearchOptions options;
                SearchResults<Hotel> response;
    
                // Query 1
                Console.WriteLine("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n");
    
                options = new SearchOptions()
                {
                    IncludeTotalCount = true,
                    Filter = "",
                    OrderBy = { "" }
                };
    
                options.Select.Add("HotelId");
                options.Select.Add("HotelName");
                options.Select.Add("Rating");
    
                response = searchClient.Search<Hotel>("*", options);
                WriteDocuments(response);
    
                // Query 2
                Console.WriteLine("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n");
    
                options = new SearchOptions()
                {
                    Filter = "Rating gt 4",
                    OrderBy = { "Rating desc" }
                };
    
                options.Select.Add("HotelId");
                options.Select.Add("HotelName");
                options.Select.Add("Rating");
    
                response = searchClient.Search<Hotel>("hotels", options);
                WriteDocuments(response);
    
                // Query 3
                Console.WriteLine("Query #3: Limit search to specific fields (pool in Tags field)...\n");
    
                options = new SearchOptions()
                {
                    SearchFields = { "Tags" }
                };
    
                options.Select.Add("HotelId");
                options.Select.Add("HotelName");
                options.Select.Add("Tags");
    
                response = searchClient.Search<Hotel>("pool", options);
                WriteDocuments(response);
    
                // Query 4 - Use Facets to return a faceted navigation structure for a given query
                // Filters are typically used with facets to narrow results on OnClick events
                Console.WriteLine("Query #4: Facet on 'Category'...\n");
    
                options = new SearchOptions()
                {
                    Filter = ""
                };
    
                options.Facets.Add("Category");
    
                options.Select.Add("HotelId");
                options.Select.Add("HotelName");
                options.Select.Add("Category");
    
                response = searchClient.Search<Hotel>("*", options);
                WriteDocuments(response);
    
                // Query 5
                Console.WriteLine("Query #5: Look up a specific document...\n");
    
                Response<Hotel> lookupResponse;
                lookupResponse = searchClient.GetDocument<Hotel>("3");
    
                Console.WriteLine(lookupResponse.Value.HotelId);
    
    
                // Query 6
                Console.WriteLine("Query #6: Call Autocomplete on HotelName...\n");
    
                var autoresponse = searchClient.Autocomplete("sa", "sg");
                WriteDocuments(autoresponse);
    
            }
    
            // Write search results to console
            private static void WriteDocuments(SearchResults<Hotel> searchResults)
            {
                foreach (SearchResult<Hotel> result in searchResults.GetResults())
                {
                    Console.WriteLine(result.Document);
                }
    
                Console.WriteLine();
            }
    
            private static void WriteDocuments(AutocompleteResults autoResults)
            {
                foreach (AutocompleteItem result in autoResults.Results)
                {
                    Console.WriteLine(result.Text);
                }
    
                Console.WriteLine();
            }
        }
    }
    
  2. 동일한 폴더에서 Hotel.cs파일을 만들고 다음 코드를 붙여넣습니다. 이 코드는 호텔 문서의 구조를 정의합니다.

    using System;
    using System.Text.Json.Serialization;
    using Azure.Search.Documents.Indexes;
    using Azure.Search.Documents.Indexes.Models;
    
    namespace AzureSearch.Quickstart
    {
        public partial class Hotel
        {
            [SimpleField(IsKey = true, IsFilterable = true)]
            public string HotelId { get; set; }
    
            [SearchableField(IsSortable = true)]
            public string HotelName { get; set; }
    
            [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.EnLucene)]
            public string Description { get; set; }
    
            [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public string Category { get; set; }
    
            [SearchableField(IsFilterable = true, IsFacetable = true)]
            public string[] Tags { get; set; }
    
            [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public bool? ParkingIncluded { get; set; }
    
            [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public DateTimeOffset? LastRenovationDate { get; set; }
    
            [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public double? Rating { get; set; }
    
            [SearchableField]
            public Address Address { get; set; }
        }
    }
    
  3. Hotel.cs파일을 만들고 다음 코드를 붙여넣어 호텔 문서의 구조를 정의합니다. 필드의 특성은 필드가 애플리케이션에서 사용되는 방식을 결정합니다. 예를 들어 IsFilterable 특성은 필터 식을 지원하는 모든 필드에 할당해야 합니다.

    using System;
    using System.Text.Json.Serialization;
    using Azure.Search.Documents.Indexes;
    using Azure.Search.Documents.Indexes.Models;
    
    namespace AzureSearch.Quickstart
    {
        public partial class Hotel
        {
            [SimpleField(IsKey = true, IsFilterable = true)]
            public string HotelId { get; set; }
    
            [SearchableField(IsSortable = true)]
            public string HotelName { get; set; }
    
            [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.EnLucene)]
            public string Description { get; set; }
    
            [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public string Category { get; set; }
    
            [SearchableField(IsFilterable = true, IsFacetable = true)]
            public string[] Tags { get; set; }
    
            [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public bool? ParkingIncluded { get; set; }
    
            [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public DateTimeOffset? LastRenovationDate { get; set; }
    
            [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public double? Rating { get; set; }
    
            [SearchableField]
            public Address Address { get; set; }
        }
    }
    
  4. Address.cs파일을 만들고 다음 코드를 붙여넣어 주소 문서의 구조를 정의합니다.

    using Azure.Search.Documents.Indexes;
    
    namespace AzureSearch.Quickstart
    {
        public partial class Address
        {
            [SearchableField(IsFilterable = true)]
            public string StreetAddress { get; set; }
    
            [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public string City { get; set; }
    
            [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public string StateProvince { get; set; }
    
            [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public string PostalCode { get; set; }
    
            [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public string Country { get; set; }
        }
    }
    
  5. Hotel.Methods.cs파일을 만들고 다음 코드를 붙여넣어 클래스에 대한 재정의 ToString()Hotel 정의합니다.

    using System;
    using System.Text;
    
    namespace AzureSearch.Quickstart
    {
        public partial class Hotel
        {
            public override string ToString()
            {
                var builder = new StringBuilder();
    
                if (!String.IsNullOrEmpty(HotelId))
                {
                    builder.AppendFormat("HotelId: {0}\n", HotelId);
                }
    
                if (!String.IsNullOrEmpty(HotelName))
                {
                    builder.AppendFormat("Name: {0}\n", HotelName);
                }
    
                if (!String.IsNullOrEmpty(Description))
                {
                    builder.AppendFormat("Description: {0}\n", Description);
                }
    
                if (!String.IsNullOrEmpty(Category))
                {
                    builder.AppendFormat("Category: {0}\n", Category);
                }
    
                if (Tags != null && Tags.Length > 0)
                {
                    builder.AppendFormat("Tags: [ {0} ]\n", String.Join(", ", Tags));
                }
    
                if (ParkingIncluded.HasValue)
                {
                    builder.AppendFormat("Parking included: {0}\n", ParkingIncluded.Value ? "yes" : "no");
                }
    
                if (LastRenovationDate.HasValue)
                {
                    builder.AppendFormat("Last renovated on: {0}\n", LastRenovationDate);
                }
    
                if (Rating.HasValue)
                {
                    builder.AppendFormat("Rating: {0}\n", Rating);
                }
    
                if (Address != null && !Address.IsEmpty)
                {
                    builder.AppendFormat("Address: \n{0}\n", Address.ToString());
                }
    
                return builder.ToString();
            }
        }
    }
    
  6. Address.Methods.cs파일을 만들고 다음 코드를 붙여넣어 클래스에 대한 재정의를 ToString()Address 정의합니다.

    using System;
    using System.Text;
    using System.Text.Json.Serialization;
    
    namespace AzureSearch.Quickstart
    {
        public partial class Address
        {
            public override string ToString()
            {
                var builder = new StringBuilder();
    
                if (!IsEmpty)
                {
                    builder.AppendFormat("{0}\n{1}, {2} {3}\n{4}", StreetAddress, City, StateProvince, PostalCode, Country);
                }
    
                return builder.ToString();
            }
    
            [JsonIgnore]
            public bool IsEmpty => String.IsNullOrEmpty(StreetAddress) &&
                                   String.IsNullOrEmpty(City) &&
                                   String.IsNullOrEmpty(StateProvince) &&
                                   String.IsNullOrEmpty(PostalCode) &&
                                   String.IsNullOrEmpty(Country);
        }
    }
    
  7. 다음 명령을 사용하여 애플리케이션을 빌드하고 실행합니다.

    dotnet run
    

출력에는 쿼리 정보 및 결과가 추가된 Console.WriteLine의 메시지가 포함됩니다.

코드 설명

이전 섹션에서는 새 콘솔 애플리케이션을 만들고 Azure AI Search 클라이언트 라이브러리를 설치했습니다. 검색 인덱스를 만들고, 문서로 로드하고, 쿼리를 실행하는 코드를 추가했습니다. 콘솔에서 결과를 확인하기 위해 프로그램을 실행했습니다.

이 섹션에서는 콘솔 애플리케이션에 추가한 코드에 대해 설명합니다.

검색 클라이언트 만들기

Program.cs 두 개의 클라이언트를 만들었습니다.

두 클라이언트 모두 이전에 리소스 정보 섹션에 설명된 검색 서비스 엔드포인트 및 자격 증명이 필요합니다.

이 빠른 시작의 샘플 코드는 권장되는 키 없는 인증에 Microsoft Entra ID를 사용합니다. API 키를 사용하려는 경우 개체를 DefaultAzureCredential 개체로 AzureKeyCredential 바꿀 수 있습니다.

Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
DefaultAzureCredential credential = new();
static void Main(string[] args)
{
    // Your search service endpoint
    Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");

    // Use the recommended keyless credential instead of the AzureKeyCredential credential.
    DefaultAzureCredential credential = new();
    //AzureKeyCredential credential = new AzureKeyCredential("Your search service admin key");

    // Create a SearchIndexClient to send create/delete index commands
    SearchIndexClient searchIndexClient = new SearchIndexClient(serviceEndpoint, credential);

    // Create a SearchClient to load and query documents
    string indexName = "hotels-quickstart";
    SearchClient searchClient = new SearchClient(serviceEndpoint, indexName, credential);
    
    // REDACTED FOR BREVITY . . . 
}

인덱스 만들기

이 빠른 시작에서는 호텔 데이터로 로드하고 쿼리를 실행하는 호텔 인덱스를 작성합니다. 이 단계에서는 인덱스 필드를 정의합니다. 각 필드 정의에는 이름, 데이터 형식 및 필드가 사용되는 방식을 결정하는 특성이 포함됩니다.

이 예제에서는 단순성과 가독성을 위해 Azure.Search.Documents 라이브러리의 동기 메서드를 사용합니다. 그러나 프로덕션 시나리오의 경우 비동기 메서드를 사용하여 앱의 확장성 및 응답성을 유지해야 합니다. 예를 들어 CreateIndex 대신 CreateIndexAsync를 사용해야 합니다.

구조 정의

Hotel.cs 및 Address.cs 두 개의 도우미 클래스를 만들어 호텔 문서의 구조와 주소를 정의했습니다. 클래스에는 Hotel 호텔 ID, 이름, 설명, 범주, 태그, 주차, 리노베이션 날짜, 등급 및 주소에 대한 필드가 포함됩니다. 이 Address 클래스에는 주소, 도시, 시/도, 우편 번호 및 국가/지역에 대한 필드가 포함됩니다.

Azure.Search.Documents 클라이언트 라이브러리에서 SearchableField 및 SimpleField를 사용하여 필드 정의를 간소화할 수 있습니다. 둘 다 SearchField의 파생물이며 잠재적으로 코드를 단순화할 수 있습니다.

  • SimpleField 는 모든 데이터 형식일 수 있고, 항상 검색할 수 없으며(전체 텍스트 검색 쿼리에서는 무시됨) 검색할 수 있습니다(숨겨지지 않음). 다른 특성은 기본적으로 꺼져 있지만 사용하도록 설정할 수 있습니다. 필터, 패싯 또는 점수 매기기 프로필에만 사용되는 문서 ID 또는 필드에는 SimpleField를 사용할 수 있습니다. 그렇다면 문서 ID에 대한 IsKey = true와 같이 시나리오에 필요한 특성을 적용해야 합니다. 자세한 내용은 소스 코드의 SimpleFieldAttribute.cs를 참조하세요.

  • SearchableField는 문자열이어야 하며, 항상 검색할 수 있고 조회할 수 있습니다. 다른 특성은 기본적으로 꺼져 있지만 사용하도록 설정할 수 있습니다. 이 필드 형식은 검색할 수 있으므로 동의어와 분석기 속성의 전체 보충을 지원합니다. 자세한 내용은 소스 코드의 SearchableFieldAttribute.cs를 참조하세요.

기본 SearchField API를 사용하든지 도우미 모델 중 하나를 사용하든지 간에 필터, 패싯 및 정렬 특성을 명시적으로 사용하도록 설정해야 합니다. 예를 들어 이전 샘플과 같이 IsFilterable, IsSortableIsFacetable 은 명시적으로 특성을 지정해야 합니다.

검색 인덱스 만들기

Program.cs SearchIndex 개체를 만든 다음 CreateIndex 메서드를 호출하여 검색 서비스에서 인덱스를 표현합니다. 인덱스에는 지정된 필드에서 자동 완성을 사용하도록 설정하는 SearchSuggester도 포함되어 있습니다.

// Create hotels-quickstart index
private static void CreateIndex(string indexName, SearchIndexClient searchIndexClient)
{
    FieldBuilder fieldBuilder = new FieldBuilder();
    var searchFields = fieldBuilder.Build(typeof(Hotel));

    var definition = new SearchIndex(indexName, searchFields);

    var suggester = new SearchSuggester("sg", new[] { "HotelName", "Category", "Address/City", "Address/StateProvince" });
    definition.Suggesters.Add(suggester);

    searchIndexClient.CreateOrUpdateIndex(definition);
}

문서 로드

Azure AI 검색은 서비스에 저장된 콘텐츠를 검색합니다. 이 단계에서는 만든 호텔 인덱스 준수 JSON 문서를 로드합니다.

Azure AI 검색에서 검색 문서는 인덱싱에 대한 입력과 쿼리의 출력 모두에 해당하는 데이터 구조입니다. 외부 데이터 소스에서 가져온, 문서 입력은 데이터베이스의 행, Blob Storage의 Blob 또는 디스크의 JSON 문서일 수 있습니다. 이 예에서는 손쉬운 방법을 사용하여 4개 호텔에 대한 JSON 문서를 코드 자체에 포함합니다.

문서를 업로드할 때 IndexDocumentsBatch 개체를 사용해야 합니다. IndexDocumentsBatch 개체에는 Actions 컬렉션이 포함되며 컬렉션마다 수행할 작업(upload, merge, delete 및 mergeOrUpload)을 Azure AI 검색에 알려주는 속성과 문서가 포함됩니다.

Program.cs 문서 및 인덱스 작업의 배열을 만든 다음 배열을 전달합니다IndexDocumentsBatch. 다음 문서는 호텔 클래스에서 정의한 대로 호텔 빠른 시작 인덱스(호텔 빠른 시작 인덱스)를 준수합니다.

// Upload documents in a single Upload request.
private static void UploadDocuments(SearchClient searchClient)
{
    IndexDocumentsBatch<Hotel> batch = IndexDocumentsBatch.Create(
        IndexDocumentsAction.Upload(
            new Hotel()
            {
                HotelId = "1",
                HotelName = "Stay-Kay City Hotel",
                Description = "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.",
                Category = "Boutique",
                Tags = new[] { "view", "air conditioning", "concierge" },
                ParkingIncluded = false,
                LastRenovationDate = new DateTimeOffset(2022, 1, 18, 0, 0, 0, TimeSpan.Zero),
                Rating = 3.6,
                Address = new Address()
                {
                    StreetAddress = "677 5th Ave",
                    City = "New York",
                    StateProvince = "NY",
                    PostalCode = "10022",
                    Country = "USA"
                }
            }),
        // REDACTED FOR BREVITY
}

IndexDocumentsBatch 개체를 초기화 한 후에는 SearchClient 개체에서 IndexDocuments를 호출하여 이 개체를 인덱스에 전송할 수 있습니다.

SearchClient를 Main()사용하여 문서를 로드하지만 작업에는 일반적으로 SearchIndexClient와 연결된 서비스에 대한 관리자 권한도 필요합니다. 이 작업을 설정하는 한 가지 방법은 SearchClient를 통해 SearchIndexClient 가져오는 것입니다(searchIndexClient 이 예제에서는).

SearchClient ingesterClient = searchIndexClient.GetSearchClient(indexName);

// Load documents
Console.WriteLine("{0}", "Uploading documents...\n");
UploadDocuments(ingesterClient);

모든 명령을 순차적으로 실행하는 콘솔 앱이 있으므로 인덱싱과 쿼리 사이에 2초 대기 시간을 추가합니다.

// Wait 2 seconds for indexing to complete before starting queries (for demo and console-app purposes only)
Console.WriteLine("Waiting for indexing...\n");
System.Threading.Thread.Sleep(2000);

2초 지연은 비동기식 인덱싱을 보정합니다. 따라서 쿼리가 실행되기 전에 모든 문서를 인덱싱할 수 있습니다. 지연 시 코딩은 일반적으로 데모, 테스트, 샘플 애플리케이션에서만 필요합니다.

인덱스 검색

첫 번째 문서의 인덱싱이 완료되는 즉시 쿼리 결과를 얻을 수 있지만 인덱스에 대한 실제 테스트는 모든 문서의 인덱싱이 완료될 때까지 기다려야 합니다.

이 섹션에서는 쿼리 논리 및 결과라는 두 가지 기능을 추가합니다. 쿼리에는 Search 메서드를 사용합니다. 이 메서드는 검색 텍스트(쿼리 문자열)뿐 아니라 다른 옵션을 사용합니다.

SearchResults 클래스는 결과를 나타냅니다.

Program.cs 메서드는 WriteDocuments 검색 결과를 콘솔에 출력합니다.

// Write search results to console
private static void WriteDocuments(SearchResults<Hotel> searchResults)
{
    foreach (SearchResult<Hotel> result in searchResults.GetResults())
    {
        Console.WriteLine(result.Document);
    }

    Console.WriteLine();
}

private static void WriteDocuments(AutocompleteResults autoResults)
{
    foreach (AutocompleteItem result in autoResults.Results)
    {
        Console.WriteLine(result.Text);
    }

    Console.WriteLine();
}

쿼리 예제 1

메서드는 RunQueries 쿼리를 실행하고 결과를 반환합니다. 결과는 Hotel 개체입니다. 이 샘플에서는 메서드 서명과 첫 번째 쿼리를 보여줍니다. 이 쿼리는 문서에서 선택한 필드를 사용하여 결과를 작성할 수 있도록 하는 Select 매개 변수를 보여 줍니다.

// Run queries, use WriteDocuments to print output
private static void RunQueries(SearchClient searchClient)
{
    SearchOptions options;
    SearchResults<Hotel> response;
    
    // Query 1
    Console.WriteLine("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n");

    options = new SearchOptions()
    {
        IncludeTotalCount = true,
        Filter = "",
        OrderBy = { "" }
    };

    options.Select.Add("HotelId");
    options.Select.Add("HotelName");
    options.Select.Add("Address/City");

    response = searchClient.Search<Hotel>("*", options);
    WriteDocuments(response);
    // REDACTED FOR BREVITY
}

쿼리 예제 2

두 번째 쿼리에서 용어를 검색하고 등급이 4보다 큰 문서를 선택하는 필터를 추가한 다음 등급별로 내림차순으로 정렬합니다. 필터는 인덱스의 IsFilterable 필드를 통해 평가되는 부울 식입니다. 필터는 포함 또는 제외 값을 쿼리합니다. 따라서 필터 쿼리와 관련된 관련성 점수가 없습니다.

// Query 2
Console.WriteLine("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n");

options = new SearchOptions()
{
    Filter = "Rating gt 4",
    OrderBy = { "Rating desc" }
};

options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Rating");

response = searchClient.Search<Hotel>("hotels", options);
WriteDocuments(response);

쿼리 예제 3

세 번째 쿼리는 전체 텍스트 검색 작업의 범위를 특정 필드로 지정하는 데 사용되는 searchFields를 보여 줍니다.

// Query 3
Console.WriteLine("Query #3: Limit search to specific fields (pool in Tags field)...\n");

options = new SearchOptions()
{
    SearchFields = { "Tags" }
};

options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Tags");

response = searchClient.Search<Hotel>("pool", options);
WriteDocuments(response);

쿼리 예제 4

네 번째 쿼리는 facets패싯 탐색 구조를 구성하는 데 사용할 수 있는 방법을 보여 줍니다.

// Query 4
Console.WriteLine("Query #4: Facet on 'Category'...\n");

options = new SearchOptions()
{
    Filter = ""
};

options.Facets.Add("Category");

options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Category");

response = searchClient.Search<Hotel>("*", options);
WriteDocuments(response);

쿼리 예제 5

다섯 번째 쿼리에서 특정 문서를 반환합니다. 문서 조회는 결과 집합의 이벤트에 대한 일반적인 응답 OnClick 입니다.

// Query 5
Console.WriteLine("Query #5: Look up a specific document...\n");

Response<Hotel> lookupResponse;
lookupResponse = searchClient.GetDocument<Hotel>("3");

Console.WriteLine(lookupResponse.Value.HotelId);

쿼리 예제 6

마지막 쿼리는 자동 완성 구문을 보여 줍니다. 이 구문은 인덱스에 정의한 제안기와 연결된 sourceFields에서 가능한 두 일치 항목으로 확인되는 sa부분 사용자 입력을 시뮬레이트합니다.

// Query 6
Console.WriteLine("Query #6: Call Autocomplete on HotelName that starts with 'sa'...\n");

var autoresponse = searchClient.Autocomplete("sa", "sg");
WriteDocuments(autoresponse);

쿼리 요약

이전 쿼리는 전체 텍스트 검색, 필터 및 자동 완성과 같은 쿼리에서 용어를 일치시키는 여러 방법을 보여줍니다.

전체 텍스트 검색 및 필터는 SearchClient.Search 메서드를 사용하여 수행됩니다. 검색 쿼리는 searchText 문자열로 전달할 수 있는 반면, 필터 식은 SearchOptions 클래스의 Filter 속성으로 전달할 수 있습니다. 검색하지 않고 필터링하려면 "*" 메서드의 searchText 매개 변수에 대한 를 전달합니다. 필터링하지 않고 검색하려면 Filter 속성을 설정하지 않고 그대로 두거나 SearchOptions 인스턴스에 전달하지 않아야 합니다.

이 빠른 시작에서는 Azure.Search.Documents 클라이언트 라이브러리를 사용하여 전체 텍스트 검색에 대한 샘플 데이터를 사용하여 검색 인덱스 만들기, 로드 및 쿼리를 수행합니다. 전체 텍스트 검색은 인덱싱 및 쿼리에 Apache Lucene을 사용하고 BM25 순위 지정 알고리즘을 사용하여 결과를 채점합니다.

이 빠른 시작에서는 호텔 4개에 대한 데이터가 포함된 작은 호텔 빠른 시작 인덱스를 만들고 쿼리합니다.

필수 조건

Microsoft Entra ID 필수 구성 요소

Microsoft Entra ID를 사용하는 권장 키 없는 인증의 경우 다음을 수행해야 합니다.

  • Microsoft Entra ID를 사용하여 키 없는 인증에 사용되는 Azure CLI 를 설치합니다.
  • 사용자 계정에 역할과 Search Service Contributor 역할을 모두 Search Index Data Contributor 할당합니다. Azure Portal의 액세스 제어(IAM)>역할을 할당할 수 있습니다. 자세한 내용은 역할을 사용하여 Azure AI Search에 연결을 참조 하세요.

리소스 정보 검색

Azure AI Search 서비스를 사용하여 애플리케이션을 인증하려면 다음 정보를 검색해야 합니다.

변수 이름
SEARCH_API_ENDPOINT 이 값은 Azure Portal에서 찾을 수 있습니다. 검색 서비스를 선택한 다음 왼쪽 메뉴에서 개요를 선택합니다. EssentialsURL 값은 필요한 엔드포인트입니다. 엔드포인트의 예는 다음과 같습니다. https://mydemo.search.windows.net

키 없는 인증 및 환경 변수 설정에 대해 자세히 알아봅니다.

설정

이 빠른 시작의 샘플은 Java 런타임에서 작동합니다. Azul Zulu OpenJDK와 같은 Java 개발 키트를 설치해야 합니다. OpenJDK의 Microsoft 빌드 또는 선호하는 JDK도 작동해야 합니다.

  1. Apache Maven을 설치합니다. 그런 다음 mvn -v을(를) 실행하여 성공적인 설치를 확인합니다.

  2. pom.xml 파일을 프로젝트의 루트에 만들고, 다음 코드를 복사합니다.

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>azure.search.sample</groupId>
        <artifactId>azuresearchquickstart</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <build>
            <sourceDirectory>src</sourceDirectory>
            <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                <source>1.8</source>
                <target>1.8</target>
                </configuration>
            </plugin>
            </plugins>
        </build>
        <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.11</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>com.azure</groupId>
                <artifactId>azure-search-documents</artifactId>
                <version>11.7.3</version>
            </dependency>
            <dependency>
                <groupId>com.azure</groupId>
                <artifactId>azure-core</artifactId>
                <version>1.53.0</version>
            </dependency>
            <dependency>
                <groupId>com.azure</groupId>
                <artifactId>azure-identity</artifactId>
                <version>1.15.1</version>
            </dependency>
        </dependencies>
    </project>
    
  3. 다음을 사용하여 Java용 Azure AI Search 클라이언트 라이브러리(Azure.Search.Documents) 및 Java 용 Azure ID 클라이언트 라이브러리를 포함한 종속성을 설치합니다.

    mvn clean dependency:copy-dependencies
    
  4. Microsoft Entra ID로 권장되는 키 없는 인증의 경우 다음 명령을 사용하여 Azure에 로그인합니다.

    az login
    

검색 인덱스 만들기, 로드 및 쿼리

이전 설정 섹션에서 Azure AI Search 클라이언트 라이브러리 및 기타 종속성을 설치했습니다.

이 섹션에서는 검색 인덱스를 만들고, 문서로 로드하고, 쿼리를 실행하는 코드를 추가합니다. 프로그램을 실행하여 콘솔에서 결과를 확인합니다. 코드에 대한 자세한 설명은 코드 설명 섹션을 참조하세요.

이 빠른 시작의 샘플 코드는 권장되는 키 없는 인증에 Microsoft Entra ID를 사용합니다. API 키를 사용하려는 경우 개체를 DefaultAzureCredential 개체로 AzureKeyCredential 바꿀 수 있습니다.

String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
  1. App.java파일을 만들고 다음 코드를 App.java 붙여넣습니다.

    import java.util.Arrays;
    import java.util.ArrayList;
    import java.time.OffsetDateTime;
    import java.time.ZoneOffset;
    import java.time.LocalDateTime;
    import java.time.LocalDate;
    import java.time.LocalTime;
    import com.azure.core.util.Configuration;
    import com.azure.core.util.Context;
    import com.azure.identity.DefaultAzureCredential;
    import com.azure.identity.DefaultAzureCredentialBuilder;
    import com.azure.search.documents.SearchClient;
    import com.azure.search.documents.SearchClientBuilder;
    import com.azure.search.documents.indexes.SearchIndexClient;
    import com.azure.search.documents.indexes.SearchIndexClientBuilder;
    import com.azure.search.documents.indexes.models.IndexDocumentsBatch;
    import com.azure.search.documents.models.SearchOptions;
    import com.azure.search.documents.indexes.models.SearchIndex;
    import com.azure.search.documents.indexes.models.SearchSuggester;
    import com.azure.search.documents.util.AutocompletePagedIterable;
    import com.azure.search.documents.util.SearchPagedIterable;
    
    public class App {
    
        public static void main(String[] args) {
            // Your search service endpoint
            "https://<Put your search service NAME here>.search.windows.net/";
    
            // Use the recommended keyless credential instead of the AzureKeyCredential credential.
            DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
            //AzureKeyCredential credential = new AzureKeyCredential("<Your search service admin key>");
    
            // Create a SearchIndexClient to send create/delete index commands
            SearchIndexClient searchIndexClient = new SearchIndexClientBuilder()
                .endpoint(searchServiceEndpoint)
                .credential(credential)
                .buildClient();
    
            // Create a SearchClient to load and query documents
            String indexName = "hotels-quickstart-java";
            SearchClient searchClient = new SearchClientBuilder()
                .endpoint(searchServiceEndpoint)
                .credential(credential)
                .indexName(indexName)
                .buildClient();
    
            // Create Search Index for Hotel model
            searchIndexClient.createOrUpdateIndex(
                new SearchIndex(indexName, SearchIndexClient.buildSearchFields(Hotel.class, null))
                .setSuggesters(new SearchSuggester("sg", Arrays.asList("HotelName"))));
    
            // Upload sample hotel documents to the Search Index
            uploadDocuments(searchClient);
    
            // Wait 2 seconds for indexing to complete before starting queries (for demo and console-app purposes only)
            System.out.println("Waiting for indexing...\n");
            try
            {
                Thread.sleep(2000);
            }
            catch (InterruptedException e)
            {
            }
    
            // Call the RunQueries method to invoke a series of queries
            System.out.println("Starting queries...\n");
            RunQueries(searchClient);
    
            // End the program
            System.out.println("Complete.\n");
        }
    
        // Upload documents in a single Upload request.
        private static void uploadDocuments(SearchClient searchClient)
        {
            var hotelList = new ArrayList<Hotel>();
    
            var hotel = new Hotel();
            hotel.hotelId = "1";
            hotel.hotelName = "Stay-Kay City Hotel";
            hotel.description = "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.";
            hotel.category = "Boutique";
            hotel.tags = new String[] { "view", "air conditioning", "concierge" };
            hotel.parkingIncluded = false;
            hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(2022, 1, 18), LocalTime.of(0, 0)), ZoneOffset.UTC);
            hotel.rating = 3.6;
            hotel.address = new Address();
            hotel.address.streetAddress = "677 5th Ave";
            hotel.address.city = "New York";
            hotel.address.stateProvince = "NY";
            hotel.address.postalCode = "10022";
            hotel.address.country = "USA";
            hotelList.add(hotel);
    
            hotel = new Hotel();
            hotel.hotelId = "2";
            hotel.hotelName = "Old Century Hotel";
            hotel.description = "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts. The hotel also regularly hosts events like wine tastings, beer dinners, and live music.",
            hotel.category = "Boutique";
            hotel.tags = new String[] { "pool", "free wifi", "concierge" };
            hotel.parkingIncluded = false;
            hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(2019, 2, 18), LocalTime.of(0, 0)), ZoneOffset.UTC);
            hotel.rating = 3.60;
            hotel.address = new Address();
            hotel.address.streetAddress = "140 University Town Center Dr";
            hotel.address.city = "Sarasota";
            hotel.address.stateProvince = "FL";
            hotel.address.postalCode = "34243";
            hotel.address.country = "USA";
            hotelList.add(hotel);
    
            hotel = new Hotel();
            hotel.hotelId = "3";
            hotel.hotelName = "Gastronomic Landscape Hotel";
            hotel.description = "The Gastronomic Hotel stands out for its culinary excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services.";
            hotel.category = "Suite";
            hotel.tags = new String[] { "restaurant", "bar", "continental breakfast" };
            hotel.parkingIncluded = true;
            hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(2015, 9, 20), LocalTime.of(0, 0)), ZoneOffset.UTC);
            hotel.rating = 4.80;
            hotel.address = new Address();
            hotel.address.streetAddress = "3393 Peachtree Rd";
            hotel.address.city = "Atlanta";
            hotel.address.stateProvince = "GA";
            hotel.address.postalCode = "30326";
            hotel.address.country = "USA";
            hotelList.add(hotel);
    
            hotel = new Hotel();
            hotel.hotelId = "4";
            hotel.hotelName = "Sublime Palace Hotel";
            hotel.description = "Sublime Palace Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 19th century resort, updated for every modern convenience.";
            hotel.category = "Boutique";
            hotel.tags = new String[] { "concierge", "view", "air conditioning" };
            hotel.parkingIncluded = true;
            hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(2020, 2, 06), LocalTime.of(0, 0)), ZoneOffset.UTC);
            hotel.rating = 4.60;
            hotel.address = new Address();
            hotel.address.streetAddress = "7400 San Pedro Ave";
            hotel.address.city = "San Antonio";
            hotel.address.stateProvince = "TX";
            hotel.address.postalCode = "78216";
            hotel.address.country = "USA";
            hotelList.add(hotel);
    
            var batch = new IndexDocumentsBatch<Hotel>();
            batch.addMergeOrUploadActions(hotelList);
            try
            {
                searchClient.indexDocuments(batch);
            }
            catch (Exception e)
            {
                e.printStackTrace();
                // If for some reason any documents are dropped during indexing, you can compensate by delaying and
                // retrying. This simple demo just logs failure and continues
                System.err.println("Failed to index some of the documents");
            }
        }
    
        // Write search results to console
        private static void WriteSearchResults(SearchPagedIterable searchResults)
        {
            searchResults.iterator().forEachRemaining(result ->
            {
                Hotel hotel = result.getDocument(Hotel.class);
                System.out.println(hotel);
            });
    
            System.out.println();
        }
    
        // Write autocomplete results to console
        private static void WriteAutocompleteResults(AutocompletePagedIterable autocompleteResults)
        {
            autocompleteResults.iterator().forEachRemaining(result ->
            {
                String text = result.getText();
                System.out.println(text);
            });
    
            System.out.println();
        }
    
        // Run queries, use WriteDocuments to print output
        private static void RunQueries(SearchClient searchClient)
        {
            // Query 1
            System.out.println("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n");
    
            SearchOptions options = new SearchOptions();
            options.setIncludeTotalCount(true);
            options.setFilter("");
            options.setOrderBy("");
            options.setSelect("HotelId", "HotelName", "Address/City");
    
            WriteSearchResults(searchClient.search("*", options, Context.NONE));
    
            // Query 2
            System.out.println("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n");
    
            options = new SearchOptions();
            options.setFilter("Rating gt 4");
            options.setOrderBy("Rating desc");
            options.setSelect("HotelId", "HotelName", "Rating");
    
            WriteSearchResults(searchClient.search("hotels", options, Context.NONE));
    
            // Query 3
            System.out.println("Query #3: Limit search to specific fields (pool in Tags field)...\n");
    
            options = new SearchOptions();
            options.setSearchFields("Tags");
    
            options.setSelect("HotelId", "HotelName", "Tags");
    
            WriteSearchResults(searchClient.search("pool", options, Context.NONE));
    
            // Query 4
            System.out.println("Query #4: Facet on 'Category'...\n");
    
            options = new SearchOptions();
            options.setFilter("");
            options.setFacets("Category");
            options.setSelect("HotelId", "HotelName", "Category");
    
            WriteSearchResults(searchClient.search("*", options, Context.NONE));
    
            // Query 5
            System.out.println("Query #5: Look up a specific document...\n");
    
            Hotel lookupResponse = searchClient.getDocument("3", Hotel.class);
            System.out.println(lookupResponse.hotelId);
            System.out.println();
    
             // Query 6
            System.out.println("Query #6: Call Autocomplete on HotelName that starts with 's'...\n");
    
            WriteAutocompleteResults(searchClient.autocomplete("s", "sg"));
        }
    }
    
  2. Hotel.java 새 파일을 만들고 다음 코드를 Hotel.java 붙여넣습니다.

    import com.azure.search.documents.indexes.SearchableField;
    import com.azure.search.documents.indexes.SimpleField;
    import com.fasterxml.jackson.annotation.JsonInclude;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.annotation.JsonInclude.Include;
    
    import java.time.OffsetDateTime;
    
    /**
     * Model class representing a hotel.
     */
    @JsonInclude(Include.NON_NULL)
    public class Hotel {
        /**
         * Hotel ID
         */
        @JsonProperty("HotelId")
        @SimpleField(isKey = true)
        public String hotelId;
    
        /**
         * Hotel name
         */
        @JsonProperty("HotelName")
        @SearchableField(isSortable = true)
        public String hotelName;
    
        /**
         * Description
         */
        @JsonProperty("Description")
        @SearchableField(analyzerName = "en.microsoft")
        public String description;
    
        /**
         * Category
         */
        @JsonProperty("Category")
        @SearchableField(isFilterable = true, isSortable = true, isFacetable = true)
        public String category;
    
        /**
         * Tags
         */
        @JsonProperty("Tags")
        @SearchableField(isFilterable = true, isFacetable = true)
        public String[] tags;
    
        /**
         * Whether parking is included
         */
        @JsonProperty("ParkingIncluded")
        @SimpleField(isFilterable = true, isSortable = true, isFacetable = true)
        public Boolean parkingIncluded;
    
        /**
         * Last renovation time
         */
        @JsonProperty("LastRenovationDate")
        @SimpleField(isFilterable = true, isSortable = true, isFacetable = true)
        public OffsetDateTime lastRenovationDate;
    
        /**
         * Rating
         */
        @JsonProperty("Rating")
        @SimpleField(isFilterable = true, isSortable = true, isFacetable = true)
        public Double rating;
    
        /**
         * Address
         */
        @JsonProperty("Address")
        public Address address;
    
        @Override
        public String toString()
        {
            try
            {
                return new ObjectMapper().writeValueAsString(this);
            }
            catch (JsonProcessingException e)
            {
                e.printStackTrace();
                return "";
            }
        }
    }
    
  3. Address.java파일을 만들고 다음 코드를 Address.java 붙여넣습니다.

    import com.azure.search.documents.indexes.SearchableField;
    import com.fasterxml.jackson.annotation.JsonInclude;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import com.fasterxml.jackson.annotation.JsonInclude.Include;
    
    /**
     * Model class representing an address.
     */
    @JsonInclude(Include.NON_NULL)
    public class Address {
        /**
         * Street address
         */
        @JsonProperty("StreetAddress")
        @SearchableField
        public String streetAddress;
    
        /**
         * City
         */
        @JsonProperty("City")
        @SearchableField(isFilterable = true, isSortable = true, isFacetable = true)
        public String city;
    
        /**
         * State or province
         */
        @JsonProperty("StateProvince")
        @SearchableField(isFilterable = true, isSortable = true, isFacetable = true)
        public String stateProvince;
    
        /**
         * Postal code
         */
        @JsonProperty("PostalCode")
        @SearchableField(isFilterable = true, isSortable = true, isFacetable = true)
        public String postalCode;
    
        /**
         * Country
         */
        @JsonProperty("Country")
        @SearchableField(isFilterable = true, isSortable = true, isFacetable = true)
        public String country;
    }
    
  4. 새 콘솔 애플리케이션을 실행합니다.

    javac Address.java App.java Hotel.java -cp ".;target\dependency\*"
    java -cp ".;target\dependency\*" App
    

코드 설명

이전 섹션에서는 새 콘솔 애플리케이션을 만들고 Azure AI Search 클라이언트 라이브러리를 설치했습니다. 검색 인덱스를 만들고, 문서로 로드하고, 쿼리를 실행하는 코드를 추가했습니다. 콘솔에서 결과를 확인하기 위해 프로그램을 실행했습니다.

이 섹션에서는 콘솔 애플리케이션에 추가한 코드에 대해 설명합니다.

검색 클라이언트 만들기

App.java 두 개의 클라이언트를 만들었습니다.

  • SearchIndexClient는 인덱스를 만듭니다.
  • SearchClient는 기존 인덱스를 로드하고 쿼리합니다.

두 클라이언트 모두 이전에 리소스 정보 섹션에 설명된 검색 서비스 엔드포인트 및 자격 증명이 필요합니다.

이 빠른 시작의 샘플 코드는 권장되는 키 없는 인증에 Microsoft Entra ID를 사용합니다. API 키를 사용하려는 경우 개체를 DefaultAzureCredential 개체로 AzureKeyCredential 바꿀 수 있습니다.

String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
public static void main(String[] args) {
    // Your search service endpoint
    String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";

    // Use the recommended keyless credential instead of the AzureKeyCredential credential.
    DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
    //AzureKeyCredential credential = new AzureKeyCredential("Your search service admin key");

    // Create a SearchIndexClient to send create/delete index commands
    SearchIndexClient searchIndexClient = new SearchIndexClientBuilder()
        .endpoint(searchServiceEndpoint)
        .credential(credential)
        .buildClient();
    
    // Create a SearchClient to load and query documents
    String indexName = "hotels-quickstart-java";
    SearchClient searchClient = new SearchClientBuilder()
        .endpoint(searchServiceEndpoint)
        .credential(credential)
        .indexName(indexName)
        .buildClient();

    // Create Search Index for Hotel model
    searchIndexClient.createOrUpdateIndex(
        new SearchIndex(indexName, SearchIndexClient.buildSearchFields(Hotel.class, null))
        .setSuggesters(new SearchSuggester("sg", Arrays.asList("HotelName"))));

    // REDACTED FOR BREVITY . . . 
}

인덱스 만들기

이 빠른 시작에서는 호텔 데이터로 로드하고 쿼리를 실행하는 호텔 인덱스를 작성합니다. 이 단계에서는 인덱스 필드를 정의합니다. 각 필드 정의에는 이름, 데이터 형식 및 필드가 사용되는 방식을 결정하는 특성이 포함됩니다.

이 예제에서는 단순성과 가독성을 위해 Azure.Search.Documents 라이브러리의 동기 메서드를 사용합니다. 그러나 프로덕션 시나리오의 경우 비동기 메서드를 사용하여 앱의 확장성 및 응답성을 유지해야 합니다. 예를 들어 CreateIndex 대신 CreateIndexAsync를 사용해야 합니다.

구조 정의

Hotel.java 및 Address.java 두 개의 도우미 클래스를 만들어 호텔 문서의 구조와 주소를 정의했습니다. Hotel 클래스에는 호텔 ID, 이름, 설명, 범주, 태그, 주차, 리노베이션 날짜, 등급 및 주소에 대한 필드가 포함되어 있습니다. 주소 클래스에는 주소, 도시, 시/도, 우편 번호 및 국가/지역에 대한 필드가 포함됩니다.

Azure.Search.Documents 클라이언트 라이브러리에서 SearchableFieldSimpleField를 사용하여 필드 정의를 간소화할 수 있습니다.

  • SimpleField 는 모든 데이터 형식일 수 있고, 항상 검색할 수 없으며(전체 텍스트 검색 쿼리에서는 무시됨) 검색할 수 있습니다(숨겨지지 않음). 다른 특성은 기본적으로 꺼져 있지만 사용하도록 설정할 수 있습니다. 필터, 패싯 또는 점수 매기기 프로필에만 사용되는 문서 ID 또는 필드에는 SimpleField를 사용할 수 있습니다. 그렇다면 문서 ID에 대한 IsKey = true와 같이 시나리오에 필요한 특성을 적용해야 합니다.
  • SearchableField는 문자열이어야 하며, 항상 검색할 수 있고 조회할 수 있습니다. 다른 특성은 기본적으로 꺼져 있지만 사용하도록 설정할 수 있습니다. 이 필드 형식은 검색할 수 있으므로 동의어와 분석기 속성의 전체 보충을 지원합니다.

기본 SearchField API를 사용하든지 도우미 모델 중 하나를 사용하든지 간에 필터, 패싯 및 정렬 특성을 명시적으로 사용하도록 설정해야 합니다. 예를 들어 isFilterable이전 isSortable샘플과 isFacetable 같이 명시적으로 특성을 지정해야 합니다.

검색 인덱스 만들기

에서 App.java메서드에서 개체를 SearchIndexmain 만든 다음, 메서드를 createOrUpdateIndex 호출하여 검색 서비스에서 인덱스 만들기를 합니다. 인덱스에는 지정된 필드에서 자동 완성을 사용하도록 설정하는 SearchSuggester도 포함되어 있습니다.

// Create Search Index for Hotel model
searchIndexClient.createOrUpdateIndex(
    new SearchIndex(indexName, SearchIndexClient.buildSearchFields(Hotel.class, null))
    .setSuggesters(new SearchSuggester("sg", Arrays.asList("HotelName"))));

문서 로드

Azure AI 검색은 서비스에 저장된 콘텐츠를 검색합니다. 이 단계에서는 만든 호텔 인덱스 준수 JSON 문서를 로드합니다.

Azure AI 검색에서 검색 문서는 인덱싱에 대한 입력과 쿼리의 출력 모두에 해당하는 데이터 구조입니다. 외부 데이터 소스에서 가져온, 문서 입력은 데이터베이스의 행, Blob Storage의 Blob 또는 디스크의 JSON 문서일 수 있습니다. 이 예에서는 손쉬운 방법을 사용하여 4개 호텔에 대한 JSON 문서를 코드 자체에 포함합니다.

문서를 업로드할 때 IndexDocumentsBatch 개체를 사용해야 합니다. IndexDocumentsBatch 개체에는 IndexActions 컬렉션이 포함되며 컬렉션마다 수행할 작업(upload, merge, delete 및 mergeOrUpload)을 Azure AI 검색에 알려주는 속성과 문서가 포함됩니다.

에서 App.java문서 및 인덱스 작업을 만든 다음 에 전달합니다 IndexDocumentsBatch. 다음 문서는 호텔 클래스에서 정의한 대로 호텔 빠른 시작 인덱스(호텔 빠른 시작 인덱스)를 준수합니다.

private static void uploadDocuments(SearchClient searchClient)
{
    var hotelList = new ArrayList<Hotel>();

    var hotel = new Hotel();
    hotel.hotelId = "1";
    hotel.hotelName = "Stay-Kay City Hotel";
    hotel.description = "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.",
    hotel.category = "Boutique";
    hotel.tags = new String[] { "view", "air conditioning", "concierge" };
    hotel.parkingIncluded = false;
    hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(2022, 1, 18), LocalTime.of(0, 0)), ZoneOffset.UTC);
    hotel.rating = 3.6;
    hotel.address = new Address();
    hotel.address.streetAddress = "677 5th Ave";
    hotel.address.city = "New York";
    hotel.address.stateProvince = "NY";
    hotel.address.postalCode = "10022";
    hotel.address.country = "USA";
    hotelList.add(hotel);
    
    // REDACTED FOR BREVITY

    var batch = new IndexDocumentsBatch<Hotel>();
    batch.addMergeOrUploadActions(hotelList);
    try
    {
        searchClient.indexDocuments(batch);
    }
    catch (Exception e)
    {
        e.printStackTrace();
        // If for some reason any documents are dropped during indexing, you can compensate by delaying and
        // retrying. This simple demo just logs failure and continues
        System.err.println("Failed to index some of the documents");
    }
}

IndexDocumentsBatch 개체를 초기화하면 개체에서 SearchClient를 호출하여 인덱스로 보낼 수 있습니다.

SearchClient를 main()사용하여 문서를 로드하지만 작업에는 일반적으로 SearchIndexClient와 연결된 서비스에 대한 관리자 권한도 필요합니다. 이 작업을 설정하는 한 가지 방법은 SearchClient를 통해 SearchIndexClient 가져오는 것입니다(searchIndexClient 이 예제에서는).

uploadDocuments(searchClient);

모든 명령을 순차적으로 실행하는 콘솔 앱이 있으므로 인덱싱과 쿼리 사이에 2초 대기 시간을 추가합니다.

// Wait 2 seconds for indexing to complete before starting queries (for demo and console-app purposes only)
System.out.println("Waiting for indexing...\n");
try
{
    Thread.sleep(2000);
}
catch (InterruptedException e)
{
}

2초 지연은 비동기식 인덱싱을 보정합니다. 따라서 쿼리가 실행되기 전에 모든 문서를 인덱싱할 수 있습니다. 지연 시 코딩은 일반적으로 데모, 테스트, 샘플 애플리케이션에서만 필요합니다.

인덱스 검색

첫 번째 문서의 인덱싱이 완료되는 즉시 쿼리 결과를 얻을 수 있지만 인덱스에 대한 실제 테스트는 모든 문서의 인덱싱이 완료될 때까지 기다려야 합니다.

이 섹션에서는 쿼리 논리와 결과의 두 가지 기능을 추가합니다. 쿼리에는 Search 메서드를 사용합니다. 이 메서드는 검색 텍스트(쿼리 문자열)뿐 아니라 다른 옵션을 사용합니다.

에서 App.java메서드는 WriteDocuments 검색 결과를 콘솔에 출력합니다.

// Write search results to console
private static void WriteSearchResults(SearchPagedIterable searchResults)
{
    searchResults.iterator().forEachRemaining(result ->
    {
        Hotel hotel = result.getDocument(Hotel.class);
        System.out.println(hotel);
    });

    System.out.println();
}

// Write autocomplete results to console
private static void WriteAutocompleteResults(AutocompletePagedIterable autocompleteResults)
{
    autocompleteResults.iterator().forEachRemaining(result ->
    {
        String text = result.getText();
        System.out.println(text);
    });

    System.out.println();
}

쿼리 예제 1

메서드는 RunQueries 쿼리를 실행하고 결과를 반환합니다. 결과는 Hotel 개체입니다. 이 샘플에서는 메서드 서명과 첫 번째 쿼리를 보여줍니다. 이 쿼리는 문서에서 선택한 필드를 사용하여 결과를 작성할 수 있도록 하는 Select 매개 변수를 보여 줍니다.

// Run queries, use WriteDocuments to print output
private static void RunQueries(SearchClient searchClient)
{
    // Query 1
    System.out.println("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n");

    SearchOptions options = new SearchOptions();
    options.setIncludeTotalCount(true);
    options.setFilter("");
    options.setOrderBy("");
    options.setSelect("HotelId", "HotelName", "Address/City");

    WriteSearchResults(searchClient.search("*", options, Context.NONE));
}

쿼리 예제 2

두 번째 쿼리에서 용어를 검색하고 등급이 4보다 큰 문서를 선택하는 필터를 추가한 다음 등급별로 내림차순으로 정렬합니다. 필터는 인덱스의 isFilterable 필드를 통해 평가되는 부울 식입니다. 필터는 포함 또는 제외 값을 쿼리합니다. 따라서 필터 쿼리와 관련된 관련성 점수가 없습니다.

// Query 2
System.out.println("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n");

options = new SearchOptions();
options.setFilter("Rating gt 4");
options.setOrderBy("Rating desc");
options.setSelect("HotelId", "HotelName", "Rating");

WriteSearchResults(searchClient.search("hotels", options, Context.NONE));

쿼리 예제 3

세 번째 쿼리는 전체 텍스트 검색 작업의 범위를 특정 필드로 지정하는 데 사용되는 searchFields를 보여 줍니다.

// Query 3
System.out.println("Query #3: Limit search to specific fields (pool in Tags field)...\n");

options = new SearchOptions();
options.setSearchFields("Tags");

options.setSelect("HotelId", "HotelName", "Tags");

WriteSearchResults(searchClient.search("pool", options, Context.NONE));

쿼리 예제 4

네 번째 쿼리는 facets패싯 탐색 구조를 구성하는 데 사용할 수 있는 방법을 보여 줍니다.

// Query 4
System.out.println("Query #4: Facet on 'Category'...\n");

options = new SearchOptions();
options.setFilter("");
options.setFacets("Category");
options.setSelect("HotelId", "HotelName", "Category");

WriteSearchResults(searchClient.search("*", options, Context.NONE));

쿼리 예제 5

다섯 번째 쿼리에서 특정 문서를 반환합니다.

// Query 5
System.out.println("Query #5: Look up a specific document...\n");

Hotel lookupResponse = searchClient.getDocument("3", Hotel.class);
System.out.println(lookupResponse.hotelId);
System.out.println();

쿼리 예제 6

마지막 쿼리는 자동 완성 구문을 보여 줍니다. 인덱스에 정의한 제안기에서 두 개의 가능한 일치 항목으로 확인되는 부분 사용자 입력 sourceFields 을 시뮬레이션합니다.

// Query 6
System.out.println("Query #6: Call Autocomplete on HotelName that starts with 's'...\n");

WriteAutocompleteResults(searchClient.autocomplete("s", "sg"));

쿼리 요약

이전 쿼리는 전체 텍스트 검색, 필터 및 자동 완성과 같은 쿼리에서 용어를 일치시키는 여러 방법을 보여줍니다.

전체 텍스트 검색 및 필터는 SearchClient.search 메서드를 사용하여 수행됩니다. 검색 쿼리는 searchText 문자열로 전달할 수 있는 반면, 필터 식은 filter 클래스의 속성으로 전달할 수 있습니다. 검색하지 않고 필터링하려면 searchText 메서드의 search 매개 변수에 대한 "*"를 전달합니다. 필터링하지 않고 검색하려면 filter 속성을 설정하지 않고 그대로 두거나 SearchOptions 인스턴스에 전달하지 않아야 합니다.

이 빠른 시작에서는 Azure.Search.Documents 클라이언트 라이브러리를 사용하여 전체 텍스트 검색에 대한 샘플 데이터를 사용하여 검색 인덱스 만들기, 로드 및 쿼리를 수행합니다. 전체 텍스트 검색은 인덱싱 및 쿼리에 Apache Lucene을 사용하고 BM25 순위 지정 알고리즘을 사용하여 결과를 채점합니다.

이 빠른 시작에서는 호텔 4개에 대한 데이터가 포함된 작은 호텔 빠른 시작 인덱스를 만들고 쿼리합니다.

필수 조건

Microsoft Entra ID 필수 구성 요소

Microsoft Entra ID를 사용하는 권장 키 없는 인증의 경우 다음을 수행해야 합니다.

  • Microsoft Entra ID를 사용하여 키 없는 인증에 사용되는 Azure CLI 를 설치합니다.
  • 사용자 계정에 역할과 Search Service Contributor 역할을 모두 Search Index Data Contributor 할당합니다. Azure Portal의 액세스 제어(IAM)>역할을 할당할 수 있습니다. 자세한 내용은 역할을 사용하여 Azure AI Search에 연결을 참조 하세요.

리소스 정보 검색

Azure AI Search 서비스를 사용하여 애플리케이션을 인증하려면 다음 정보를 검색해야 합니다.

변수 이름
SEARCH_API_ENDPOINT 이 값은 Azure Portal에서 찾을 수 있습니다. 검색 서비스를 선택한 다음 왼쪽 메뉴에서 개요를 선택합니다. EssentialsURL 값은 필요한 엔드포인트입니다. 엔드포인트의 예는 다음과 같습니다. https://mydemo.search.windows.net

키 없는 인증 및 환경 변수 설정에 대해 자세히 알아봅니다.

설정

  1. 애플리케이션을 포함할 새 폴더 full-text-quickstart 를 만들고 다음 명령을 사용하여 해당 폴더에서 Visual Studio Code를 엽니다.

    mkdir full-text-quickstart && cd full-text-quickstart
    
  2. 다음 명령을 사용하여 package.json 만듭니다.

    npm init -y
    
  3. 다음을 사용하여 JavaScript용 Azure AI Search 클라이언트 라이브러리(Azure.Search.Documents)를 설치합니다.

    npm install @azure/search-documents
    
  4. 권장되는 암호 없는 인증의 경우 다음을 사용하여 Azure ID 클라이언트 라이브러리를 설치합니다.

    npm install @azure/identity
    

검색 인덱스 만들기, 로드 및 쿼리

이전 설정 섹션에서 Azure AI Search 클라이언트 라이브러리 및 기타 종속성을 설치했습니다.

이 섹션에서는 검색 인덱스를 만들고, 문서로 로드하고, 쿼리를 실행하는 코드를 추가합니다. 프로그램을 실행하여 콘솔에서 결과를 확인합니다. 코드에 대한 자세한 설명은 코드 설명 섹션을 참조하세요.

이 빠른 시작의 샘플 코드는 권장되는 키 없는 인증에 Microsoft Entra ID를 사용합니다. API 키를 사용하려는 경우 개체를 DefaultAzureCredential 개체로 AzureKeyCredential 바꿀 수 있습니다.

String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
  1. index.js파일을 만들고 다음 코드를 index.js 붙여넣습니다.

    // Import from the @azure/search-documents library
    import { SearchIndexClient, odata } from "@azure/search-documents";
    // Import from the Azure Identity library
    import { DefaultAzureCredential } from "@azure/identity";
    // Importing the hotels sample data
    import hotelData from './hotels.json' assert { type: "json" };
    // Load the .env file if it exists
    import * as dotenv from "dotenv";
    dotenv.config();
    // Defining the index definition
    const indexDefinition = {
        "name": "hotels-quickstart",
        "fields": [
            {
                "name": "HotelId",
                "type": "Edm.String",
                "key": true,
                "filterable": true
            },
            {
                "name": "HotelName",
                "type": "Edm.String",
                "searchable": true,
                "filterable": false,
                "sortable": true,
                "facetable": false
            },
            {
                "name": "Description",
                "type": "Edm.String",
                "searchable": true,
                "filterable": false,
                "sortable": false,
                "facetable": false,
                "analyzerName": "en.lucene"
            },
            {
                "name": "Description_fr",
                "type": "Edm.String",
                "searchable": true,
                "filterable": false,
                "sortable": false,
                "facetable": false,
                "analyzerName": "fr.lucene"
            },
            {
                "name": "Category",
                "type": "Edm.String",
                "searchable": true,
                "filterable": true,
                "sortable": true,
                "facetable": true
            },
            {
                "name": "Tags",
                "type": "Collection(Edm.String)",
                "searchable": true,
                "filterable": true,
                "sortable": false,
                "facetable": true
            },
            {
                "name": "ParkingIncluded",
                "type": "Edm.Boolean",
                "filterable": true,
                "sortable": true,
                "facetable": true
            },
            {
                "name": "LastRenovationDate",
                "type": "Edm.DateTimeOffset",
                "filterable": true,
                "sortable": true,
                "facetable": true
            },
            {
                "name": "Rating",
                "type": "Edm.Double",
                "filterable": true,
                "sortable": true,
                "facetable": true
            },
            {
                "name": "Address",
                "type": "Edm.ComplexType",
                "fields": [
                    {
                        "name": "StreetAddress",
                        "type": "Edm.String",
                        "filterable": false,
                        "sortable": false,
                        "facetable": false,
                        "searchable": true
                    },
                    {
                        "name": "City",
                        "type": "Edm.String",
                        "searchable": true,
                        "filterable": true,
                        "sortable": true,
                        "facetable": true
                    },
                    {
                        "name": "StateProvince",
                        "type": "Edm.String",
                        "searchable": true,
                        "filterable": true,
                        "sortable": true,
                        "facetable": true
                    },
                    {
                        "name": "PostalCode",
                        "type": "Edm.String",
                        "searchable": true,
                        "filterable": true,
                        "sortable": true,
                        "facetable": true
                    },
                    {
                        "name": "Country",
                        "type": "Edm.String",
                        "searchable": true,
                        "filterable": true,
                        "sortable": true,
                        "facetable": true
                    }
                ]
            }
        ],
        "suggesters": [
            {
                "name": "sg",
                "searchMode": "analyzingInfixMatching",
                "sourceFields": [
                    "HotelName"
                ]
            }
        ]
    };
    async function main() {
        // Your search service endpoint
        const searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
        // Use the recommended keyless credential instead of the AzureKeyCredential credential.
        const credential = new DefaultAzureCredential();
        //const credential = new AzureKeyCredential(Your search service admin key);
        // Create a SearchIndexClient to send create/delete index commands
        const searchIndexClient = new SearchIndexClient(searchServiceEndpoint, credential);
        // Creating a search client to upload documents and issue queries
        const indexName = "hotels-quickstart";
        const searchClient = searchIndexClient.getSearchClient(indexName);
        console.log('Checking if index exists...');
        await deleteIndexIfExists(searchIndexClient, indexName);
        console.log('Creating index...');
        let index = await searchIndexClient.createIndex(indexDefinition);
        console.log(`Index named ${index.name} has been created.`);
        console.log('Uploading documents...');
        let indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotelData['value']);
        console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)} `);
        // waiting one second for indexing to complete (for demo purposes only)
        sleep(1000);
        console.log('Querying the index...');
        console.log();
        await sendQueries(searchClient);
    }
    async function deleteIndexIfExists(searchIndexClient, indexName) {
        try {
            await searchIndexClient.deleteIndex(indexName);
            console.log('Deleting index...');
        }
        catch {
            console.log('Index does not exist yet.');
        }
    }
    async function sendQueries(searchClient) {
        // Query 1
        console.log('Query #1 - search everything:');
        let searchOptions = {
            includeTotalCount: true,
            select: ["HotelId", "HotelName", "Rating"]
        };
        let searchResults = await searchClient.search("*", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log(`Result count: ${searchResults.count}`);
        console.log();
        // Query 2
        console.log('Query #2 - search with filter, orderBy, and select:');
        let state = 'FL';
        searchOptions = {
            filter: odata `Address/StateProvince eq ${state}`,
            orderBy: ["Rating desc"],
            select: ["HotelId", "HotelName", "Rating"]
        };
        searchResults = await searchClient.search("wifi", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log();
        // Query 3
        console.log('Query #3 - limit searchFields:');
        searchOptions = {
            select: ["HotelId", "HotelName", "Rating"],
            searchFields: ["HotelName"]
        };
        searchResults = await searchClient.search("sublime palace", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log();
        // Query 4
        console.log('Query #4 - limit searchFields and use facets:');
        searchOptions = {
            facets: ["Category"],
            select: ["HotelId", "HotelName", "Rating"],
            searchFields: ["HotelName"]
        };
        searchResults = await searchClient.search("*", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log();
        // Query 5
        console.log('Query #5 - Lookup document:');
        let documentResult = await searchClient.getDocument('3');
        console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`);
        console.log();
    }
    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    main().catch((err) => {
        console.error("The sample encountered an error:", err);
    });
    
  2. hotels.json 파일을 만들고 다음 코드를 hotels.json 붙여넣습니다.

    {
        "value": [
            {
                "HotelId": "1",
                "HotelName": "Stay-Kay City Hotel",
                "Description": "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.",
                "Category": "Boutique",
                "Tags": ["view", "air conditioning", "concierge"],
                "ParkingIncluded": false,
                "LastRenovationDate": "2022-01-18T00:00:00Z",
                "Rating": 3.6,
                "Address": {
                    "StreetAddress": "677 5th Ave",
                    "City": "New York",
                    "StateProvince": "NY",
                    "PostalCode": "10022"
                }
            },
            {
                "HotelId": "2",
                "HotelName": "Old Century Hotel",
                "Description": "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts. The hotel also regularly hosts events like wine tastings, beer dinners, and live music.",
                "Category": "Boutique",
                "Tags": ["pool", "free wifi", "concierge"],
                "ParkingIncluded": "false",
                "LastRenovationDate": "2019-02-18T00:00:00Z",
                "Rating": 3.6,
                "Address": {
                    "StreetAddress": "140 University Town Center Dr",
                    "City": "Sarasota",
                    "StateProvince": "FL",
                    "PostalCode": "34243"
                }
            },
            {
                "HotelId": "3",
                "HotelName": "Gastronomic Landscape Hotel",
                "Description": "The Gastronomic Hotel stands out for its culinary excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services.",
                "Category": "Suite",
                "Tags": ["restaurant", "bar", "continental breakfast"],
                "ParkingIncluded": "true",
                "LastRenovationDate": "2015-09-20T00:00:00Z",
                "Rating": 4.8,
                "Address": {
                    "StreetAddress": "3393 Peachtree Rd",
                    "City": "Atlanta",
                    "StateProvince": "GA",
                    "PostalCode": "30326"
                }
            },
            {
                "HotelId": "4",
                "HotelName": "Sublime Palace Hotel",
                "Description": "Sublime Palace Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 19th century resort, updated for every modern convenience.",
                "Category": "Boutique",
                "Tags": ["concierge", "view", "air conditioning"],
                "ParkingIncluded": true,
                "LastRenovationDate": "2020-02-06T00:00:00Z",
                "Rating": 4.6,
                "Address": {
                    "StreetAddress": "7400 San Pedro Ave",
                    "City": "San Antonio",
                    "StateProvince": "TX",
                    "PostalCode": "78216"
                }
            }
        ]
    }
    
  3. hotels_quickstart_index.json 파일을 만들고 다음 코드를 hotels_quickstart_index.json 붙여넣습니다.

    {
    	"name": "hotels-quickstart",
    	"fields": [
    		{
    			"name": "HotelId",
    			"type": "Edm.String",
    			"key": true,
    			"filterable": true
    		},
    		{
    			"name": "HotelName",
    			"type": "Edm.String",
    			"searchable": true,
    			"filterable": false,
    			"sortable": true,
    			"facetable": false
    		},
    		{
    			"name": "Description",
    			"type": "Edm.String",
    			"searchable": true,
    			"filterable": false,
    			"sortable": false,
    			"facetable": false,
    			"analyzerName": "en.lucene"
    		},
    		{
    			"name": "Category",
    			"type": "Edm.String",
    			"searchable": true,
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "Tags",
    			"type": "Collection(Edm.String)",
    			"searchable": true,
    			"filterable": true,
    			"sortable": false,
    			"facetable": true
    		},
    		{
    			"name": "ParkingIncluded",
    			"type": "Edm.Boolean",
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "LastRenovationDate",
    			"type": "Edm.DateTimeOffset",
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "Rating",
    			"type": "Edm.Double",
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "Address",
    			"type": "Edm.ComplexType",
    			"fields": [
    				{
    					"name": "StreetAddress",
    					"type": "Edm.String",
    					"filterable": false,
    					"sortable": false,
    					"facetable": false,
    					"searchable": true
    				},
    				{
    					"name": "City",
    					"type": "Edm.String",
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				},
    				{
    					"name": "StateProvince",
    					"type": "Edm.String",
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				},
    				{
    					"name": "PostalCode",
    					"type": "Edm.String",
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				},
    				{
    					"name": "Country",
    					"type": "Edm.String",
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				}
    			]
    		}
    	],
    	"suggesters": [
    		{
    			"name": "sg",
    			"searchMode": "analyzingInfixMatching",
    			"sourceFields": [
    				"HotelName"
    			]
    		}
    	]
    }
    
  4. 다음 명령을 사용하여 Azure에 로그인합니다.

    az login
    
  5. 다음 명령을 사용하여 JavaScript 코드를 실행합니다.

    node index.js
    

코드 설명

인덱스 만들기

hotels_quickstart_index.json 파일은 다음 단계에서 로드하는 문서에서 Azure AI Search가 작동하는 방식을 정의합니다. 각 필드는 a name 로 식별되며 지정된 type필드가 있습니다. 또한 각 필드에는 Azure AI 검색에서 필드를 검색, 필터링 및 정렬하고 패싯을 수행할 수 있는지 여부를 지정하는 일련의 인덱스 특성도 있습니다. 대부분의 필드는 단순 데이터 형식이지만 AddressType과 같은 일부 형식은 인덱스에서 다양한 데이터 구조를 만들 수 있게 해주는 복합 형식입니다. 인덱스 만들기(REST)에 설명된 지원되는 데이터 형식 및 인덱스 특성에 대해 자세히 알아볼 수 있습니다.

인덱스 정의가 있으면 main 함수에서 인덱스 정의에 액세스할 수 있도록 index.js의 위쪽에 있는 hotels_quickstart_index.json을 가져와야 합니다.

const indexDefinition = require('./hotels_quickstart_index.json');

그런 다음, main 함수 내에서 Azure AI 검색의 인덱스를 만들고 관리하는 데 사용되는 SearchIndexClient를 만듭니다.

const indexClient = new SearchIndexClient(endpoint, new AzureKeyCredential(apiKey));

다음으로, 인덱스가 이미 있으면 이를 삭제해야 합니다. 이 작업은 테스트/데모 코드에 대한 일반적인 사례입니다.

이 작업은 인덱스 삭제를 시도하는 간단한 함수를 정의하여 수행합니다.

async function deleteIndexIfExists(indexClient, indexName) {
    try {
        await indexClient.deleteIndex(indexName);
        console.log('Deleting index...');
    } catch {
        console.log('Index does not exist yet.');
    }
}

이 함수를 실행하기 위해 인덱스 정의에서 인덱스 이름을 추출하고 indexNameindexClient와 함께 deleteIndexIfExists() 함수에 전달합니다.

const indexName = indexDefinition["name"];

console.log('Checking if index exists...');
await deleteIndexIfExists(indexClient, indexName);

그러면 createIndex() 메서드를 사용하여 인덱스를 만들 준비가 되었습니다.

console.log('Creating index...');
let index = await indexClient.createIndex(indexDefinition);

console.log(`Index named ${index.name} has been created.`);

문서 로드

Azure AI 검색에서 문서는 인덱싱에 대한 입력과 쿼리의 출력 모두에 해당하는 데이터 구조입니다. 이러한 데이터를 인덱스에 푸시하거나 인덱서를 사용할 수 있습니다. 이 경우 프로그래밍 방식으로 문서를 인덱스로 푸시합니다.

문서 입력은 데이터베이스의 행, Blob Storage의 Blob 또는 이 샘플처럼 디스크의 JSON 문서일 수 있습니다. indexDefinition또한 주 함수에서 데이터에 액세스할 수 있도록 index.jshotels.json가져와야 합니다.

const hotelData = require('./hotels.json');

데이터를 검색 인덱스로 인덱싱하려면 이제 SearchClient를 만들어야 합니다. SearchIndexClient는 인덱스를 만들고 관리하는 데 사용되지만, SearchClient는 문서를 업로드하고 인덱스를 쿼리하는 데 사용됩니다.

SearchClient를 만드는 방법은 두 가지입니다. 첫 번째 옵션은 SearchClient를 처음부터 만드는 것입니다.

 const searchClient = new SearchClient(endpoint, indexName, new AzureKeyCredential(apiKey));

또는 getSearchClient()SearchIndexClient 메서드를 사용하여 SearchClient를 만들 수 있습니다.

const searchClient = indexClient.getSearchClient(indexName);

이제 클라이언트가 정의되었으므로 문서를 검색 인덱스에 업로드합니다. 이 경우 동일한 키를 가진 문서가 이미 있는 경우 문서를 업로드하거나 기존 문서와 병합하는 메서드를 사용합니다 mergeOrUploadDocuments() .

console.log('Uploading documents...');
let indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotelData['value']);

console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)}`);

인덱스 검색

인덱스가 만들어지고 문서가 업로드되면 쿼리를 인덱스에 보낼 준비가 되었습니다. 이 섹션에서는 5개의 다른 쿼리를 검색 인덱스에 보내 사용 가능한 다양한 쿼리 기능을 보여 줍니다.

쿼리는 다음과 같이 main 함수에서 호출하는 함수로 작성 sendQueries() 됩니다.

await sendQueries(searchClient);

쿼리는 search()searchClient 메서드를 사용하여 보냅니다. 첫 번째 매개 변수는 검색 텍스트이고, 두 번째 매개 변수는 검색 옵션을 지정합니다.

쿼리 예제 1

첫 번째 쿼리는 모든 항목을 검색하는 것과 동일한 *를 검색하고 인덱스에서 세 개의 필드를 선택합니다. 불필요한 데이터를 다시 끌어오면 쿼리의 대기 시간이 늘어날 수 있으므로 필요한 필드만 선택(select)하는 것이 좋습니다.

searchOptions 또한 이 쿼리의 includeTotalCount 경우 일치하는 결과 수를 반환하는 <a0/>로 설정되었습니다.

async function sendQueries(searchClient) {
    console.log('Query #1 - search everything:');
    let searchOptions = {
        includeTotalCount: true,
        select: ["HotelId", "HotelName", "Rating"]
    };

    let searchResults = await searchClient.search("*", searchOptions);
    for await (const result of searchResults.results) {
        console.log(`${JSON.stringify(result.document)}`);
    }
    console.log(`Result count: ${searchResults.count}`);

    // remaining queries go here
}

아래에 설명된 나머지 쿼리도 sendQueries() 함수에 추가해야 합니다. 이 경우 읽기 쉽도록 구분됩니다.

쿼리 예제 2

다음 쿼리에서는 "wifi"라는 검색 용어를 지정하고, 상태가 'FL'인 결과만 반환하는 필터도 포함합니다. 결과는 Hotel의 Rating을 기준으로 정렬됩니다.

console.log('Query #2 - Search with filter, orderBy, and select:');
let state = 'FL';
searchOptions = {
    filter: odata`Address/StateProvince eq ${state}`,
    orderBy: ["Rating desc"],
    select: ["HotelId", "HotelName", "Rating"]
};

searchResults = await searchClient.search("wifi", searchOptions);
for await (const result of searchResults.results) {
    console.log(`${JSON.stringify(result.document)}`);
}

쿼리 예제 3

다음으로, searchFields 매개 변수를 사용하여 검색을 검색 가능한 단일 필드로 제한합니다. 이 접근 방식은 특정 필드의 일치에만 관심이 있는 경우 쿼리를 더 효율적으로 만들 수 있는 좋은 옵션입니다.

console.log('Query #3 - Limit searchFields:');
searchOptions = {
    select: ["HotelId", "HotelName", "Rating"],
    searchFields: ["HotelName"]
};

searchResults = await searchClient.search("Sublime Palace", searchOptions);
for await (const result of searchResults.results) {
    console.log(`${JSON.stringify(result.document)}`);
}
console.log();

쿼리 예제 4

쿼리에 포함하는 또 다른 일반적인 옵션은 facets입니다. 패싯을 사용하면 사용자가 필터링할 수 있는 값을 쉽게 알 수 있도록 필터를 UI에 빌드할 수 있습니다.

console.log('Query #4 - Use facets:');
searchOptions = {
    facets: ["Category"],
    select: ["HotelId", "HotelName", "Rating"],
    searchFields: ["HotelName"]
};

searchResults = await searchClient.search("*", searchOptions);
for await (const result of searchResults.results) {
    console.log(`${JSON.stringify(result.document)}`);
}

쿼리 예제 5

최종 쿼리는 getDocument()searchClient 메서드를 사용합니다. 이렇게 하면 해당 키를 통해 문서를 효율적으로 검색할 수 있습니다.

console.log('Query #5 - Lookup document:');
let documentResult = await searchClient.getDocument(key='3')
console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`)

쿼리 요약

이전 쿼리는 전체 텍스트 검색, 필터 및 자동 완성과 같은 쿼리에서 용어를 일치시키는 여러 방법을 보여줍니다.

전체 텍스트 검색 및 필터는 메서드를 searchClient.search 사용하여 수행됩니다. 검색 쿼리는 문자열에 searchText 전달될 수 있지만 필터 식은 클래스의 filter 속성에 SearchOptions 전달될 수 있습니다. 검색하지 않고 필터링하려면 searchText 메서드의 search 매개 변수에 대한 "*"를 전달합니다. 필터링하지 않고 검색하려면 filter 속성을 설정하지 않고 그대로 두거나 SearchOptions 인스턴스에 전달하지 않아야 합니다.

이 빠른 시작에서는 Azure.Search.Documents 클라이언트 라이브러리를 사용하여 전체 텍스트 검색에 대한 샘플 데이터를 사용하여 검색 인덱스 만들기, 로드 및 쿼리를 수행합니다. 전체 텍스트 검색은 인덱싱 및 쿼리에 Apache Lucene을 사용하고 BM25 순위 지정 알고리즘을 사용하여 결과를 채점합니다.

이 빠른 시작에서는 호텔 4개에 대한 데이터가 포함된 작은 호텔 빠른 시작 인덱스를 만들고 쿼리합니다.

완성된 전자 필기장을 다운로드하고 실행할 수 있습니다.

필수 조건

Microsoft Entra ID 필수 구성 요소

Microsoft Entra ID를 사용하는 권장 키 없는 인증의 경우 다음을 수행해야 합니다.

  • Microsoft Entra ID를 사용하여 키 없는 인증에 사용되는 Azure CLI 를 설치합니다.
  • 사용자 계정에 역할과 Search Service Contributor 역할을 모두 Search Index Data Contributor 할당합니다. Azure Portal의 액세스 제어(IAM)>역할을 할당할 수 있습니다. 자세한 내용은 역할을 사용하여 Azure AI Search에 연결을 참조 하세요.

리소스 정보 검색

Azure AI Search 서비스를 사용하여 애플리케이션을 인증하려면 다음 정보를 검색해야 합니다.

변수 이름
SEARCH_API_ENDPOINT 이 값은 Azure Portal에서 찾을 수 있습니다. 검색 서비스를 선택한 다음 왼쪽 메뉴에서 개요를 선택합니다. EssentialsURL 값은 필요한 엔드포인트입니다. 엔드포인트의 예는 다음과 같습니다. https://mydemo.search.windows.net

키 없는 인증 및 환경 변수 설정에 대해 자세히 알아봅니다.

환경 설정

Jupyter Notebook에서 샘플 코드를 실행합니다. 따라서 Jupyter Notebook을 실행하도록 환경을 설정해야 합니다.

  1. GitHub에서 샘플 Notebook을 다운로드하거나 복사합니다.

  2. Visual Studio Code에서 Notebook을 엽니다.

  3. 이 자습서에 필요한 패키지를 설치하는 데 사용할 새 Python 환경을 만듭니다.

    중요합니다

    글로벌 Python 설치에 패키지를 설치하지 마세요. Python 패키지를 설치할 때 항상 가상 환경 또는 conda 환경을 사용해야 합니다. 그렇지 않으면 Python의 전역 설치가 중단될 수 있습니다.

    py -3 -m venv .venv
    .venv\scripts\activate
    

    설정하는 데 1분 정도 걸릴 수 있습니다. 문제가 발생하면 VS Code의 Python 환경을 참조하세요.

  4. Jupyter Notebook이 아직 없는 경우 Jupyter Notebook 및 Jupyter Notebook용 IPython 커널을 설치합니다.

    pip install jupyter
    pip install ipykernel
    python -m ipykernel install --user --name=.venv
    
  5. Notebook 커널을 선택합니다.

    1. Notebook의 오른쪽 위 모서리에서 커널 선택을 선택합니다.
    2. 목록에 표시되는 .venv 경우 선택합니다. 표시되지 않으면 다른 커널.venv

검색 인덱스 만들기, 로드 및 쿼리

이 섹션에서는 검색 인덱스를 만들고, 문서로 로드하고, 쿼리를 실행하는 코드를 추가합니다. 프로그램을 실행하여 콘솔에서 결과를 확인합니다. 코드에 대한 자세한 설명은 코드 설명 섹션을 참조하세요.

  1. 이전 섹션에서 설명한 대로 Notebook이 .venv 커널에서 열려 있는지 확인합니다.

  2. 첫 번째 코드 셀을 실행하여 azure-search-documents를 포함하여 필요한 패키지를 설치합니다.

    ! pip install azure-search-documents==11.6.0b1 --quiet
    ! pip install azure-identity --quiet
    ! pip install python-dotenv --quiet
    
  3. 인증 방법에 따라 두 번째 코드 셀의 내용을 다음 코드로 바꿉니다.

    참고 항목

    이 빠른 시작의 샘플 코드는 권장되는 키 없는 인증에 Microsoft Entra ID를 사용합니다. API 키를 사용하려는 경우 개체를 DefaultAzureCredential 개체로 AzureKeyCredential 바꿀 수 있습니다.

    from azure.core.credentials import AzureKeyCredential
    from azure.identity import DefaultAzureCredential, AzureAuthorityHosts
    
    search_endpoint: str = "https://<Put your search service NAME here>.search.windows.net/"
    authority = AzureAuthorityHosts.AZURE_PUBLIC_CLOUD
    credential = DefaultAzureCredential(authority=authority)
    
    index_name: str = "hotels-quickstart-python"
    
  4. 인덱스 코드 만들기 셀에서 다음 두 줄을 제거합니다. 자격 증명은 이미 이전 코드 셀에 설정되어 있습니다.

    from azure.core.credentials import AzureKeyCredential
    credential = AzureKeyCredential(search_api_key)
    
  5. 인덱코드 셀 만들기를 실행하여 검색 인덱스 만들기

  6. 나머지 코드 셀을 순차적으로 실행하여 문서를 로드하고 쿼리를 실행합니다.

코드 설명

인덱스 만들기

SearchIndexClient 는 Azure AI Search에 대한 인덱스를 만들고 관리하는 데 사용됩니다. 각 필드는 a name 로 식별되며 지정된 type필드는 있습니다.

또한 각 필드에는 Azure AI 검색에서 필드를 검색, 필터링 및 정렬하고 패싯을 수행할 수 있는지 여부를 지정하는 일련의 인덱스 특성도 있습니다. 대부분의 필드는 단순 데이터 형식이지만 AddressType과 같은 일부 형식은 인덱스에서 다양한 데이터 구조를 만들 수 있게 해주는 복합 형식입니다. 인덱스 만들기(REST)에 설명된 지원되는 데이터 형식 및 인덱스 특성에 대해 자세히 알아볼 수 있습니다.

문서 페이로드 만들기 및 문서 업로드

업로드 또는 병합 및 업로드와 같은 작업 유형에 대한 인덱스 작업을 사용합니다. 문서는 GitHub의 HotelsData 샘플에서 시작됩니다.

인덱스 검색

첫 번째 문서의 인덱싱이 완료되는 즉시 쿼리 결과를 얻을 수 있지만 인덱스에 대한 실제 테스트는 모든 문서의 인덱싱이 완료될 때까지 기다려야 합니다.

search.client 클래스search 메서드를 사용합니다.

Notebook의 샘플 쿼리는 다음과 같습니다.

  • 기본 쿼리: 임의의 문서의 순위가 지정되지 않은 목록(검색 점수 = 1.0)을 반환하여 빈 검색(search=*)을 실행합니다. 조건이 없으므로 모든 문서가 결과에 포함됩니다.
  • 용어 쿼리: 검색 식("wifi")에 전체 용어를 추가합니다. 이 쿼리는 select 문의 해당 필드만 결과에 포함되도록 지정합니다. 반환되는 필드를 제한하면 유선을 통해 다시 보내지는 데이터의 양이 최소화되고 검색 대기 시간이 줄어듭니다.
  • 필터링된 쿼리: 내림차순으로 정렬된 등급이 4보다 큰 호텔만 반환하는 필터 식을 추가합니다.
  • 필드 범위 지정: 범위 쿼리 실행에 특정 필드에 추가 search_fields 합니다.
  • 패싯: 검색 결과에서 찾은 양수 일치에 대한 패싯을 생성합니다. 일치하는 항목이 0개도 없습니다. 검색 결과에 wifi라는 용어가 포함되어 있지 않으면 패싯 탐색 구조에 wifi가 나타나지 않습니다.
  • 문서 조회: 키에 따라 문서를 반환합니다. 이 작업은 사용자가 검색 결과에서 항목을 선택할 때 드릴스루를 제공하려는 경우에 유용합니다.
  • 자동 완성: 사용자가 검색 상자에 입력할 때 잠재적 일치 항목을 제공합니다. 자동 완성은 제안기(sg)를 사용하여 제안기 요청에 대한 잠재적 일치 항목이 포함된 필드를 파악합니다. 이 빠른 시작에서는 이러한 필드가 Tags, Address/City, Address/Country입니다. 자동 완성을 시뮬레이션하려면 sa 문자를 부분 문자열로 전달합니다. SearchClient의 자동 완성 메서드는 잠재적으로 일치하는 용어 항목을 다시 보냅니다.

인덱스 제거

이 인덱스로 완료된 경우 코드 셀 정리를 실행하여 삭제할 수 있습니다. 불필요한 인덱스를 삭제하면 더 빠른 시작 및 자습서를 단계별로 실행하기 위한 공간이 확보됩니다.

이 빠른 시작에서는 Azure.Search.Documents 클라이언트 라이브러리를 사용하여 전체 텍스트 검색에 대한 샘플 데이터를 사용하여 검색 인덱스 만들기, 로드 및 쿼리를 수행합니다. 전체 텍스트 검색은 인덱싱 및 쿼리에 Apache Lucene을 사용하고 BM25 순위 지정 알고리즘을 사용하여 결과를 채점합니다.

이 빠른 시작에서는 호텔 4개에 대한 데이터가 포함된 작은 호텔 빠른 시작 인덱스를 만들고 쿼리합니다.

필수 조건

Microsoft Entra ID 필수 구성 요소

Microsoft Entra ID를 사용하는 권장 키 없는 인증의 경우 다음을 수행해야 합니다.

  • Microsoft Entra ID를 사용하여 키 없는 인증에 사용되는 Azure CLI 를 설치합니다.
  • 사용자 계정에 역할과 Search Service Contributor 역할을 모두 Search Index Data Contributor 할당합니다. Azure Portal의 액세스 제어(IAM)>역할을 할당할 수 있습니다. 자세한 내용은 역할을 사용하여 Azure AI Search에 연결을 참조 하세요.

리소스 정보 검색

Azure AI Search 서비스를 사용하여 애플리케이션을 인증하려면 다음 정보를 검색해야 합니다.

변수 이름
SEARCH_API_ENDPOINT 이 값은 Azure Portal에서 찾을 수 있습니다. 검색 서비스를 선택한 다음 왼쪽 메뉴에서 개요를 선택합니다. EssentialsURL 값은 필요한 엔드포인트입니다. 엔드포인트의 예는 다음과 같습니다. https://mydemo.search.windows.net

키 없는 인증 및 환경 변수 설정에 대해 자세히 알아봅니다.

설정

  1. 애플리케이션을 포함할 새 폴더 full-text-quickstart 를 만들고 다음 명령을 사용하여 해당 폴더에서 Visual Studio Code를 엽니다.

    mkdir full-text-quickstart && cd full-text-quickstart
    
  2. 다음 명령을 사용하여 package.json 만듭니다.

    npm init -y
    
  3. package.json 다음 명령을 사용하여 ECMAScript로 업데이트합니다.

    npm pkg set type=module
    
  4. 다음을 사용하여 JavaScript용 Azure AI Search 클라이언트 라이브러리(Azure.Search.Documents)를 설치합니다.

    npm install @azure/search-documents
    
  5. 권장되는 암호 없는 인증의 경우 다음을 사용하여 Azure ID 클라이언트 라이브러리를 설치합니다.

    npm install @azure/identity
    

검색 인덱스 만들기, 로드 및 쿼리

이전 설정 섹션에서 Azure AI Search 클라이언트 라이브러리 및 기타 종속성을 설치했습니다.

이 섹션에서는 검색 인덱스를 만들고, 문서로 로드하고, 쿼리를 실행하는 코드를 추가합니다. 프로그램을 실행하여 콘솔에서 결과를 확인합니다. 코드에 대한 자세한 설명은 코드 설명 섹션을 참조하세요.

이 빠른 시작의 샘플 코드는 권장되는 키 없는 인증에 Microsoft Entra ID를 사용합니다. API 키를 사용하려는 경우 개체를 DefaultAzureCredential 개체로 AzureKeyCredential 바꿀 수 있습니다.

const searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
const credential = new DefaultAzureCredential();
  1. index.ts 새 파일을 만들고 다음 코드를 index.ts 붙여넣습니다.

    // Import from the @azure/search-documents library
    import {
        SearchIndexClient,
        SearchClient,
        SearchFieldDataType,
        AzureKeyCredential,
        odata,
        SearchIndex
    } from "@azure/search-documents";
    
    // Import from the Azure Identity library
    import { DefaultAzureCredential } from "@azure/identity";
    
    // Importing the hotels sample data
    import hotelData from './hotels.json' assert { type: "json" };
    
    // Load the .env file if it exists
    import * as dotenv from "dotenv";
    dotenv.config();
    
    // Defining the index definition
    const indexDefinition: SearchIndex = {
    	"name": "hotels-quickstart",
    	"fields": [
    		{
    			"name": "HotelId",
    			"type": "Edm.String" as SearchFieldDataType,
    			"key": true,
    			"filterable": true
    		},
    		{
    			"name": "HotelName",
    			"type": "Edm.String" as SearchFieldDataType,
    			"searchable": true,
    			"filterable": false,
    			"sortable": true,
    			"facetable": false
    		},
    		{
    			"name": "Description",
    			"type": "Edm.String" as SearchFieldDataType,
    			"searchable": true,
    			"filterable": false,
    			"sortable": false,
    			"facetable": false,
    			"analyzerName": "en.lucene"
    		},
    		{
    			"name": "Category",
    			"type": "Edm.String" as SearchFieldDataType,
    			"searchable": true,
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "Tags",
    			"type": "Collection(Edm.String)",
    			"searchable": true,
    			"filterable": true,
    			"sortable": false,
    			"facetable": true
    		},
    		{
    			"name": "ParkingIncluded",
    			"type": "Edm.Boolean",
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "LastRenovationDate",
    			"type": "Edm.DateTimeOffset",
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "Rating",
    			"type": "Edm.Double",
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "Address",
    			"type": "Edm.ComplexType",
    			"fields": [
    				{
    					"name": "StreetAddress",
    					"type": "Edm.String" as SearchFieldDataType,
    					"filterable": false,
    					"sortable": false,
    					"facetable": false,
    					"searchable": true
    				},
    				{
    					"name": "City",
    					"type": "Edm.String" as SearchFieldDataType,
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				},
    				{
    					"name": "StateProvince",
    					"type": "Edm.String" as SearchFieldDataType,
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				},
    				{
    					"name": "PostalCode",
    					"type": "Edm.String" as SearchFieldDataType,
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				},
    				{
    					"name": "Country",
    					"type": "Edm.String" as SearchFieldDataType,
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				}
    			]
    		}
    	],
    	"suggesters": [
    		{
    			"name": "sg",
    			"searchMode": "analyzingInfixMatching",
    			"sourceFields": [
    				"HotelName"
    			]
    		}
    	]
    };
    
    async function main() {
    
    	// Your search service endpoint
    	const searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
    
    	// Use the recommended keyless credential instead of the AzureKeyCredential credential.
    	const credential = new DefaultAzureCredential();
    	//const credential = new AzureKeyCredential(Your search service admin key);
    
    	// Create a SearchIndexClient to send create/delete index commands
    	const searchIndexClient: SearchIndexClient = new SearchIndexClient(
    		searchServiceEndpoint,
    		credential
    	);
    
    	// Creating a search client to upload documents and issue queries
    	const indexName: string  = "hotels-quickstart";
        const searchClient: SearchClient<any> = searchIndexClient.getSearchClient(indexName);
    
        console.log('Checking if index exists...');
        await deleteIndexIfExists(searchIndexClient, indexName);
    
        console.log('Creating index...');
        let index: SearchIndex = await searchIndexClient.createIndex(indexDefinition);
        console.log(`Index named ${index.name} has been created.`);
    
        console.log('Uploading documents...');
        let indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotelData['value']);
        console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)} `);
    
        // waiting one second for indexing to complete (for demo purposes only)
        sleep(1000);
    
        console.log('Querying the index...');
        console.log();
        await sendQueries(searchClient);
    }
    
    async function deleteIndexIfExists(searchIndexClient: SearchIndexClient, indexName: string) {
        try {
            await searchIndexClient.deleteIndex(indexName);
            console.log('Deleting index...');
        } catch {
            console.log('Index does not exist yet.');
        }
    }
    
    async function sendQueries(searchClient: SearchClient<any>) {
        // Query 1
        console.log('Query #1 - search everything:');
        let searchOptions: any = {
            includeTotalCount: true,
            select: ["HotelId", "HotelName", "Rating"]
        };
    
        let searchResults = await searchClient.search("*", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log(`Result count: ${searchResults.count}`);
        console.log();
    
    
        // Query 2
        console.log('Query #2 - search with filter, orderBy, and select:');
        let state = 'FL';
        searchOptions = {
            filter: odata`Address/StateProvince eq ${state}`,
            orderBy: ["Rating desc"],
            select: ["HotelId", "HotelName", "Rating"]
        };
    
        searchResults = await searchClient.search("wifi", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log();
    
        // Query 3
        console.log('Query #3 - limit searchFields:');
        searchOptions = {
            select: ["HotelId", "HotelName", "Rating"],
            searchFields: ["HotelName"]
        };
    
        searchResults = await searchClient.search("sublime palace", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log();
    
        // Query 4
        console.log('Query #4 - limit searchFields and use facets:');
        searchOptions = {
            facets: ["Category"],
            select: ["HotelId", "HotelName", "Rating"],
            searchFields: ["HotelName"]
        };
    
        searchResults = await searchClient.search("*", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log();
    
        // Query 5
        console.log('Query #5 - Lookup document:');
        let documentResult = await searchClient.getDocument('3');
        console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`);
        console.log();
    }
    
    function sleep(ms: number) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    
    main().catch((err) => {
        console.error("The sample encountered an error:", err);
    });
    
  2. hotels.json 파일을 만들고 다음 코드를 hotels.json 붙여넣습니다.

    {
        "value": [
            {
                "HotelId": "1",
                "HotelName": "Stay-Kay City Hotel",
                "Description": "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.",
                "Category": "Boutique",
                "Tags": ["view", "air conditioning", "concierge"],
                "ParkingIncluded": false,
                "LastRenovationDate": "2022-01-18T00:00:00Z",
                "Rating": 3.6,
                "Address": {
                    "StreetAddress": "677 5th Ave",
                    "City": "New York",
                    "StateProvince": "NY",
                    "PostalCode": "10022"
                }
            },
            {
                "HotelId": "2",
                "HotelName": "Old Century Hotel",
                "Description": "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts. The hotel also regularly hosts events like wine tastings, beer dinners, and live music.",
                "Category": "Boutique",
                "Tags": ["pool", "free wifi", "concierge"],
                "ParkingIncluded": "false",
                "LastRenovationDate": "2019-02-18T00:00:00Z",
                "Rating": 3.6,
                "Address": {
                    "StreetAddress": "140 University Town Center Dr",
                    "City": "Sarasota",
                    "StateProvince": "FL",
                    "PostalCode": "34243"
                }
            },
            {
                "HotelId": "3",
                "HotelName": "Gastronomic Landscape Hotel",
                "Description": "The Gastronomic Hotel stands out for its culinary excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services.",
                "Category": "Suite",
                "Tags": ["restaurant, "bar", "continental breakfast"],
                "ParkingIncluded": "true",
                "LastRenovationDate": "2015-09-20T00:00:00Z",
                "Rating": 4.8,
                "Address": {
                    "StreetAddress": "3393 Peachtree Rd",
                    "City": "Atlanta",
                    "StateProvince": "GA",
                    "PostalCode": "30326"
                }
            },
            {
                "HotelId": "4",
                "HotelName": "Sublime Palace Hotel",
                "Description": "Sublime Palace Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 19th century resort, updated for every modern convenience.",
                "Category": "Boutique",
                "Tags": ["concierge", "view", "air conditioning"],
                "ParkingIncluded": true,
                "LastRenovationDate": "2020-02-06T00:00:00Z",
                "Rating": 4.6,
                "Address": {
                    "StreetAddress": "7400 San Pedro Ave",
                    "City": "San Antonio",
                    "StateProvince": "TX",
                    "PostalCode": "78216"
                }
            }
        ]
    }
    
  3. tsconfig.json TypeScript 코드를 변환하고 ECMAScript에 대해 다음 코드를 복사하는 파일을 만듭니다.

    {
        "compilerOptions": {
          "module": "NodeNext",
          "target": "ES2022", // Supports top-level await
          "moduleResolution": "NodeNext",
          "skipLibCheck": true, // Avoid type errors from node_modules
          "strict": true // Enable strict type-checking options
        },
        "include": ["*.ts"]
    }
    
  4. TypeScript에서 JavaScript로 변환합니다.

    tsc
    
  5. 다음 명령을 사용하여 Azure에 로그인합니다.

    az login
    
  6. 다음 명령을 사용하여 JavaScript 코드를 실행합니다.

    node index.js
    

코드 설명

인덱스 만들기

hotels_quickstart_index.json 파일을 만듭니다. 이 파일은 다음 단계에서 로드하는 문서에서 Azure AI Search가 작동하는 방식을 정의합니다. 각 필드는 a name 로 식별되며 지정된 type필드가 있습니다. 또한 각 필드에는 Azure AI 검색에서 필드를 검색, 필터링 및 정렬하고 패싯을 수행할 수 있는지 여부를 지정하는 일련의 인덱스 특성도 있습니다. 대부분의 필드는 단순 데이터 형식이지만 AddressType과 같은 일부 형식은 인덱스에서 다양한 데이터 구조를 만들 수 있게 해주는 복합 형식입니다. 인덱스 만들기(REST)에 설명된 지원되는 데이터 형식 및 인덱스 특성에 대해 자세히 알아볼 수 있습니다.

주 함수가 인덱스 정의에 액세스할 수 있도록 hotels_quickstart_index.json 가져오려고 합니다.

import indexDefinition from './hotels_quickstart_index.json';

interface HotelIndexDefinition {
    name: string;
    fields: SimpleField[] | ComplexField[];
    suggesters: SearchSuggester[];
};
const hotelIndexDefinition: HotelIndexDefinition = indexDefinition as HotelIndexDefinition;

그런 다음, main 함수 내에서 Azure AI 검색의 인덱스를 만들고 관리하는 데 사용되는 SearchIndexClient를 만듭니다.

const indexClient = new SearchIndexClient(endpoint, new AzureKeyCredential(apiKey));

다음으로, 인덱스가 이미 있으면 이를 삭제해야 합니다. 이 작업은 테스트/데모 코드에 대한 일반적인 사례입니다.

이 작업은 인덱스 삭제를 시도하는 간단한 함수를 정의하여 수행합니다.

async function deleteIndexIfExists(indexClient: SearchIndexClient, indexName: string): Promise<void> {
    try {
        await indexClient.deleteIndex(indexName);
        console.log('Deleting index...');
    } catch {
        console.log('Index does not exist yet.');
    }
}

이 함수를 실행하기 위해 인덱스 정의에서 인덱스 이름을 추출하고 indexNameindexClient와 함께 deleteIndexIfExists() 함수에 전달합니다.

// Getting the name of the index from the index definition
const indexName: string = hotelIndexDefinition.name;

console.log('Checking if index exists...');
await deleteIndexIfExists(indexClient, indexName);

그러면 createIndex() 메서드를 사용하여 인덱스를 만들 준비가 되었습니다.

console.log('Creating index...');
let index = await indexClient.createIndex(hotelIndexDefinition);

console.log(`Index named ${index.name} has been created.`);

문서 로드

Azure AI 검색에서 문서는 인덱싱에 대한 입력과 쿼리의 출력 모두에 해당하는 데이터 구조입니다. 이러한 데이터를 인덱스에 푸시하거나 인덱서를 사용할 수 있습니다. 이 경우 프로그래밍 방식으로 문서를 인덱스로 푸시합니다.

문서 입력은 데이터베이스의 행, Blob Storage의 Blob 또는 이 샘플처럼 디스크의 JSON 문서일 수 있습니다. 다음 콘텐츠를 사용하여 hotels.json을 다운로드하거나 자체 hotels.json 파일을 만들 수 있습니다.

indexDefinition을 사용하여 수행한 작업과 마찬가지로 main 함수에서 데이터에 액세스할 수 있도록 hotels.json의 위쪽에 있는 을 가져와야 합니다.

import hotelData from './hotels.json';

interface Hotel {
    HotelId: string;
    HotelName: string;
    Description: string;
    Category: string;
    Tags: string[];
    ParkingIncluded: string | boolean;
    LastRenovationDate: string;
    Rating: number;
    Address: {
        StreetAddress: string;
        City: string;
        StateProvince: string;
        PostalCode: string;
    };
};

const hotels: Hotel[] = hotelData["value"];

데이터를 검색 인덱스로 인덱싱하려면 이제 SearchClient를 만들어야 합니다. SearchIndexClient는 인덱스를 만들고 관리하는 데 사용되지만, SearchClient는 문서를 업로드하고 인덱스를 쿼리하는 데 사용됩니다.

SearchClient를 만드는 방법은 두 가지입니다. 첫 번째 옵션은 SearchClient를 처음부터 만드는 것입니다.

 const searchClient = new SearchClient<Hotel>(endpoint, indexName, new AzureKeyCredential(apiKey));

또는 getSearchClient()SearchIndexClient 메서드를 사용하여 SearchClient를 만들 수 있습니다.

const searchClient = indexClient.getSearchClient<Hotel>(indexName);

이제 클라이언트가 정의되었으므로 문서를 검색 인덱스에 업로드합니다. 이 경우 동일한 키를 가진 문서가 이미 있는 경우 문서를 업로드하거나 기존 문서와 병합하는 메서드를 사용합니다 mergeOrUploadDocuments() . 최소한 첫 번째 문서가 있으므로 작업이 성공했는지 확인합니다.

console.log("Uploading documents...");
const indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotels);

console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)}`);

tsc && node index.ts를 사용하여 프로그램을 다시 실행합니다. 1단계에서 본 것과 약간 다른 메시지 세트가 표시됩니다. 이번에는 인덱스가 있으며, 앱에서 새 인덱스를 만들어 데이터를 이 인덱스에 게시하기 전에 해당 인덱스를 삭제하라는 메시지가 표시됩니다.

다음 단계에서 쿼리를 실행하기 전에 프로그램에서 1초 동안 대기하도록 함수를 정의합니다. 이 작업은 인덱싱이 완료되고 쿼리에 대한 인덱스에서 문서를 사용할 수 있도록 하기 위해 테스트/데모 용도로만 수행됩니다.

function sleep(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
}

프로그램이 1초 동안 대기하도록 하려면 함수를 호출합니다 sleep .

sleep(1000);

인덱스 검색

인덱스가 만들어지고 문서가 업로드되면 쿼리를 인덱스에 보낼 준비가 되었습니다. 이 섹션에서는 5개의 다른 쿼리를 검색 인덱스에 보내 사용 가능한 다양한 쿼리 기능을 보여 줍니다.

쿼리는 다음과 같이 main 함수에서 호출하는 함수로 작성 sendQueries() 됩니다.

await sendQueries(searchClient);

쿼리는 search()searchClient 메서드를 사용하여 보냅니다. 첫 번째 매개 변수는 검색 텍스트이고, 두 번째 매개 변수는 검색 옵션을 지정합니다.

쿼리 예제 1

첫 번째 쿼리는 모든 항목을 검색하는 것과 동일한 *를 검색하고 인덱스에서 세 개의 필드를 선택합니다. 불필요한 데이터를 다시 끌어오면 쿼리의 대기 시간이 늘어날 수 있으므로 필요한 필드만 선택(select)하는 것이 좋습니다.

이 쿼리에 대한 searchOptions에도 includeTotalCount로 설정된 true가 있으며, 이 경우 일치하는 결과의 수를 반환합니다.

async function sendQueries(
    searchClient: SearchClient<Hotel>
): Promise<void> {

    // Query 1
    console.log('Query #1 - search everything:');
    const selectFields: SearchFieldArray<Hotel> = [
        "HotelId",
        "HotelName",
        "Rating",
    ];
    const searchOptions1 = { 
        includeTotalCount: true, 
        select: selectFields 
    };

    let searchResults = await searchClient.search("*", searchOptions1);
    for await (const result of searchResults.results) {
        console.log(`${JSON.stringify(result.document)}`);
    }
    console.log(`Result count: ${searchResults.count}`);

    // remaining queries go here
}

아래에 설명된 나머지 쿼리도 sendQueries() 함수에 추가해야 합니다. 이 경우 읽기 쉽도록 구분됩니다.

쿼리 예제 2

다음 쿼리에서는 "wifi"라는 검색 용어를 지정하고, 상태가 'FL'인 결과만 반환하는 필터도 포함합니다. 결과는 Hotel의 Rating을 기준으로 정렬됩니다.

console.log('Query #2 - search with filter, orderBy, and select:');
let state = 'FL';
const searchOptions2 = {
    filter: odata`Address/StateProvince eq ${state}`,
    orderBy: ["Rating desc"],
    select: selectFields
};
searchResults = await searchClient.search("wifi", searchOptions2);
for await (const result of searchResults.results) {
    console.log(`${JSON.stringify(result.document)}`);
}

쿼리 예제 3

다음으로, searchFields 매개 변수를 사용하여 검색을 검색 가능한 단일 필드로 제한합니다. 이 접근 방식은 특정 필드의 일치에만 관심이 있는 경우 쿼리를 더 효율적으로 만들 수 있는 좋은 옵션입니다.

console.log('Query #3 - limit searchFields:');
const searchOptions3 = {
    select: selectFields,
    searchFields: ["HotelName"] as const
};

searchResults = await searchClient.search("Sublime Palace", searchOptions3);
for await (const result of searchResults.results) {
    console.log(`${JSON.stringify(result.document)}`);
}

쿼리 예제 4

쿼리에 포함하는 또 다른 일반적인 옵션은 facets입니다. 패싯을 사용하면 UI의 결과에서 자기 주도형 드릴다운을 제공할 수 있습니다. 패싯 결과를 결과 창의 확인란으로 전환할 수 있습니다.

console.log('Query #4 - limit searchFields and use facets:');
const searchOptions4 = {
    facets: ["Category"],
    select: selectFields,
    searchFields: ["HotelName"] as const
};

searchResults = await searchClient.search("*", searchOptions4);
for await (const result of searchResults.results) {
    console.log(`${JSON.stringify(result.document)}`);
}

쿼리 예제 5

최종 쿼리는 getDocument()searchClient 메서드를 사용합니다. 이렇게 하면 해당 키를 통해 문서를 효율적으로 검색할 수 있습니다.

console.log('Query #5 - Lookup document:');
let documentResult = await searchClient.getDocument('3')
console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`)

쿼리 요약

이전 쿼리는 전체 텍스트 검색, 필터 및 자동 완성과 같은 쿼리에서 용어를 일치시키는 여러 방법을 보여줍니다.

전체 텍스트 검색 및 필터는 메서드를 searchClient.search 사용하여 수행됩니다. 검색 쿼리는 문자열에 searchText 전달될 수 있지만 필터 식은 클래스의 filter 속성에 SearchOptions 전달될 수 있습니다. 검색하지 않고 필터링하려면 searchText 메서드의 search 매개 변수에 대한 "*"를 전달합니다. 필터링하지 않고 검색하려면 filter 속성을 설정하지 않고 그대로 두거나 SearchOptions 인스턴스에 전달하지 않아야 합니다.

리소스 정리

본인 소유의 구독으로 이 모듈을 진행하고 있는 경우에는 프로젝트가 끝날 때 여기에서 만든 리소스가 계속 필요한지 확인하는 것이 좋습니다. 계속 실행되는 리소스에는 요금이 부과될 수 있습니다. 리소스를 개별적으로 삭제하거나 리소스 그룹을 삭제하여 전체 리소스 세트를 삭제할 수 있습니다.

왼쪽 탐색 창의 모든 리소스 또는 리소스 그룹 링크를 사용하여 Azure Portal에서 리소스를 찾고 관리할 수 있습니다.

무료 서비스를 사용하는 경우 인덱스, 인덱서, 데이터 원본 3개로 제한됩니다. Azure Portal에서 개별 항목을 삭제하여 제한 이하로 유지할 수 있습니다.

다음 단계

이 빠른 시작에서는 인덱스를 만들고 문서와 함께 로드하며 쿼리를 실행하는 일련의 작업을 수행했습니다. 여러 단계에서, 가독성과 이해를 돕기 위해 손쉬운 방법을 사용하여 코드를 간소화했습니다.

이제 기본 개념에 익숙해졌으므로 웹앱에서 Azure AI Search API를 호출하는 자습서를 시도해 보세요.