Создание с помощью Bluemix и Cloudant корпоративной базы данных для финансовых данных, требуемых SEC

13.04.2016
PDF (822 KB)
 
Фото автора

Тед Потма

Продакт-менеджер IBM

Базы данных NoSQL стали последним писком моды, и тому есть веские причины. В Интернете доступно огромное количество неструктурированных и слабоструктурированных данных. Загрузка этих данных в традиционную СУБД – громоздкий и сложный процесс, тогда как способность баз данных NoSQL динамически загружать информацию значительно упростила преобразование этих данных в доступный формат.

В этом руководстве рассматривается тип наборов данных, используемый Комиссией по ценным бумагам и биржам США (U.S. Securities and Exchange Commission – SEC). Отчеты, которые SEC требует от всех акционерных компаний США, состоят из балансовых отчетов, отчетов о движении денежных средств и другой финансовой информации. Эти общедоступные отчеты стали обязательными для публикации после обвала фондового рынка 1929 года в целях повышения прозрачности бизнеса и предотвращения новой Великой депрессии. Вот в качестве примера выдержка из отчета о прибылях и убытках IBM.

Отчет IBM

Сначала отчеты представлялись в SEC на бумаге, а позднее стали представляться в электронном формате. Начиная с 2005 года SEC постепенно переводит эти данные в машиночитаемый структурированный формат расширяемого языка деловых отчетов (eXtensible Business Reporting Language - XBRL). XBRL – это разновидность XML-документа, и полное его объяснение выходит далеко за рамки этого руководства. Однако вот как выглядит значение Services из предыдущего примера в формате XBRL:

Кликните, чтобы увидеть код

<us-gaap:SalesRevenueServicesNet id="ID_1119" contextRef="FROM_Jul01_2014_TO_Sep30_2014_Entity_0000051143" unitRef="USD" decimals="-6">13869000000</us-gaap:SalesRevenueServicesNet>

В этом руководстве объясняется, как построить базу данных – с нуля или по с использованием образца – и развернуть свою собственную версию xbrl.mybluemix.net.

К каждому квартальному или годовому отчету, подаваемому в SEC, прилагается XBRL-файл, содержащий те же данные, что и в отчете, удобочитаемом для человека. SEC публикует листинги этих файлов, и каждый может бесплатно загрузить их. Хотя эти данные предоставляются в структурированной форме, к ним нелегко обращаться, так как они состоит из сотен тысяч отдельных XML-файлов.

Приложение xbrl.mybluemix.net, о котором идет речь в этом руководстве, предоставляет простой интерфейс для обращения к набору данных и их отображения в наглядной форме.

В качестве примера рассмотрим следующий рисунок. После выполнения поиска по компании и финансовой концепции (в данном примере – AssetsCurrent) результаты представлены в виде линейного графика. Отдельные точки снабжены всплывающими надписями, которые можно «прикнопить», для указания на файлы на веб-сайте SEC, откуда были взяты эти данные.

Результаты IBM

Кликните, чтобы увидеть увеличенное изображение

Что требуется для создания приложения

 
  • Учетные записи Bluemix и DevOps Services, обе должны быть привязаны к вашему ID IBM
  • При необходимости локального развертывания: среда исполнения Node.js

Шаг 1. Создание базы данных

 

Существует три способа ввода данных в приложение. Вот они в порядке убывания сложности:

  1. создание новой базы данных с нуля;
  2. копирование существующей базы данных в личный кабинет Cloudant;
  3. использование готовой базы данных.

Вариант c на самом деле означает, что ничего делать не надо, так что мы сосредоточимся на вариантах а и b. Те, кто хочет использовать готовую базу данных, могут перейти к Шагу 2. Создание нового проекта в DevOps Services.

