Экспорт пользовательских измерений событий в SQL из Application Insights с помощью Stream Analytics

Я следую примеру пошагового руководства Export to SQL из Application Insights с помощью Stream Analytics. Я пытаюсь экспортировать пользовательские измерения событий (context.custom.dimensions в приведенном ниже примере JSON), которые добавляются как вложенный массив JSON в файл данных. Как сгладить массив измерений в context.custom.dimensions для экспорта в SQL?

JSON...

{
  "event": [
    {
      "name": "50_DistanceSelect",
      "count": 1
    }
  ],
  "internal": {
    "data": {
      "id": "aad2627b-60c5-48e8-aa35-197cae30a0cf",
      "documentVersion": "1.5"
    }
  },
  "context": {
    "device": {
      "os": "Windows",
      "osVersion": "Windows 8.1",
      "type": "PC",
      "browser": "Chrome",
      "browserVersion": "Chrome 43.0",
      "screenResolution": {
        "value": "1920X1080"
      },
      "locale": "unknown",
      "id": "browser",
      "userAgent": "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.134 Safari/537.36"
    },
    "application": {},
    "location": {
      "continent": "North America",
      "country": "United States",
      "point": {
        "lat": 38.0,
        "lon": -97.0
      },
      "clientip": "0.115.6.185",
      "province": "",
      "city": ""
    },
    "data": {
      "isSynthetic": false,
      "eventTime": "2015-07-15T23:43:27.595Z",
      "samplingRate": 0.0
    },
    "operation": {
      "id": "2474EE6F-5F6F-48C3-BA43-51636928075A"
    },
    "user": {
      "anonId": "BA05C4BE-1C42-482F-9836-D79008E78A9D",
      "anonAcquisitionDate": "0001-01-01T00:00:00Z",
      "authAcquisitionDate": "0001-01-01T00:00:00Z",
      "accountAcquisitionDate": "0001-01-01T00:00:00Z"
    },
    "custom": {
      "dimensions": [
        {
          "CategoryAction": "click"
        },
        {
          "SessionId": "73ef454d-fa39-4125-b4d0-44486933533b"
        },
        {
          "WebsiteVersion": "3.0"
        },
        {
          "PageSection": "FilterFind"
        },
        {
          "Category": "EventCategory1"
        },
        {
          "Page": "/page-in-question"
        }
      ],
      "metrics": []
    },
    "session": {
      "id": "062703E5-5E15-491A-AC75-2FE54EF03623",
      "isFirst": false
    }
  }
}

person viperguynaz    schedule 20.07.2015    source источник
comment
единственный возможный сейчас способ — написать свою собственную рабочую роль и анализировать данные каждый час. github.com/Azure/azure-content/blob/master/articles/   -  person Riccardo    schedule 23.07.2015


Ответы (5)


Чуть более динамичное решение — настроить временную таблицу:

WITH ATable AS (
SELECT
     temp.internal.data.id as ID
    ,dimensions.ArrayValue.CategoryAction as CategoryAction
    ,dimensions.ArrayValue.SessionId as SessionId 
    ,dimensions.ArrayValue.WebsiteVersion as WebsiteVersion 
    ,dimensions.ArrayValue.PageSection as PageSection 
    ,dimensions.ArrayValue.Category as Category 
    ,dimensions.ArrayValue.Page as Page  
FROM [analyticseventinputs] temp 
CROSS APPLY GetElements(temp.[context].[custom].[dimensions]) as dimensions)

а затем выполнять соединения на основе уникального ключа

FROM [analyticseventinputs] Input 
Left JOIN ATable CategoryAction on 
    Input.internal.data.id = CategoryAction.ID AND
    CategoryAction.CategoryAction <> "" AND
     DATEDIFF(day, Input, CategoryAction) BETWEEN 0 AND 5 

Довольно раздражающим моментом является требование для datediff, потому что соединения предназначены для объединения двух потоков данных, но в этом случае вы соединяетесь только по уникальному ключу. Поэтому я установил большое значение 5 дней. Это действительно защищает только от того, что пользовательские параметры не упорядочены по сравнению с другим решением.

person Xinco24    schedule 27.07.2015
comment
Это работает! Спасибо. Надеемся, что обработка массивов, которая появится в августе, облегчит эту задачу. - person viperguynaz; 29.07.2015

