SQL 시작

본 투고는 Elastic Stack (Elasticsearch) Advent Calendar 2020 24일째 보도이다.

이마


SQL 좋아하세요?처음에 글을 쓰는 것은두번째이지만, 나는 SQL을 완전히 이해하지 못해서 좋아하게 되었다.
이번 기고문에는 SQL에서 Elaticsearch를 요청할 수 있는 Elastic사 공식 플러그인Elasticsearch SQL이 소개된다.

지난번 보도와는 다르다


저번 보도에서도 Elasticsearch에 SQL을 발매하고 요청하는 방법을 소개했는데 그것은 Open Distro for Elasticsearch의 플러그인이다.양자의 커다란 차이는 다음과 같다.
Elasticsearch SQL
Open Distor for Elasticsearch SQL
라이선스
Elastic License (Basic Subscription) [1]
Apache License
잇닿다
JDBC, ODBC
JDBC, ODBC
GUI
×
Query Workbench
CLI


DSL로 변환
○(translate API)
○ (explain API)

Elasticsearch SQL에 대한 제한 사항


Elasticsearch SQL은 다양한 제한이 있고 SQL Limitations 페이지에 일람된 기록이 있다.
2020년 12월 12일 비교적 중요한 것에 뽑혔다면 다음과 같은 몇 가지를 열거할 수 있다.
  • Aray 가 지원되지 않음
  • 건수 제한
  • Group By 결과 기반 정렬
  • 또 기재되지 않은 부분에서도 이번 검증을 통해 다음과 같은 제한이 발견됐다.
  • DISTINCT 문은 지원되지 않습니다
  • JOIN 문장이 지원되지 않음
  • Elatic License Edition 컨테이너 실행


    검증된 환경 구축을 위해docker-compose를 사용합니다.주요 환경은 다음과 같다.
    릴리즈
    mac OS
    10.15.7
    docker for mac
    2.4.0.0
    Elaticsearch
    7.10.0
    Kibana
    7.10.0
    또한 이번 검증에는 키바나가 처음 시작될 때 기본적으로 설정할 수 있는 Sample eCommerce orders 데이터 세트가 사용됩니다.

    docker-compose.준비


    이번에 준비한 검증용 클러스터의 구성은 다음과 같다.특별히 여러 대를 구성할 필요가 없기 때문에 싱글 노드다.
    docker-compose.yml
    version: '2.2'
    services:
      es01:
        image: docker.elastic.co/elasticsearch/elasticsearch:7.10.0
        container_name: es01
        environment:
          - node.name=es01
          - discovery.seed_hosts=es02
          - cluster.initial_master_nodes=es01
          - cluster.name=docker-cluster
          - bootstrap.memory_lock=true
          - "ES_JAVA_OPTS=-Xms256m -Xmx256m"
        ulimits:
          memlock:
            soft: -1
            hard: -1
        ports:
          - 9200:9200
        networks:
          - esnet
      kibana:
        image: docker.elastic.co/kibana/kibana:7.10.0
        links:
         - es01:elasticsearch
        ports:
          - 5601:5601
        networks:
          - esnet
        environment:
          - xpack.monitoring.ui.container.elasticsearch.enabled=true
    
    networks:
      esnet:
    
    이때의 주의점으로 docker 그림 *-oss 을 주지 않은 것을 사용하십시오.
    현재 Elatic사가 공식적으로 공개한 도커 이미지는 아파치 라이선스에서 사용할 수 있는 Elasticsearch-Soss와 Elastic 라이선스에서 사용할 수 있는 두 가지 기능으로 나뉜다.
    elasticsearch | Docker
    이번에 사용하고자 하는 기능은 앞서 설명한 대로 Elastic 라이센스 아래에 있는 기능이므로 후자의 기능을 명시적으로 지정합니다.

    실천하다


    전권을 던져 조회를 얻으려 하다


    우선 획득한 SQL을 모두 던져보자.
    Kibana&Dev Tools에서 요청 실행 화면을 표시합니다.
    way to devtools
    ▼ Dev Tools
    devtools
    던지기 질의는 일반 SELECT*입니다.
    select_all.sql
    SELECT * FROM "kibana_sample_data_ecommerce"
    
    入力画面

    결실


    결과 얻기
    {
      "error" : {
        "root_cause" : [
          {
            "type" : "ql_illegal_argument_exception",
            "reason" : "Arrays (returned by [manufacturer]) are not supported"
          }
        ],
        "type" : "ql_illegal_argument_exception",
        "reason" : "Arrays (returned by [manufacturer]) are not supported"
      },
      "status" : 500
    }
    
    오류Aray가 대응하지 않았다고 합니다.실제 데이터를 보기 위해 어떤 이유인지 translate API를 사용하여DSL로 변환해 봅니다.
    청원
    GET /_sql/translate
    {
      "query": """
      SELECT * FROM "kibana_sample_data_ecommerce"
      """
    }
    

    결실


    {
      "size": 1000,
      "_source": {
        "includes": [
          "category",
          "customer_first_name",
          ... 中略
          "total_unique_products"
        ],
        "excludes": []
      },
      "docvalue_fields": [
        {
          "field": "currency"
        },
        ... 中略
        {
          "field": "user"
        }
      ],
      "sort": [
        {
          "_doc": {
            "order": "asc"
          }
        }
      ]
    }
    
    실제 기반 인덱스검색의 요구를 제기해 보세요.
    _검색 결과.json
    {
      "took" : 8,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 4675,
          "relation" : "eq"
        },
        "max_score" : null,
        "hits" : [
          {
            "_index" : "kibana_sample_data_ecommerce",
            "_type" : "_doc",
            "_id" : "uzf7j3YBp8s7-c5IvNTe",
            "_score" : null,
            "_source" : {
              "customer_full_name" : "Eddie Underwood",
              "customer_last_name" : "Underwood",
              "customer_first_name" : "Eddie",
              "day_of_week_i" : 0,
              "total_quantity" : 2,
              "taxless_total_price" : 36.98,
              "total_unique_products" : 2,
              "category" : [
                "Men's Clothing"
              ],
              "manufacturer" : [
                "Elitelligence",
                "Oceanavigations"
              ],
              "products" : [
                {
                  "tax_amount" : 0,
                  "taxful_price" : 11.99,
                  "quantity" : 1,
                  "taxless_price" : 11.99,
                  "discount_amount" : 0,
                  "base_unit_price" : 11.99,
                  "discount_percentage" : 0,
                  "product_name" : "Basic T-shirt - dark blue/white",
                  "manufacturer" : "Elitelligence",
                  "min_price" : 6.35,
                  "unit_discount_amount" : 0,
                  "price" : 11.99,
                  "product_id" : 6283,
                  "base_price" : 11.99,
                  "_id" : "sold_product_584677_6283",
                  "category" : "Men's Clothing"
                },
                {
                  "tax_amount" : 0,
                  "taxful_price" : 24.99,
                  "quantity" : 1,
                  "taxless_price" : 24.99,
                  "discount_amount" : 0,
                  "base_unit_price" : 24.99,
                  "discount_percentage" : 0,
                  "product_name" : "Sweatshirt - grey multicolor",
                  "manufacturer" : "Oceanavigations",
                  "min_price" : 11.75,
                  "unit_discount_amount" : 0,
                  "price" : 24.99,
                  "product_id" : 19400,
                  "base_price" : 24.99,
                  "_id" : "sold_product_584677_19400",
                  "category" : "Men's Clothing"
                }
              ],
              "taxful_total_price" : 36.98
            },
            "fields" : {
              "products.sku" : [
                "ZO0299602996",
                "ZO0549605496"
              ],
              "customer_phone" : [
                ""
              ],
              "geoip.city_name" : [
                "Cairo"
              ],
              "geoip.region_name" : [
                "Cairo Governorate"
              ],
              "type" : [
                "order"
              ],
              "order_date" : [
                "1609752528000"
              ],
              "geoip.location" : [
                "30.09999997448176, 31.29999996162951"
              ],
              "geoip.country_iso_code" : [
                "EG"
              ],
              "products.created_on" : [
                "1482744528000",
                "1482744528000"
              ],
              "currency" : [
                "EUR"
              ],
              "geoip.continent_name" : [
                "Africa"
              ],
              "customer_id" : [
                "38"
              ],
              "sku" : [
                "ZO0299602996",
                "ZO0549605496"
              ],
              "order_id" : [
                "584677"
              ],
              "user" : [
                "eddie"
              ],
              "customer_gender" : [
                "MALE"
              ],
              "email" : [
                "[email protected]"
              ],
              "event.dataset" : [
                "sample_ecommerce"
              ],
              "day_of_week" : [
                "Monday"
              ]
            },
            "sort" : [
              0
            ]
          }
        ]
      }
    }
    
    결과를 보면 category, manufacture 등 Aray 유형의 필드를 포함한다.
    이는 제약에 기재된 사항과 마찬가지로 앞으로 Aray형의 필드를 선택해서 피하지 말아야 한다.

    order id 내림차순으로 Top10 고객 이름 얻기


    query
    SELECT customer_full_name FROM "kibana_sample_data_ecommerce" ORDER BY order_id DESC LIMIT 10
    
    DSL
    {
      "size" : 10,
      "_source" : {
        "includes" : [
          "customer_full_name"
        ],
        "excludes" : [ ]
      },
      "sort" : [
        {
          "order_id" : {
            "order" : "desc",
            "missing" : "_first",
            "unmapped_type" : "keyword"
          }
        }
      ]
    }
    
    result
    {
      "columns" : [
        {
          "name" : "customer_full_name",
          "type" : "text"
        }
      ],
      "rows" : [
        [
          "Jim Pratt"
        ],
        ... 中略
        [
          "Wilhemina St. Graham"
        ]
      ]
    }
    

    customer_first_반복해서 가져오기name


    query
    SELECT DISTINCT customer_first_name from "kibana_sample_data_ecommerce"
    
    result
    {
      "error" : {
        "root_cause" : [
          {
            "type" : "verification_exception",
            "reason" : "Found 1 problem\nline 2:8: SELECT DISTINCT is not yet supported"
          }
        ],
        "type" : "verification_exception",
        "reason" : "Found 1 problem\nline 2:8: SELECT DISTINCT is not yet supported"
      },
      "status" : 400
    }
    
    Limitation 페이지에는 표시되지 않지만 DISTINCT 문은 지원되지 않는 것 같습니다.Aggregation이 얻을 수 있는 내용이라 조금 아쉽다.

    taxless_total_price 100 이상 200 이하order id 10개 얻기


    query
    SELECT order_id
    FROM kibana_sample_data_ecommerce
    WHERE taxless_total_price BETWEEN 100 AND 200
    LIMIT 10
    
    DSL
    {
      "size" : 10,
      "query" : {
        "range" : {
          "taxless_total_price" : {
            "from" : 100,
            "to" : 200,
            "include_lower" : true,
            "include_upper" : true,
            "time_zone" : "Z",
            "boost" : 1.0
          }
        }
      },
      "_source" : false,
      "stored_fields" : "_none_",
      "docvalue_fields" : [
        {
          "field" : "order_id"
        }
      ],
      "sort" : [
        {
          "_doc" : {
            "order" : "asc"
          }
        }
      ]
    }
    
    result
    {
      "columns" : [
        {
          "name" : "order_id",
          "type" : "keyword"
        }
      ],
      "rows" : [
        [
          "584058"
        ],
        ... 中略
        [
          "578650"
        ]
      ]
    }
    

    각 고객명의 주문서 수를 얻다


    query
    SELECT COUNT(customer_full_name), customer_full_name FROM kibana_sample_data_ecommerce GROUP BY customer_full_name
    
    DSL
    {
      "size" : 0,
      "_source" : false,
      "stored_fields" : "_none_",
      "aggregations" : {
        "groupby" : {
          "composite" : {
            "size" : 1000,
            "sources" : [
              {
                "f8f23918" : {
                  "terms" : {
                    "field" : "customer_full_name.keyword",
                    "missing_bucket" : true,
                    "order" : "asc"
                  }
                }
              }
            ]
          },
          "aggregations" : {
            "8df1ff4b" : {
              "filter" : {
                "exists" : {
                  "field" : "customer_full_name",
                  "boost" : 1.0
                }
              }
            }
          }
        }
      }
    }
    
    result
    {
      "columns" : [
        {
          "name" : "COUNT(customer_full_name)",
          "type" : "long"
        },
        {
          "name" : "customer_full_name",
          "type" : "text"
        }
      ],
      "rows" : [
        [
          2,
          "Abd Adams"
        ],
        ... 中略
        [
          1,
          "Abd Bradley"
        ]
      ]
    }
    

    JOIN


    데이터 준비


    JOIN 대상 테이블을 작성하려면 다음 정의를 사용하여 색인을 작성합니다.
    PUT /ecommerce_customer_info
    {
      "mappings": {
        "properties": {
          "customer_id": {
            "type": "integer",
            "fields": {
              "keyword": {
                "type": "keyword"
              }
            }
          },
          "customer_full_name": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword"
              }
            }
          }
        }
      }
    }
    
    임시로 몇 개의 데이터를 투입한다.
    POST /_bulk
    {"index": {"_index": "ecommerce_customer_info"}}
    {"customer_id": 1, "customer_full_name": "Eddie Underwood"}
    {"index": {"_index": "ecommerce_customer_info"}}
    {"customer_id": 2, "customer_full_name": "Mary Bailey"}
    {"index": {"_index": "ecommerce_customer_info"}}
    {"customer_id": 3, "customer_full_name": "Gwen Butler"}
    {"index": {"_index": "ecommerce_customer_info"}}
    {"customer_id": 4, "customer_full_name": "Diane Chandler"}
    

    질의 실행


    query
    SELECT e.customer_id
    FROM kibana_sample_data_ecommerce k
    INNER JOIN ecommerce_customer_info e
      ON k.customer_full_name = e.customer_full_name
    
    result
    {
      "error" : {
        "root_cause" : [
          {
            "type" : "parsing_exception",
            "reason" : "line 4:2: Queries with JOIN are not yet supported"
          }
        ],
        "type" : "parsing_exception",
        "reason" : "line 4:2: Queries with JOIN are not yet supported"
      },
      "status" : 400
    }
    
    JOIN 문은 DISTINCT 문과 마찬가지로 Limitations 페이지가 지원되지 않습니다.
    원래 Elasticsearch의 순기능을 사용하면 JOIN이 할 수 없기 때문에 지원하지 않으면 이해할 수 있다.

    전체 텍스트 찾기


    여기서 일반적인 SQL에서 벗어났지만 Elastic search처럼 전문 검색을 시도해 보세요.
    Full-Text Search Functions를 보면 2010/12는 지금 이하로 지지를 받고 있다.
  • Match Query
  • query_string Query
  • Score
  • 점수 링도 잘 치네.몇 개 바로 해볼게요.

    smith에 맞는 이름을 가진 고객의order id


    query
    SELECT order_id FROM kibana_sample_data_ecommerce WHERE MATCH(customer_full_name, 'smith')
    
    DSL
    {
      "size" : 1000,
      "query" : {
        "match" : {
          "customer_full_name" : {
            "query" : "smith",
            "operator" : "OR",
            "prefix_length" : 0,
            "max_expansions" : 50,
            "fuzzy_transpositions" : true,
            "lenient" : false,
            "zero_terms_query" : "NONE",
            "auto_generate_synonyms_phrase_query" : true,
            "boost" : 1.0
          }
        }
      },
      "_source" : false,
      "stored_fields" : "_none_",
      "docvalue_fields" : [
        {
          "field" : "order_id"
        }
      ],
      "sort" : [
        {
          "_doc" : {
            "order" : "asc"
          }
        }
      ]
    }
    
    result
    {
      "columns" : [
        {
          "name" : "order_id",
          "type" : "keyword"
        }
      ],
      "rows" : [
        [
          "557262"
        ],
        ... 中略
        [
          "573691"
        ]
      ]
    }
    

    smith에 이름이 맞는 고객을 매칭도 순서대로 점수를 매기다


    query
    SELECT customer_full_name
    FROM kibana_sample_data_ecommerce
    WHERE MATCH(customer_full_name, 'smith')
    ORDER BY SCORE()
    
    DSL
    {
      "size" : 1000,
      "query" : {
        "match" : {
          "customer_full_name" : {
            "query" : "smith",
            "operator" : "OR",
            "prefix_length" : 0,
            "max_expansions" : 50,
            "fuzzy_transpositions" : true,
            "lenient" : false,
            "zero_terms_query" : "NONE",
            "auto_generate_synonyms_phrase_query" : true,
            "boost" : 1.0
          }
        }
      },
      "_source" : {
        "includes" : [
          "customer_full_name"
        ],
        "excludes" : [ ]
      },
      "sort" : [
        {
          "_score" : {
            "order" : "asc"
          }
        }
      ]
    }
    
    result
    {
      "columns" : [
        {
          "name" : "customer_full_name",
          "type" : "text"
        }
      ],
      "rows" : [
        [
          "Wilhemina St. Smith"
        ],
        ... 中略
        [
          "Tariq Smith"
        ]
      ]
    }
    
    이름에 smith가 포함되어 있어 비슷한 결과를 얻었다.

    최후


    이번 기고문에서는 Elasticsearch SQL을 사용하여 어느 정도의 작업을 수행할 수 있는지 검증했습니다.
    개인적으로 SCORE 함수가 잘 준비되어 있어 전문 검색 솔루션의 특색을 좋아합니다.다른 한편, 지원하지 않는 조회도 많아 로그를 분석하고 데이터를 추출할 때 사용하기도 힘들다.
    또 translate API를 사용함으로써 초보자의 학습을 지원한다는 인상을 남겼다.선교할 때 사용할 수 있을 것 같아요.
    다만, 이쪽 SQL 기능과 비교하면 Open Distro for Elasticsearch SQL 할 수 있는 일이 많고, 큐리 워크벤치 등도 있어 부드러운 인상을 준다.
    다양한 선택이 있었지만 본격적인 플러그인으로 앞으로 발전할 것으로 기대된다.

    참고 자료


    공식 문서
    각주
    https://github.com/elastic/elasticsearch/blob/master/licenses/ELASTIC-LICENSE.txt 각 요금 체계와 사용 가능한 기능은 여기.을 참조하십시오.↩︎

    좋은 웹페이지 즐겨찾기