Вариант 1. Создание новой базы данных с нуля

 
  1. Войдите в Bluemix и добавьте в свое пространство Bluemix службу Cloudant NoSQL DB.
  2. Откройте панель управления Cloudant. (Инструкции по получению доступа к панели управления Cloudant см. в Приложении.) Создайте новую базу данных.
  3. Установите среду исполнения Node.js, если это еще не сделано.
  4. Экспортируйте дерево исходного кода из репозитория исходного кода XbrlBuilder в файл архива.
  5. Распакуйте архив в локальный каталог.
  6. Из каталога XbrlBuilder запустите приложение командой
    node app.js

    Нужно задать следующие переменные среды:

    1. BASE_DIRECTORY: место хранения загруженных файлов XBRL (сжатых).
    2. CLOUDANT_DATABASE: база данных Cloudant, созданная на шаге 2. Она должна выглядеть примерно так:
      https://<user>:<pass>@cloudant.com/<db>/

      Эти реквизиты доступа можно найти на панели управления Bluemix. Обратите внимание, что если к службе Cloudant не привязаны никакие Bluemix-приложения, то чтобы увидеть эти реквизиты доступа, необходимо создать такое приложение.

      Приложение совершает итерацию каждый месяц, когда поступают файлы XBRL. Сначала оно загружает файлы XBRL в BASE_DIRECTORY, а затем передает их в CLOUDANT_DATABASE. В случае неустранимой ошибки во время загрузки достаточно просто перезапустить приложение. Все уже загруженные файлы будут пропущены.

  7. Создайте необходимые представления. Вот они:

    factsMainViews/EntityConceptName

    Map:

    Кликните, чтобы увидеть код

    function getValue(fqn) 
    { 
        return fqn.substring(fqn.lastIndexOf("/") + 1, fqn.length); 
    } 
     
    function(doc) { 
      emit([doc['http://www.xbrl.org/2003/instance/Entity'], getValue(doc['http://www.xbrl.org/2003/instance/Concept'])], null); 
    }

    Reduce:

    _count

    factsMainSearchIndexes/EntitySplitConcept

    Функция Search index:

    Кликните, чтобы увидеть код

    function unCamelCase (str){ 
        return str 
            // вставка пробела между буквами нижнего и верхнего регистров &amp;amp; 
            .replace(/([a-z])([A-Z])/g, &apos;$1 $2&apos;) 
            // пробел перед последней буквой верхнего регистра в последовательности, за которой следует буква нижнего регистра 
            .replace(/\b([A-Z]+)([A-Z])([a-z])/, &apos;$1 $2$3&apos;) 
            // первый символ в верхнем регистре 
            .replace(/^./, function(str){ return str.toUpperCase(); }); 
    } 
     
    function getValue(fqn) 
    { 
        return fqn.substring(fqn.lastIndexOf(&quot;/&quot;) + 1, fqn.length); 
    } 
     
    function(doc){ 
    	index(&quot;entity&quot;, doc[&apos;http://www.xbrl.org/2003/instance/Entity&apos;], {&quot;store&quot;: true}); 
    	index(&quot;conceptNameSplit&quot;, unCamelCase(getValue(doc[&apos;http://www.xbrl.org/2003/instance/Concept&apos;])), {&quot;store&quot;: true, &quot;facet&quot;:true}); 
    }

    factsMainSearchIndexes/EntityCipherCompanyName

    Функция Search index:

    Кликните, чтобы увидеть код

    function rotateText(text, rotation) { 
        // Предел суррогатной пары 
        var bound = 0x10000; 
     
        // Принудительный перенос целого в пределах границ, на всякий случай 
        rotation = parseInt(rotation) % bound; 
     
        // При отсутствии изменений может также возвращать текст  
        if(rotation === 0) return text; 
     
        // Создание строки из кодов символов 
        return String.fromCharCode.apply(null, 
            // Превращение строки в коды символов 
            text.split('').map(function(v) { 
                // Возвращает текущий код символа с циклическим сдвигом 
                return (v.charCodeAt() + rotation + bound) % bound; 
            }) 
        ); 
    } 
     
    function getValue(fqn) 
    { 
        return fqn.substring(fqn.lastIndexOf("/") + 1, fqn.length); 
    } 
     
    function(doc){ 
    	index("companyName", rotateText(doc['http://www.xbrl.org/2003/instance/Entity'], 325) + ' ' + doc['http://www.sec.gov/Archives/edgar/companyName'], {"store": true, "facet":true}); 
    }

Вариант 2. Копирование в новую базу данных Cloudant

 
  1. Войдите в Bluemix и добавьте в свое пространство Bluemix службу Cloudant NoSQL DB.
  2. Откройте панель управления Cloudant. (Инструкции по получению доступа к панели управления Cloudant см. в Приложении.)
  3. Перейдите в окно репликации и выполните следующие действия:
    • выберите пункт New Replication;
    • в разделе SOURCE DATABASE > Remote Database введите URL-адрес базы данных:
      https://0741ae13-4f99-4ffb-8282-60d27e161c7f-bluemix.cloudant.com/facts;
    • в разделе TARGET DATABASE > New Database > Create a new database locally введите имя новой базы данных. Оно может быть любым. В этом примере я использую имя facts.
    Экран репликации

    Кликните, чтобы увидеть увеличенное изображение

  4. Нажмите кнопку Replicate.

Примечание. Объем базы данных facts больше 100 ГБ. Имеется меньший поднабор данных facts_test (примерно 47 МБ). Используйте этот поднабор для тестирования репликации. facts_test можно использовать и для проверки всего приложения, но тогда нужно будет создать представления, показанные на последнем шаге инструкций Вариант 1. Создание новой базы данных с нуля.

Шаг 2. Создание нового проекта в DevOps Services

 
  1. Войдите в DevOps Services.
  2. Перейдите в репозиторий исходного кода XbrlServer.
  3. Нажмите кнопку FORK в верхнем меню и создайте новый проект, следуя инструкциям.

Шаг 3. Развертывание приложения в Bluemix

 
  1. Перейдите в проект DevOps, созданный на шаге 2.
  2. Измените код, чтобы он указывал на нужную базу данных:
    1. Если вы создали собственную базу данных, связанную с приложением, и назвали ее facts, то нечего делать не надо. URL-адрес Cloudant будет взят из переменных среды приложения.
    2. Если вы создали собственную базу данных с именем, отличным от facts, то необходимо изменить параметр cloudantFactsUri в файле helpers.coffee и параметр cloudantUri в файлах elements.coffee, companies.coffee и size.coffee.
    3. Если используется существующая база данных, измените метод getCloudantUrl в файле helpers.coffee, чтобы он возвращал значение https://0741ae13-4f99-4ffb-8282-60d27e161c7fbluemix.cloudant.com.
  3. Щелкните на корневой папке, нажмите кнопку раскрывающегося списка на панели Launch Configuration и нажмите кнопку CREATE NEW.
  4. Выполните процесс развертывания, следуя инструкциям в диалоговом окне.

Процесс развертывания может занять несколько минут. Когда процесс завершится, отображается сообщение; состояние процесса развертывания можно также проверить на панели управления Bluemix.

Процесс развертывания достаточно настроить один раз. Чтобы снова развернуть ту же установку, достаточно нажать кнопку PLAY рядом с панелью запуска Launch Configuration.

Основные составляющие кода

 

XbrlBuilder

 

Вот некоторые ключевые файлы из репозитория исходного кода XbrlBuilder:

  • app.coffee – главная точка входа:
    • parseRss() принимает RSS-каналы с передаваемыми файлами, разбирает их на соответствующие XBRL-файлы и запускает процесс загрузки/анализа этих файлов;
    • parseInstance() принимает экземпляр XBRL и преобразует его в поток документов JSON, которые направляются в конечную точку Cloudant bulk_posts;
  • streams/CloudantBulkPostStream.coffee принимает последовательность объектов и сериализует их как JSON-массив для загрузки в Cloudant;
  • streams/FactTransformStream.coffee принимает последовательность фактов из XBRL-документа и преобразует их в объекты JavaScript;
  • streams/InstancePerProcessorTransformStream.coffee сохраняет сведения об экземпляре документа из RSS-канала, из которого он поступил, для использования в файле FactTransformStream.coffee;
  • streams/XmlTransformStream.coffee – это потоковый XML-парсер;
  • model/xml/Selector.coffee — это класс, используемый для выбора элемента в XML с применением XmlTransformStream.coffee (в отличие от строкового селектора, такого как XLink);
  • model/xml/SelectorSet.coffee – это последовательность тестов, которые выполняются над XML-элементами, когда парсер встречает их, чтобы определить, следует ли их выбрать. Это логические операции OR. Для выбора элемента достаточно, чтобы один тест был успешным.

XbrlServer

 

Вот некоторые из ключевых файлов xbrl.mybluemix.net:

  • Routes/helpers.Coffee содержит ряд вспомогательных функций, используемых приложением:
    • getCloudantURL() возвращает URL-адрес хранилища данных Cloudant; по умолчанию он берется из VCAP_SERVICES. Измените это для локальной разработки или для получения данных из базы, не указанной в этой переменной;
    • recursiveCloudantSearch() в настоящее время не используется; за информацией о том, как ее можно использовать, обращайтесь к разделу Каждый результат из Cloudant Search Index;
    • getParsedFactData() возвращает данные для построения диаграммы;
    • tickerResolver() позволяет искать тикеры, а также текст в названиях компаний;
  • routes/companies.coffee используется для заполнения поля поиска со списком компаний;
  • routes/elements.coffee используется для заполнения поля поиска со списком концепций;
  • routes/facts.coffee возвращает данные для заполнения диаграммы;
  • routes/save.coffee сохраняет данные в Cloudant, обеспечивая работу функции share. Для этого требуется доступ для записи, так что здесь нельзя использовать готовую базу данных facts, которая доступна только для чтения;
  • model/Fact.coffee позволяет более эффективно использовать JSON-объекты, полученные из Cloudant:
    • getValue() возвращает текст, следующий за последним знаком «/» в записи URI;
    • GetUnitDescription() возвращает наглядное строковое описание записи базы данных fact;
    • GetDimensions() возвращает ассоциативный массив пар ось/член, управляющих XBRL-размерностью фактов;
    • GetHashValue() возвращает строку, однозначно идентифицирующую факт, независимо от его значения. Если два факта имеют одно и то же значение HashValue, принимается решение о том, какой из них «корректнее», например, последний по времени;
    • GetPeriodDescription() возвращает наглядное строковое описание XBRL-размерности факта;
  • streams/FactTransformStream.coffee принимает данные факта из Cloudant и преобразует их в форму, более подходящую для загрузки в Highcharts (инструмент построения диаграмм, используемый в xbrl.mybluemix.net);
  • public/scripts/index.coffee содержит весь клиентский код JavaScript, используемый xbrl.mybluemix.net.

Уроки из опыта работы с Cloudant

 

Этот раздел состоит из ряда уроков, которые я извлек при создании xbrl.mybluemix.net. Некоторые из них могут показаться гуру Node.js или Cloudant чрезвычайно простыми, но когда я начинал, не имея никаких знаний ни по одной из этих тем, мне пришлось пройти трудный путь.

Минимизируйте число представлений каждого проектного документа

 

Это не официальная «рекомендация», а просто привычка, которую я приобрел, работая с Cloudant и большими наборами данных. При работе с базой данных Cloudant «запросов» как таковых не существует. Вместо этого можно создавать вторичные или поисковые индексы, или «представления» по базе данных. Обе эти функции, написанные на JavaScript, выполняются для каждого документа из базы данных, а результаты хранятся в B-дереве. Это позволяет очень быстро извлекать результаты для больших наборов данных, так как все возможные результаты предварительно вычислены. Однако процесс создания индекса может оказаться очень медленным, если данных много или функция индекса сложна. Каждое представление – это часть проектного документа, а каждый проектный документ может содержать любое количество представлений. Как следует из документации Cloudant:


При обновлении проектного документа индексы-представления перестраиваются. Обновление любого представления приводит к перестройке всех представлений документа.


Это второе предложение очень важно. На момент написания этого руководства в основной базе данных xbrl.mybluemix.net содержалось около 80 млн документов. Построение простого вторичного индекса по этим данным обычно занимает порядка 72 часов. Если у вас есть приложение, зависящее от такого представления, оно в это время будет либо отображать очень неполные данные, либо вообще не будет отображать никаких данных. В некоторых руководствах пишут, что каждый проектный документ концептуально представляет собой «приложение», а каждое представление – часть этого приложения. Это хорошо, но когда у вас сотни гигабайт или терабайты данных, для того чтобы изменить часть приложения или даже просто дополнить его, вам придется отложить все на несколько дней. Это очень раздражает даже если это процесс разработки, а не производства. Поэтому независимо от официальных «рекомендаций», я взял в привычку иметь как можно меньше представлений на каждый проектный документ, как правило, одно или два. Может быть это и не очень красиво, зато очень удобно, когда нужно изменить одну часть приложения, не отключая другие.

Анализатор CamelCase-лексем для поисковых индексов Cloudant

 

При создании поискового индекса для базы данных Cloudant можно найти ряд анализаторов для анализа и выделения лексем в полнотекстовых данных. При создании xbrl.mybluemix.net я хотел искать по CamelCase-строкам, таким как SalesRevenueNet. В Cloudant нет встроенного анализатора CamelCase-лексем. Можно построить свой собственный анализатор лексем Lucene.Net для CamelCase-строк, но при использовании Cloudant это не имеет смысла. К счастью, возможность создания поискового индекса как функции JavaScript позволяет выполнять практически любой анализ лексем. Достаточно при создании индекса применить свой собственный анализатор лексем.

Кликните, чтобы увидеть код

function unCamelCase (str){ 
    return str 
        // вставка пробела между нижним и верхним регистрами 
        .replace(/([a-z])([A-Z])/g, '$1 $2') 
        // пробел перед последней буквой верхнего регистра в последовательности, за которой следует буква нижнего регистра 
        .replace(/\b([A-Z]+)([A-Z])([a-z])/, '$1 $2$3') 
        // первый символ в верхнем регистре 
        .replace(/^./, function(str){ return str.toUpperCase(); }); 
} 
 
function(doc){ 
	index("name", unCamelCase(doc.name), {"store": true}); 
}

Все очень просто. Не нужно индексировать по уже существующему полю; его можно создать «в процессе выполнения». Для наблюдательных – я понимаю, что предыдущий пример не учитывает чисел, но если хотите, дополните его этим в качестве упражнения. Спасибо этому форуму StackOverflow за пример лексического CamelCase-анализатора на JavaScript.

«Уникальные» результаты из поискового индекса Cloudant

 

Поисковые индексы – чрезвычайно полезная функция Cloudant. Я бы даже сказал, важнейшая. Без них решение обычных задач по работе с базой данных многим показалось бы гораздо более сложным, а то и невозможным. Рассмотрим упрощенный набор данных с сайта xbrl.mybluemix.net, где IBM публикует некоторые свои финансовые данные:

{ "date": "03/31/2010", "company": "IBM",  
"concept":"Assets", "value": 100.00} 
{ "date": "06/30/2010", "company": "IBM",  
"concept":"Assets", "value": 110.00}

...повторы...

{ "date": "12/31/2014", "company": "IBM",  
"concept":"Assets", "value": 200.00} 
{ "date": "03/31/2010", "company": "IBM",  
"concept":"Current Assets", "value": 50.00}

...продолжение

Пока ничего особо сложного. Давайте создадим поисковый индекс по полю concept:

function(doc){ 
	index("default",  
		doc.company,  
		{"store": true}); 
	index("concept",  
		doc.concept,  
		{"store": true}); 
}

Пока все выглядит хорошо. Для того чтобы заполнить второе поле со списком автозаполнения на главной странице xbrl.mybluemix.net, нужно найти все концепции, указанные компанией, которые содержат определенную строку. Предположим, что мы ищем все приведенные IBM концепции, содержащие текст «активы», с ограничением до 10 результатов. URI запроса будет выглядеть примерно так:

Кликните, чтобы увидеть код

https://myaccount.cloudant.com/facts/_design/factsSearches/_search/nameSearch?q="IBM"%20AND%20name:Assets&limit=10

Фактические результаты в формате JSON пока не важны, важно то, что ответ из этого URI будет содержать следующие результаты:

{ "date": "03/31/2010", "company": "IBM",  
"concept":"Assets", "value": 100.00} 
{ "date": "06/30/2010", "company": "IBM",  
"concept":"Assets", "value": 110.00}

... повторы...

{ "date": "06/32/2012", "company": "IBM",  
"concept":"Assets", "value": 150.00}

Значения "Current Assets" здесь нет! И не должно быть. Поисковый индекс сделал именно то, что заказывали. На самом же деле мы хотели бы найти концепции со словом Assets без повторов. В случае SQL мы бы искали:

SELECT DISTINCT  
    concept  
FROM  
    facts  
WHERE  
    company='IBM'  
    AND 
    concept LIKE '%Assets%'

Очевидным решением будет увеличить лимит, скажем, до 100, а затем, прежде чем заполнить поле со списком, просто обработать список на стороне клиента или сервера, сохраняя только уникальные результаты. В нашем простом примере это сработает, но предположим, что IBM сообщила о своих активах 100 раз или 1000 раз? Cloudant устанавливает максимальный предел в 200 результатов. Если IBM опубликовала Assets 1000 раз (отметим, что это немного надуманный пример), то у нас ничего бы не вышло. Так как нам нужен только список уникальных значений одного поля (concept), мы можем использовать фасет counts. Просто перестроим индекс следующим образом:

function(doc){ 
	index("company",  
		doc.company,  
		{"store": true}); 
	index("concept",  
		doc.concept,  
		{"store": true, "facet": true}); 
}

Теперь запрос к URI выглядит так:

Кликните, чтобы увидеть код

https://myaccount.cloudant.com/facts/_design/factsSearches/_search/nameSearch?q="IBM"%20AND%20name:Assets&limit=0&counts=["name"]

Мы используем limit=0, потому что нас не интересуют реальные документы, нужен просто список уникальных значений; counts=["name"] дает нам его. Результат этого запроса в формате JSON будет выглядеть примерно так:

{"total_rows":17,"bookmark":"g2o","rows":[],"counts":{"name":{"Assets":16,"CurrentAssets":1}))

Это готовый ассоциативный массив, который нам нужен, список вариантов имен полей, содержащих слово Assets, и число появлений каждого варианта. Предостережение. При работе с фасетом counts вариант limit=x недопустим. Так что если запрос должен выбрать, например, все документы, где поле concept содержит строку «е», то ответ может занять длительное время и возвратить чрезвычайно длинный список значений.

Получение нескольких полей с помощью поискового индекса Cloudant distinct

 

В предыдущем разделе мы хотели получить уникальные результаты из поискового индекса. Это получилось, но мы не смогли получить более одного поля. Я имею в виду, что нам не удалось создать запрос, подобный следующему SQL-запросу:

SELECT DISTINCT  
    identifier,  
    name  
FROM  
    companies  
WHERE  
    name LIKE '%International%'

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

Это не проблема, если в полях используются строго различные пространства символов. Например, в одном только буквенные, а в другом только цифровые. Даже если это не так, того же можно добиться с помощью подстановки шифра. JSON-документы не ограничиваются ASCII-символами. Пространство символов UTF-8 очень велико, и при создании индекса можно просто сдвинуть одно или несколько полей в совершенно другое пространство символов.

Вот как будет выглядеть поисковый индекс (спасибо Тиму Северьену, поместившему пример шифра в GitHub, за функцию кольцевого сдвига):

Кликните, чтобы увидеть код

function rotateText(text, rotation) { 
    // Предел суррогатной пары 
    var bound = 0x10000; 

    // Принудительный перенос целого в пределах границ, на всякий случай 
    rotation = parseInt(rotation) % bound; 

    // При отсутствии изменений может также возвращать текст  
    if(rotation === 0) return text; 

    // Создание строки из кодов символов 
    return String.fromCharCode.apply(null, 
        // Превращение строки в коды символов 
        text.split('').map(function(v) { 
            // Возвращение текущего кода символа с циклическим сдвигом 
            return (v.charCodeAt() + rotation + bound) % bound; 
        }) 
    ); 
} 

function(doc){ 
    index("companyName", rotateText(doc.identifier, 500) + ' ' + doc.name, {"store": true, "facet":true}); 
}

Мы создаем индекс по объединенной строке, состоящей из зашифрованного идентификатора и исходного имени. Это означает, что пара ['0000051143′, 'INTERNATIONAL BUSINESS MACHINES CORP'] становится строкой 'ȤȤȤȤȤȩȥȥȨȧ INTERNATIONAL BUSINESS MACHINES CORP'.

Далее мы продолжаем как прежде, используя разбивку на фасеты для получения списка уникальных результатов. Затем нужно просто расшифровать результат, чтобы вернуть первоначальные идентификаторы (в данном примере, циклически сдвинуть текст на -500).Можно либо сделать очень разумное предположение, что никто не будет вводить в поле поиска символы из шифрованного пространства символов, либо не допускать поиска по таким символам.

Все результаты из поискового индекса Cloudant

 

Как мы выяснили ранее, для любого единичного вызова поискового индекса существует предел в 200 результатов. Предположим, что по какой-то причине нам нужны все результаты. Давайте посмотрим на фактической результат JSON, который мы получаем при выполнении запроса по поисковому индексу URI:

Кликните, чтобы увидеть код

{ 
	"total_rows":980, 
	"bookmark":"g1AAAAGneJzLYWBgYMtgTmGQT0lKzi9KdUhJMjTUy0zKNTCw1EvOyS9NScwr0ctLLckBKmRKZEiS____f1YGk5uD0sugB0CxJAahPbYgA-TgBhjj1J-UANJRDzfifWwD2Ah28To0NxjhNCOPBUgyNAApoDHzIebMzYKYI_bxDapTzAgYswBizH6IMV9OHwAbw6-cS6yPIMYcgBhzH2LMw9UQY9inPCDNmAcQY6CB870aakx6dRYA32qFdg", 
	"rows": 
	[ 
		{ 
			"id":"0bd8dab855b66e643350625a33d79b00", 
			"order":[9.491182327270508,925635], 
			"fields": 
			{ 
				"default":"0000789019", 
				"conceptNameSplit":"Deferred Revenue Revenue Recognized" 
			} 
		} 
		... continues ... 
	] 
}

Обратите внимание на первое поле результата, "total_rows". Cloudant известно, сколько результатов имеется. Однако она предоставляет их только пакетами по 200. Теперь обратите внимание на второе поле результата, "bookmark". Чтобы получить следующие n результатов, достаточно просто обратиться к тому же URI с параметром "bookmark=", используя закладку из предыдущих n результатов. Благодаря чудесам Node.js, потоковой технологии и рекурсии, мы можем рассмотреть простой метод, который заключается в потоковой передаче каждого результата с последующим получением списка из n уникальных результатов. Во-первых, нам понадобится поток, который может извлекать значение закладки и сохранять его в переменной, передаваемой по ссылке.

stream = require('stream') 
 
class exports.JSONArrayTransformStream extends stream.Transform 
 
  constructor: () -> 
    @first = true 
    super 
      objectMode: true 
 
  _transform: (chunk, enc, next) -> 
 
    if (@first) 
      @push('[') 
      @first  = false 
    else 
      @push(',\n') 
 
    @push(JSON.stringify(chunk)) 
 
    next() 
 
  _flush: (next) -> 
    if (@first) 
      @push('[]') 
    else 
      @push(']') 
    next()

Затем нам понадобится поток, который принимает входящие объекты и выводит их в виде массива JSON. Это предполагает, что мы куда-то посылаем результаты, например, в ответный поток, файл или на консоль.

stream = require('stream') 
 
class exports.JSONArrayTransformStream extends stream.Transform 
 
  constructor: () -> 
    @first = true 
    super 
      objectMode: true 
 
  _transform: (chunk, enc, next) -> 
 
    if (@first) 
      @push('[') 
      @first  = false 
    else 
      @push(',\n') 
 
    @push(JSON.stringify(chunk)) 
 
    next() 
 
  _flush: (next) -> 
    if (@first) 
      @push('[]') 
    else 
      @push(']') 
    next()

Наконец, мы используем рекурсивную функцию для последовательного выполнения каждого вызова API с применением закладки из предыдущего вызова. Здесь будет очень полезным пакет JSONStream.

stream = require('stream') 
 
class exports.JSONArrayTransformStream extends stream.Transform 
 
  constructor: () -> 
    @first = true 
    super 
      objectMode: true 
 
  _transform: (chunk, enc, next) -> 
 
    if (@first) 
      @push('[') 
      @first  = false 
    else 
      @push(',\n') 
 
    @push(JSON.stringify(chunk)) 
 
    next() 
 
  _flush: (next) -> 
    if (@first) 
      @push('[]') 
    else 
      @push(']') 
    next()

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

stream = require('stream') 
 
class exports.DistinctingTransformStream extends stream.Transform 
 
  #@keySelector: имя поля в объекте, уникальные значения которого мы ищем 
  #@limit: останов после нахождения такого количества уникальных значений 
  constructor: (@keySelector, @limit) -> 
    @myArray = {} 
    super 
      objectMode: true 
 
  _transform: (chunk, enc, next) -> 
 
    if (Object.keys(@myArray).length < @limit) 
      @myArray[chunk[@keySelector]] = chunk[@keySelector] 
 
    next() 
 
  _flush: (next) -> 
 
    for k,v of @myArray 
      @push(v) 
    next()

Заключение

 

В этом руководстве показано, как с помощью Node.js построить корпоративную базу данных Cloudant для финансовых данных. А также как создать простой пользовательский интерфейс к этим данным в виде веб-инструмента для построения диаграмм. Кроме того, мы рассмотрели некоторые уроки из опыта работы с большими объемами данных и поиска уникальных результатов в таких наборах данных.

Приложение. Как открыть панель управления Cloudant

 
  1. Щелкните на своем приложении на панели управления Bluemix. Вы увидите экран, аналогичный показанному ниже. Панель управления Bluemix

    Кликните, чтобы увидеть увеличенное изображение

  2. Щелкните на значке Cloudant.
  3. На следующем экране нажмите кнопку Launch. Кнопка Launch

    Кликните, чтобы увидеть увеличенное изображение

Добавить комментарий

Внимание: HTML элементы не поддерживаются в комментариях.


осталось 1000 символов

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Большие данные и аналитика, Облачные вычисления
ArticleID=1029989
ArticleTitle=Создание с помощью Bluemix и Cloudant корпоративной базы данных для финансовых данных, требуемых SEC
publish-date=04132016