Большинство руководств в Интернете используют CROSS APPLY или OUTER APPLY, однако это не то, что вам нужно, потому что каждое свойство будет помещено в другую строку. Чтобы преодолеть это, используйте функции: GetRecordPropertyValue и GetArrayElement, как показано ниже. Это объединит свойства в одну строку.

SELECT
    GetRecordPropertyValue(GetArrayElement(MySource.context.custom.dimensions, 0), 'CategoryAction') AS CategoryAction,
    GetRecordPropertyValue(GetArrayElement(MySource.context.custom.dimensions, 1), 'SessionId') AS SessionId,
    GetRecordPropertyValue(GetArrayElement(MySource.context.custom.dimensions, 2), 'WebsiteVersion') AS WebsiteVersion,
    GetRecordPropertyValue(GetArrayElement(MySource.context.custom.dimensions, 3), 'PageSection') AS PageSection,
    GetRecordPropertyValue(GetArrayElement(MySource.context.custom.dimensions, 4), 'Category') AS Category,
    GetRecordPropertyValue(GetArrayElement(MySource.context.custom.dimensions, 5), 'Page') AS Page
INTO
  [outputstream]
FROM
  [inputstream] MySource
person Soto    schedule 17.12.2015
comment
Пожалуйста, добавьте несколько комментариев к вашему ответу. - person kvorobiev; 17.12.2015
comment
Что делать, если порядок или количество измерений меняется? - person Eugene D. Gubenkov; 08.02.2016
comment
Я пометил это как ответ, потому что 1) решение Join от Xinco24 кажется менее эффективным, чем этот подход - особенно если у вас много полей, 2) оно более компактно, чем использование оператора CASE, как в решении Константина Зорина и 3) порядок полей массива измерений на входе JSON не изменится. - person samneric; 15.02.2016
comment
Евгений Д.Губенков, если количество измерений может варьироваться, то можно использовать комбинацию описанного выше метода с примером постановки дела Константином Зориным. - person Soto; 16.02.2016
comment
Проблема, с которой я сталкиваюсь, заключается в том, что порядок измерений меняется, поэтому я не могу получить доступ по индексу. - person Mario Levrero; 27.09.2016
comment
@MarioLevrero: вы нашли решение с различным порядком размеров? У меня точно такая же проблема! - person Dave New; 18.10.2016
comment
@davenewza: Когда я изначально разрабатывал свой запрос, размеры не меняли порядок, но сегодня они изменились. Мне пришлось использовать подход JOIN от Xinco24. - person samneric; 18.10.2016
comment
@davenewza, я использую case/else, чтобы проверить правильность порядка измерений, но запрос экспоненциально становится уродливым :( Я также спрашивал на форуме AI без ответа - person Mario Levrero; 19.10.2016

Какая схема у вас есть в SQL? Вам нужна одна строка в SQL со всеми измерениями в виде столбцов?

Сегодня это может быть невозможно. Однако после 30 июля в Azure Stream Analytics будет больше функций массива/записи.

Тогда вы сможете сделать что-то вроде этого:

SELECT 
    CASE 
        WHEN GetArrayLength(A.context.custom.dimensions) > 0
            THEN GetRecordPropertyValue(GetArrayElement(A.context.custom.dimensions, 0), 'CategoryAction')
        ELSE ''
        END AS CategoryAction 
    CASE 
        WHEN GetArrayLength(A.context.custom.dimensions) > 1
            THEN GetRecordPropertyValue(GetArrayElement(A.context.custom.dimensions, 1), 'WebsiteVersion')
        ELSE ''
        END AS WebsiteVersion 
    CASE 
        WHEN GetArrayLength(A.context.custom.dimensions) > 2
            THEN GetRecordPropertyValue(GetArrayElement(A.context.custom.dimensions, 2), 'PageSection')
        ELSE ''
        END AS PageSection
FROM input

Если вы хотите иметь отдельные строки для каждого измерения, вы можете использовать оператор CROSS APPLY.

person Konstantin Zoryn    schedule 22.07.2015
comment
Да, мне нужна одна строка в SQL со всеми измерениями в виде столбцов. Думаю, придется подождать :-( - person viperguynaz; 23.07.2015
comment
С этим решением кажется, что вы знаете точное положение пользовательских параметров в массиве. что делать, если пользовательские параметры не упорядочены? - person Riccardo; 23.07.2015
comment
извините - пришлось обновить ответ - Xinco24 предоставил рабочее решение. - person viperguynaz; 29.07.2015
comment
Для кого-то это может быть очевидно, но сегодня меня это огорчило. Если в именах ваших измерений есть точки, вам нужно использовать обычный синтаксис SQL, в котором вы заключаете имена в скобки в своем вызове GetRecordPropertyValue. то есть '[AppName.Metric]' - person Nate Jackson; 28.12.2015

Очень удобный способ сделать это, предложенный Алексом Райзманом, состоит в том, чтобы выполнить некоторые агрегации для полей, которые вы хотите сгладить, группируя по оставшемуся списку выбора, предполагая, что

  • вы знаете набор возможных объектов в измерениях, и
  • у вас нет повторяющихся объектов в этом массиве, и
  • есть что-то, что вы можете однозначно идентифицировать ваши начальные строки (например, id )

    SELECT
      CategoryAction= MIN(CAST(GetRecordPropertyValue(d.arrayvalue, 'CategoryAction') AS
      NVARCHAR(MAX))),
      SessionId= min(CAST(GetRecordPropertyValue(d.arrayvalue, 'SessionId') AS
      NVARCHAR(MAX))),
      WebsiteVersion= MIN(CAST(GetRecordPropertyValue(d.arrayvalue, 'WebsiteVersion') AS
      NVARCHAR(MAX))),
      PageSection= MIN(CAST(GetRecordPropertyValue(d.arrayvalue, 'PageSection') AS
      NVARCHAR(MAX))),
      Category= MIN(CAST(GetRecordPropertyValue(d.arrayvalue, 'Category') AS
      NVARCHAR(MAX))),    
      Page= MIN(CAST(GetRecordPropertyValue(d.arrayvalue, 'Page') AS NVARCHAR(MAX))) 
    INTO  
      [outputstream] 
    FROM [inputstream] MySource 
    CROSS APPLY GetArrayElements(MySource.[context].[custom].[dimensions]) d 
    GROUP BY System.Timestamp, MySource.id
    

Мы также группируем по System.Timestamp, чтобы создать одно временное окно, как ожидается от Stream Analytics, для выполнения операций на основе наборов, таких как подсчет или агрегирование.

person athina.bikaki    schedule 27.09.2018

Хотя вопрос старый. Но именно так достигается одна строка для пользовательских размеров. Это может стать уродливым по мере увеличения количества настраиваемых параметров.

    SELECT
    A.internal.data.id,        
    eventFlat.ArrayValue.name as eventName,
    A.context.operation.name as operation,
    A.context.data.eventTime,
    a1.company,
    a2.userId,
    a3.feature,        
    A.context.device,    
    A.context.location         
FROM [YourInputAlias] A   
OUTER APPLY GetArrayElements(A.event) eventFlat  
LEFT JOIN (
        SELECT 
        A1.internal.data.id as id,   
        customDimensionsFlat.ArrayValue.company
      FROM [YourInputAlias] A1  
      OUTER APPLY GetArrayElements(A1.context.custom.dimensions) customDimensionsFlat   
      where  customDimensionsFlat.ArrayValue.company IS NOT NULL
      ) a1 ON a.internal.data.id = a1.id AND datediff(day, a, a1) between 0 and 5
LEFT JOIN (
        SELECT 
        A2.internal.data.id as id,   
        customDimensionsFlat.ArrayValue.userid     
      FROM [YourInputAlias] A2  
      OUTER APPLY GetArrayElements(A2.context.custom.dimensions) customDimensionsFlat    
      where  customDimensionsFlat.ArrayValue.userid  IS NOT NULL
      ) a2 ON a.internal.data.id = a2.id AND datediff(day, a, a2) between 0 and 5
LEFT JOIN (
        SELECT 
        A3.internal.data.id as id,   
        customDimensionsFlat.ArrayValue.feature     
      FROM [YourInputAlias] A3
      OUTER APPLY GetArrayElements(A3.context.custom.dimensions) customDimensionsFlat    
      where  customDimensionsFlat.ArrayValue.feature  IS NOT NULL
      ) a3 ON a.internal.data.id = a3.id AND datediff(day, a, a3) between 0 and 5
person Dharmesh Tailor    schedule 20.05.2020