SQL для Cosmos DB - Советы и рекомендации

Tags: SQL, SQL Server, JSON

Эта статья поможет вам расширить базовые возможности SQL-запросов Cosmos DB. Здесь вы узнаете несколько хитростей, которые могут вам понадобиться, чтобы преодолеть ограничения в стиле Cosmos DB SQL.

Строковые функции для поиска по шаблону

Движок Cosmos DB неплохо подходит для поиска по обширным наборам данных JSON. Если вам нужно использовать поиск по шаблону для текста внутри определенного атрибута, функция CONTAINS() является хорошей отправной точкой. Вы используете его скорее как T-SQL LIKE в предложении WHERE.

ПРИМЕЧАНИЕ. Загрузите данные SimpleCars2 в эмулятор Cosmos DB. Для получения справки об этом смотрите первую статью в этой серии.

Запустите примеры из этой статьи для SimpleCars2.

SELECT  s.CustomerName, s.InvoiceNumber

FROM    s

WHERE   CONTAINS(s.CustomerName, "Wheels")


Этот запрос находит следующие документы в исходной коллекции:

[

    {

        "CustomerName": "Wonderland Wheels",

        "InvoiceNumber": "GBPGB011"

    },

    {

        "CustomerName": "Wonderland Wheels",

        "InvoiceNumber": "GBPGB011"

    }

]


Вы должны знать, что текст поиска чувствителен к регистру. Что нужно, чтобы сделать поиск по шаблону нечувствительным к регистру? Ответ заключается в том, чтобы заставить атрибут и текст, который вы ищете, быть в одном и том же регистре, как если бы вы использовали для сортировки с учетом регистра в SQL Server:

SELECT  s.CustomerName

FROM    s

WHERE   CONTAINS(UPPER(s.CustomerName), "WHEELS")


В качестве альтернативы, если вам нужно настроить поиск по шаблону, который применяется к началу или концу содержимого атрибута, аналогично LIKE «%какой-то текст» в T-SQL, вы можете использовать этот подход:

SELECT  s.CustomerName

FROM    s

WHERE   STARTSWITH(s.CustomerName, "Won")

        OR

        ENDSWITH(s.CustomerName, "Vehicles")

 


Этот более сфокусированный поиск по шаблону дает следующий результат:

[

    {

        "CustomerName": "Wonderland Wheels"

    },

    {

        "CustomerName": "Wonderland Wheels"

    },

    {

        "CustomerName": "Birmingham Executive Prestige Vehicles"

    }

]


Этот запрос будет по-прежнему чувствителен к регистру, но вы можете использовать функции UPPER() или LOWER(), чтобы обеспечить применение поиска без учета регистра.

Обработка NULL в Cosmos DB SQL

Одна из обнадеживающих общих возможностей между T-SQL и Cosmos DB SQL заключается в том, что значения NULL, выраженные в JSON как строчные слова без кавычек в JSON, «заражают» весь расчет и возвращают значение NULL, если какой-либо один атрибут имеет значение NULL. Это то, что вы должны научиться обрабатывать в документах JSON.

Однако одна из радостей полуструктурированного формата, такого как JSON, заключается в том, что ни один атрибут не является обязательным. Потенциальное последствие заключается в том, что атрибуты могут отсутствовать в определенных документах, а отсутствующий атрибут также будет возвращать ноль в расчете. По сути, вы должны защититься от двух различных типов нулей, отсутствующих элементов и фактических нулевых значений.

Чтобы сделать это более понятным, предположим, что вы написали некоторый превентивный код, подобный следующему:

SELECT  s.InvoiceNumber

       ,s.Cost + s.RepairsCost

       + s.PartsCost + s.TransportInCost

       + (IS_NULL(s.LineItemDiscount) ? 0 : s.LineItemDiscount)

           AS Costs

FROM   s

Предполагая, что запрос работает (а нет причин, по которым он не должен работать), вывод должен быть следующим:

 

[

    {

        "InvoiceNumber": "GBPGB011",

        "Costs": 7425

    },

    {

        "InvoiceNumber": "GBPGB011",

        "Costs": 66400

    },

    {

        "InvoiceNumber": "GBPGB001" ,

        "Costs": 56425

    },

    {

        "InvoiceNumber": "GBPGB002" ,

        "Costs": 185650

    },

    {

        "InvoiceNumber": "GBPGB003"

       

    },

    {

        "InvoiceNumber": "EURDE004"

    },

    {

        "InvoiceNumber": "EURFR005"

    }

]

 

Здесь вы комбинируете функцию IS_NULL () из Cosmos DB SQL которая обнаруживает отсутствующий атрибут, с троичной логикой для имитации функции ISNULL () T-SQL. Более того, вы заключили троичную логику обнаружения NULL в круглые скобки следующим образом:

(IS_NULL(s.LineItemDiscount) ? 0 : s.LineItemDiscount)

Это гарантирует, что вывод работает так, как вы ожидаете. Действительно, если забыть заключить логику IS_NULL () в круглые скобки, это вызовет проблемы.

Если вы выполните этот запрос к коллекции SimpleCars2, вы увидите, что этот метод работает безупречно для Invoice GBPGB002, где в исходном документе JSON установлен атрибут LineItemDiscount, равный нулю. К сожалению, общие затраты отсутствуют по трем счетам, номерам GBPGB003, GBPGB004 и GBPGB005. Если вы посмотрите на эти документы, то увидите, что во всех них отсутствует атрибут LineItemDiscount.

Вы непоколебимо пытаетесь исправить ситуацию, используя функцию IS_DEFINED(), например таким образом (и обратите внимание, что троичная логика перевернута по сравнению с функцией IS_NULL()):

SELECT  s.InvoiceNumber

       ,s.Cost + s.RepairsCost

       + s.PartsCost + s.TransportInCost

       + (IS_DEFINED(s.LineItemDiscount) ? s.LineItemDiscount : 0)

           AS Costs

FROM   s


На этот раз вывод выглядит следующим образом:

[

    {

        "InvoiceNumber": "GBPGB011",

        "Costs": 7475

    },

    {

        "InvoiceNumber": "GBPGB011",

        "Costs": 66900

    },

    {

        "InvoiceNumber": "GBPGB001 ,

        "Costs": 59125

    },

    {

        "InvoiceNumber": "GBPGB002"

    },

    {

        "InvoiceNumber": "GBPGB003",

        "Costs": 16410

    },

    {

        "InvoiceNumber": "EURDE004",

        "Costs": 10600

    },

    {

        "InvoiceNumber": "EURFR005",

        "Costs": 17970

    }

]

В этом случае вы увидите затраты для счетов-фактур GBPGB003, GBPGB004 и GBPGB005, где вообще нет атрибута для скидки на позицию, но не для GBPGB002, документа с фактическим нулевым значением.

Этот пример показывает, что на самом деле в JSON есть два типа нулей: пропущенные атрибуты и атрибуты, содержащие нуль. Одним из способов обеспечения того, чтобы обе эти потенциальные ловушки были обработаны, является код, подобный следующему:

SELECT  s.InvoiceNumber

       ,s.Cost + s.PartsCost + s.TransportInCost

       + (IS_DEFINED(s.LineItemDiscount)

          AND NOT IS_NULL(s.LineItemDiscount) ? s.LineItemDiscount : 0)

           AS Costs

FROM   s


Наконец, вы получаете результат, который искали:

[

    {

        "InvoiceNumber": "GBPGB011",

        "Costs": 7225

    },

    {

        "InvoiceNumber": "GBPGB011",

        "Costs": 66400

    },

    {

        "InvoiceNumber": "GBPGB001",

        "Costs": 56950

    },

    {

        "InvoiceNumber": "GBPGB002",

        "Costs": 180150

    },

    {

        "InvoiceNumber": "GBPGB003",

        "Costs": 15750

    },

    {

        "InvoiceNumber": "EURDE004",

        "Costs": 10100

    },

    {

        "InvoiceNumber": "EURFR005",

        "Costs": 16610

    }

]

 


Здесь вы используете простую логику, чтобы, во-первых, выявить, определен ли атрибут, и, во-вторых, чтобы убедиться, что, если он присутствует, то не содержит нуль. Такой подход приближается к базовой обработке нуля в T-SQL. Как только вы достигнете определенного уровня владения в Cosmos DB, вы можете написать свои собственные пользовательские функции на JavaScript для решения подобных проблем.

Учитывая явные ограничения Cosmos DB SQL, вам придется проявить изобретательность при написании SQL-запросов. Вам нужно будет написать сложный SQL, используя комбинации доступных функций для достижения ваших целей.

Взгляните на несколько элементарных обходных путей, чтобы дать вам несколько идей.

SUBSTRING() для LEFT()

Cosmos DB SQL в настоящее время не имеет эквивалента функции SQL LEFT (). Вместо этого вам нужно проявить немного изобретательности и применить функцию SUBSTRING() следующим образом:

SELECT  s.CustomerName ,s.SalePrice

FROM    simplecars AS s

WHERE   SUBSTRING(s.CustomerName, 0, 1) = "M"


Вы должны получить следующий результат:

[

    {

        "CustomerName": "Magic Motors",

        "SalePrice": 65000

    }

]


SUBSTRING() для RIGHT()

Так же, как нет функции LEFT(), нет функции RIGHT(). Однако объединение функций SUBSTRING() и REVERSE(), как это, может решить определенные проблемы:

SELECT  s.CustomerName ,s.SalePrice

FROM    simplecars AS s

WHERE   SUBSTRING(REVERSE(s.CustomerName), 0, 1) = "r"


На этот раз результат будет следующим:

[

    {

        "CustomerName": "WunderKar",

        "SalePrice": 11500

    }

]

Помните, что если вы ищете более одного символа справа от строки, используя эту технику, вам нужно будет перевернуть строку, которую вы ищете. То есть вместо того, чтобы искать «red», вы должны были бы ввести «der».

Если ввод текста поиска в обратном порядке вызывает у вас дискомфорт, вы всегда можете добавить второй REVERSE(), например:

SELECT  s.CustomerName ,s.SalePrice

FROM    saleselements AS s

WHERE   REVERSE(SUBSTRING(REVERSE(s.CustomerName), 0, 3)) = "Kar"


Напомним, что это чувствительно к регистру, если вы не расширите SQL для обеспечения учета регистра, как здесь:

SELECT  s.CustomerName ,s.SalePrice

FROM    saleselements AS s

WHERE   SUBSTRING(REVERSE(UPPER(s.CustomerName)), 0, 1) = "R"

Имитация T-SQL YEAR(), MONTH() и DAY()

В Cosmos DB SQL нет функции YEAR(). Однако одна простая альтернатива - применить функцию SUBSTRING() к строке даты и изолировать соответствующий элемент даты. Чтобы указать документы JSON для определенного года, вы можете написать:

SELECT s.SaleDate

FROM   s

WHERE  SUBSTRING(s.SaleDate, 0, 4) = "2015"


Выходные данные этого запроса отображают дату продажи шести из семи документов в коллекции.

Расширяя этот принцип, вы можете выделить месяц следующим образом:

SELECT s.SaleDate

FROM   s

WHERE  SUBSTRING(s.SaleDate, 5, 2) = "02"


В этом случае возвращаются только два объекта. День можно выделить таким образом:

SELECT s.SaleDate

FROM   s

WHERE  SUBSTRING(s.SaleDate, 8, 2) = "25"

 


Этот фильтр возвращает только один объект.

Хотя они, вероятно, самоочевидны, для полноты картины стоит отметить, что вы можете использовать:

Операторы> =, <=, <> или ! =

IN для набора лет и т. д.

Как только вы начнете создавать более сложные условия WHERE, которые смешивают и сопоставляют эти элементы, вы обнаружите, что можете очень легко указывать диапазоны даты и времени.

Округление до ближайшей 1000

В качестве простого примера того, как расширить SQL-функции Cosmos DB, рассмотрим следующий фрагмент кода, который расширяет функцию TRUNC () до ближайшей тысячи:

SELECT  TRUNC(ROUND(s.TotalSalePrice / 1000)) * 1000 AS RoundedUp

FROM    s

Здесь вывод будет выглядеть следующим образом:

 

[

    {

        "RoundedUp": 89000

    },

    {

        "RoundedUp": 89000

    },

    {

        "RoundedUp": 65000

    },

    {

        "RoundedUp": 220000

    },

    {

        "RoundedUp": 20000

    },

    {

        "RoundedUp": 12000

    },

    {

        "RoundedUp": 20000

    }

]


Заключение

Существует множество других обходных путей, которые вы можете применить, чтобы преодолеть ограничения реализации SQL в Cosmos DB, и этот короткий список может быть расширен для охвата ряда вариантов. Однако это будет похоже на возвращение к SQL Server примерно в 1994 году, и даже это не охватит все проблемы, с которыми вы, вероятно, столкнетесь. Достаточно сказать, что вам, вероятно, потребуется некоторая изобретательность и написать чрезвычайно сложный SQL, чтобы сделать что-то, что было бы просто в T-SQL, или рассмотреть другие решения.

У Cosmos DB есть короткий ответ на этот вопрос, а именно на то, чтобы написать свои собственные пользовательские функции в JavaScript. Это богатый и чрезвычайно мощный язык, и его может быть довольно легко дополнить Cosmos DB SQL множеством функций, отвечающих вашим конкретным требованиям. Тем не менее, цель этой статьи состоит в том, чтобы взглянуть на SQL-код Cosmos DB, и поэтому для изучения того, как добавлять расширения на языке, с которым вы, возможно, не знакомы, придется подождать еще один день.

В следующей статье этой серии будет рассказано, как запрашивать более сложные документы JSON.              

SELECT  s.CustomerName ,s.SalePrice

FROM    simplecars AS s

WHERE   SUBSTRING(REVERSE(s.CustomerName), 0, 1) = "r"

No Comments

Add a Comment