---
title: "Query"
description: "<p>Schema management api to add/edit queries and user-fields.</p><p>Lytics Query Language</p><p>=============================</p><p>The Lytics Query Language is used to define the transformation of uploaded</p><p>records, and event data into user Profiles.  It transforms row-level event data into Document-oriented User info.</p><p>This Query langage is similar to the HIVE or SQL query langauges, however departs from these in</p><p>order to offer more of a *Rich Document* (json user profile) construction.</p><p>**Query example**</p><p>``<span data-type=\"inlineCode\" class=\"code\"></p><p># Build a user from web data</p><p>SELECT</p><p>    name                       -- Simple field, by default = string</p><p>    , age            KIND INT  -- cast field as int</p><p>    , last_visit_ts  KIND DATE -- cast as date</p><p>    -- Showing the aggregate counter function and aliasing name of output column AS</p><p>    , count(_ref)         AS ref_ct</p><p>    -- Valuect makes a map[string]int count of occurences of a key</p><p>    , valuect(</span>my field<span data-type=\"inlineCode\" class=\"code\">) AS myfield_mapct</p><p>    -- showcase every optional syntax element in column</p><p>    --     meregeop oldest we don't want to over-write this value, keep oldest</p><p>    --     KIND INT   normally we don't have to cast as most functions have a specific type</p><p>    , amt AS first_order_amount</p><p>          IF  event == \"cart checkout\"</p><p>          SHORTDESC \"Amount of First Order\"</p><p>          LONGDESC \"Amount of First Order\"</p><p>          KIND INT</p><p>          MERGEOP OLDEST</p><p>    -- lets keep around the date at which they signed up (mergeop oldest)</p><p>    , now()               AS signedup_date                  IF event == \"signed up\"    KIND DATE  MERGEOP oldest</p><p>    -- maps:   map all fields that start with \"user.\" into a fact map</p><p>    , match(\"user.\")        AS user_attributes     KIND map[string]string</p><p>    -- list of strings</p><p>    , set(event) AS all_events</p><p>    -- Identified By Columns allow merging across streams</p><p>    , email(EmailAddress) AS email</p><p>    , _uid</p><p>    , fbuid</p><p>FROM</p><p>    default</p><p>INTO</p><p>    user</p><p>BY</p><p>    _uid OR email OR fbuid</p><p>WHERE</p><p>    _bot = \"f\" OR NOT EXISTS _bot</p><p>ALIAS</p><p>    web_user;</p><p># validate the query</p><p>curl -s -XPOST \"https://api.lytics.io/api/query/_validate\" \\</p><p>   -H \"Authorization: $LIOKEY\" \\</p><p>   -H \"Content-Type: text/plain\" \\</p><p>   --data-binary @/tmp/tmp.lql | jq '.'</p><p># upload the query</p><p>curl -s -XPOST \"https://api.lytics.io/api/query\" \\</p><p>   -H \"Authorization: $LIOKEY\" \\</p><p>   -H \"Content-Type: text/plain\" \\</p><p>   --data-binary @your_file.lql | jq '.'</p><p># look at schema it output:</p><p>curl -s -H \"Authorization: $LIOKEY\" \\</p><p> -XGET \"https://api.lytics.io/api/schema/user\" | jq '.'</p><p></span>`<span data-type=\"inlineCode\" class=\"code\"></p><p>**Standard Syntax**</p><p></span>`<span data-type=\"inlineCode\" class=\"code\"></p><p>Select = \"SELECT\" COLUMNS FROM INTO BY [WHERE] ALIAS</p><p># required from, the stream to operate on for this query</p><p>FROM = \"FROM\" Identifier</p><p># Required Identified By field, name of column \"AS\" from Column</p><p>BY = \"BY\" Identifier [\"OR\" Identifier]</p><p># Required Alias for giving a query a unqique identifier</p><p>ALIAS = \"ALIAS\" Identifier</p><p># Optional Where Filter, same as SQL where</p><p>WHERE = \"WHERE\" LogicalExpression</p><p>COLUMNS         = COLUMN [, COLUMN]</p><p>COLUMN     = Expression [\"AS\" Identifier]</p><p>     [\"IF\" LogicalExpression] [\"SHORTDESC\" String]</p><p>     [\"LONGDESC\" String] [\"KIND\" Kind] [\"MERGEOP\" MergeOp]</p><p>LogicalExpression = NOT</p><p>           | Comparison</p><p>           | EXISTS</p><p>           | IN</p><p>           | CONTAINS</p><p>           | LIKE</p><p>           | Function</p><p>           | Expression</p><p>           | \"(\" LogicalExpression \")\"</p><p>           | LogicalExpression OR LogicalExpression</p><p>           | LogicalExpression AND LogicalExpression</p><p>Expression =</p><p>    Identifier</p><p>    | Function</p><p>    | Literal</p><p>Function = Identifier \"(\" Expression [, Expression] \")\"</p><p>NOT            = \"NOT\" LogicalExpression</p><p>Comparison     = Identifier ComparisonOp Literal</p><p>ComparisonOp   = \">\" | \">=\" | \"&lt;\" | \"&lt;=\" | \"==\" | \"!=\"</p><p>EXISTS         = \"EXISTS\" Identifier</p><p>IN             = Identifier \"IN\" (Literal, Literal, ...)</p><p>CONTAINS       = Identifier \"CONTAINS\" Literal</p><p>LIKE           = Identifier \"LIKE\" String # uses * for wildcards</p><p>Literal = String | Int | Float | Bool | Timestamp</p><p>Identifier = [a-zA-Z][a-zA-Z0-9_]+ | \"</span>\" + String + \"<span data-type=\"inlineCode\" class=\"code\">\"</p><p>Kind = \"int\" | \"number\" | \"string\" | \"date\" | \"[]string\" |</p><p>    \"ts[]string\" | \"map[string]int\" | \"map[string]number\" | \"map[string]string*</p><p># MergeOp's are very seldom used and have to be used on the right Kind</p><p>#  ie string can use Latest, Oldest (but not min, max)</p><p>MergeOp =  \"max\" | \"min\" | \"latest\" | \"oldest\" | \"mapmax\"</p><p></span>`<span data-type=\"inlineCode\" class=\"code\"></p><p>* **SELECT** Select data to be added to user profiles.   Including Maps, Counts, and other complex data types.</p><p>* **FROM** The stream to select from</p><p>* **INTO** This is </span>USER<span data-type=\"inlineCode\" class=\"code\"> for all user profiles.   (technically you could create other types, such as \"account\")</p><p>* **WHERE** Filters out entire records to not be included/analyzed.  Bots, Employees, Test data.</p><p>* **BY** What field are we going to identify this entity by</p><p>* **ALIAS** When a selection query has an alias, that is the profile-fragment(table) name to use</p><p>Functions</p><p>--------------------------------</p><p>There are a variety of functions for transformation and logic evaluation.</p><p>**Aggregate Functions**</p><p>There are a variety of expressions for building document type structures (maps, lists, sets).</p><p>These are functional expressions but can only be used in Columns.</p><p>- **cap**  Limit the items stored in a field by count or by date. </span>cap(field, int)<span data-type=\"inlineCode\" class=\"code\"> </span>cap(field, \"number_of_days\")<span data-type=\"inlineCode\" class=\"code\"> (e.g. </span>cap(field, \"30d\")<span data-type=\"inlineCode\" class=\"code\">). Returns an array containing the values within the capped count or length of time.</p><p>- **count**   Count of this key.  For instance, count occurences of sessions that have started (ie, visited web site).</p><p>- **set**  Create a unique list/array of each value we have seen from this field</p><p>- **min,max**  Minimum or Maxium value (for numerics)</p><p>- **sum** Sum values (keep track of total video play time, etc)</p><p>**Logical Functions**</p><p>Local Evaluation, return boolean true/false.</p><p>- **all** checks for existince of n keys </span>all(key1,key2,key3,...)<span data-type=\"inlineCode\" class=\"code\"> returns boolean.</p><p>- **any** accepts a list of values and returns True if any are the contents of a field  </span>any(fieldname, value1,value2,value3) <span data-type=\"inlineCode\" class=\"code\"></p><p>- **exists** Check for field (aka key) existence.</p><p>    * </span>exists(purchase_total)<span data-type=\"inlineCode\" class=\"code\"> checks to see if </span>purchase_total<span data-type=\"inlineCode\" class=\"code\"> is defined for the current message</p><p>    * </span>valuect(yymm())        AS visits_by_yymm    IF exists(_sesstart) <span data-type=\"inlineCode\" class=\"code\"> Only fires </span>valuect(yymm())<span data-type=\"inlineCode\" class=\"code\"> if </span>_sesstart<span data-type=\"inlineCode\" class=\"code\"> exists</p><p>- **in** Determines if a field value is in a set of values.</p><p>    * </span>\"t\" AS is_student IF role_type IN (\"student\",\"other\")<span data-type=\"inlineCode\" class=\"code\"></p><p>    * </span>dailyContact          AS dailyContact   IF dailyContact IN (\"student\",\"other\")<span data-type=\"inlineCode\" class=\"code\"></p><p>- **eq**  Equal to       </span>eq(domain,\"google.com\")<span data-type=\"inlineCode\" class=\"code\"></p><p>- **ne**  Not Equal to    </span>ne(domain,\"google.com\")<span data-type=\"inlineCode\" class=\"code\"></p><p>- **lt**  Less Than    </span>lt(seconds(video_time), 30)<span data-type=\"inlineCode\" class=\"code\"></p><p>- **le**  Less Than or Equal to    </span>le(seconds(video_time), 30)<span data-type=\"inlineCode\" class=\"code\"></p><p>- **gt**  Greater Than              </span>gt(seconds(video_time), 30)<span data-type=\"inlineCode\" class=\"code\"></p><p>- **ge**  Greater Than or Equal to    </span>ge(seconds(video_time), 30)<span data-type=\"inlineCode\" class=\"code\"></p><p>- **not** Not                         </span>not(exists(domain))<span data-type=\"inlineCode\" class=\"code\"></p><p>- **or**  Or                          </span>or(exists(domain), contains(domain,\"google.com\")) AS from_google<span data-type=\"inlineCode\" class=\"code\"></p><p>**String Functions**</p><p>- **join** Join together multiple values, coerce them into strings.  Last argument is which string to use to join (may be empty string).</p><p>    * </span>join(\"apples\",\"oranges\",\",\")   => \"apples,oranges\"<span data-type=\"inlineCode\" class=\"code\"></p><p>    * </span>join(\"apples\",\"oranges\",\"\")   => \"applesoranges\"<span data-type=\"inlineCode\" class=\"code\"></p><p>- **len**  Length (of array, string)</p><p>- **oneof** Choose value from the first field that has a non nil value.</p><p>    * </span>oneof(fielda,fieldb,fieldc)<span data-type=\"inlineCode\" class=\"code\"></p><p>- **replace** - Replace a matching part of a string with an empty string.  Converts to string first.</p><p>    * </span>replace(url,\"/search/apachesolr_search/\")<span data-type=\"inlineCode\" class=\"code\"> - Removes </span>/search/apachesolr_search/<span data-type=\"inlineCode\" class=\"code\"> from URL (in this case, leaving the search term</p><p>- **split** Breaks a variable into smaller fragments given a specific delimiter</p><p>    * </span>split(cc,\",\")<span data-type=\"inlineCode\" class=\"code\"> - Splits the variable </span>cc<span data-type=\"inlineCode\" class=\"code\"> at each comma it contains</p><p>- **strip(field)** Strips leading and trailing whitespace (spaces, tabs, newline, carriage-return) from string, or arrays of strings.</p><p>- **string.lowercase** Convert strings to lower case</p><p>- **string.uppercase** Convert strings to upper case</p><p>- **string.titlecase** Convert strings to title case</p><p>- **contains**  Does this value contain this string?  Is a sub-string match, not full match (eq)</p><p>    * </span>IF contains(total_price, \"$\")<span data-type=\"inlineCode\" class=\"code\"> - Check to see if </span>total_price<span data-type=\"inlineCode\" class=\"code\"> has a </span>$<span data-type=\"inlineCode\" class=\"code\"> in it</p><p>    * </span>IF not(contains(subscriber_key,\"-\")) AND not(contains(subscriber_key,\"@\"))<span data-type=\"inlineCode\" class=\"code\"> check to make sure </span>-<span data-type=\"inlineCode\" class=\"code\"> or </span>@<span data-type=\"inlineCode\" class=\"code\"> is not in it.</p><p>- **hasprefix**  Does this value start with this string?</p><p>    * </span>hasprefix(event, \"created\")<span data-type=\"inlineCode\" class=\"code\"> - Check to see if </span>event<span data-type=\"inlineCode\" class=\"code\"> starts with \"created\"</p><p>- **hassuffix**  Does this value start with this string?</p><p>    * </span>hassuffix(subscriber_key, \"user\")<span data-type=\"inlineCode\" class=\"code\"> - Check to see if </span>subscriber_key<span data-type=\"inlineCode\" class=\"code\"> ends with \"user\"</p><p>**Hash &amp; Encoding Functions**</p><p>- **hash.sip** </span>hash.sip(email)<span data-type=\"inlineCode\" class=\"code\"> Hash the given value using sip hash to integer output.</p><p>- **hash.md5** </span>hash.md5(email)<span data-type=\"inlineCode\" class=\"code\"> Hash the given value using md5</p><p>- **hash.sha1** </span>hash.sha1(email)<span data-type=\"inlineCode\" class=\"code\"> Hash the given value using sha1</p><p>- **hash.sha256** </span>hash.sha256(email)<span data-type=\"inlineCode\" class=\"code\"> Hash the given value using sha256</p><p>- **hash.sha512** </span>hash.sha512(email)<span data-type=\"inlineCode\" class=\"code\"> Hash the given value using sha512</p><p>- **encoding.b64encode(field)** base64 encode.</p><p>- **encoding.b64decode(field)** base64 decode.</p><p>**Cast &amp; Convert**</p><p>- **toint** Converts strings to integers.  Useful for converting a string to a number before applying a number-based expression.</p><p>    * </span>toint(order_total)<span data-type=\"inlineCode\" class=\"code\"> - Converts </span>order_total<span data-type=\"inlineCode\" class=\"code\"> to an int</p><p>    * </span>set(toint(split(cc,\",\")))<span data-type=\"inlineCode\" class=\"code\"> - Takes the field </span>cc<span data-type=\"inlineCode\" class=\"code\"> and splits it at commas, and converts the results to integers.  Then adds them to a set.</p><p>- **tonumber** Convert to Number</p><p>- **todate** Converts strings to dates, see full doc in Date/Time section below.</p><p>- **tobool(field)** Cast to Boolean.</p><p>**Map &amp; Set/Array Functions**</p><p>- **filter** Filter out Values that match specified list of match filter criteria</p><p>    * </span>filter(split(\"apples,oranges\",\",\"),\"ora*\")  => [\"apples\"]<span data-type=\"inlineCode\" class=\"code\"></p><p>- **len**  Length (of array, string)</p><p>- **map** Type: Map  </span>map(key1, todate(date_field))<span data-type=\"inlineCode\" class=\"code\"></p><p>    * </span>map(key1, todate(date_field))  KIND map[string]time <span data-type=\"inlineCode\" class=\"code\">  By default the </span>map<span data-type=\"inlineCode\" class=\"code\"> is generic map, cast to map[string]time with</p><p>- **match** Type:  Map (generic map, use KIND to cast) Match a key, and then keep a map of key/values with the match value removed</p><p>    * </span>, match(\"topic_\")           AS global       KIND map[string]number<span data-type=\"inlineCode\" class=\"code\"></p><p>- **mapkeys** Type:  Map input, []string{} output. Given a map, return a list of string of each of the keys.</p><p>- **mapvalues** Type:  Map input, []string{} output. Given a map, return a list of string values of each of the values.</p><p>- **mapinvert** Type:  Map input, MapString output. Given a map, return a map[string]string inverting keys/values.</p><p>- **array.index**  Cherry pick a single item out of an array:</p><p>    * </span>array.index(split(\"apples,oranges,peaches\",\",\"),1)  => [\"oranges\"]<span data-type=\"inlineCode\" class=\"code\"></p><p>- **array.slice**  Slice an array of items selecting some sub-set of them.</p><p>    * </span>array.slice(split(\"apples,oranges,peaches,pineapple\",\",\"),2)  => [\"peaches\",\"pineapple\"]<span data-type=\"inlineCode\" class=\"code\"></p><p>    * </span>array.slice(split(\"apples,oranges,peaches,pineapple\",\",\"),1,3)  => [\"oranges\",\"peaches\"]<span data-type=\"inlineCode\" class=\"code\"></p><p>**Url/Http &amp; Email Functions**</p><p>- **email** Extract email address from \"</span>Bob &lt;emailaddress@lytics.io><span data-type=\"inlineCode\" class=\"code\">\" format</p><p>- **emailname** Extract *Bob* from \"</span>Bob &lt;emailaddress@gmail.com><span data-type=\"inlineCode\" class=\"code\">\" or </span>email@gmail.com<span data-type=\"inlineCode\" class=\"code\"></p><p>- **emaildomain** Extract *gmail.com* from \"</span>Bob &lt;emailaddress@gmail.com><span data-type=\"inlineCode\" class=\"code\">\" or </span>email@gmail.com<span data-type=\"inlineCode\" class=\"code\"></p><p>- **domain** Extract domain from url</p><p>- **host** Extract host from url</p><p>- **path** Extract the url path from url (no query string or domain), must be valid url parserable string.</p><p>- **qs**  Extract the querystring parameter from url </span>qs(urlfield, \"nameOfParam\")<span data-type=\"inlineCode\" class=\"code\"></p><p>    * </span>qs(url, \"mc_eid\")<span data-type=\"inlineCode\" class=\"code\"> - Extracts the MailChimp user ID</p><p>    * </span>set(qs(url, \"video_id\")<span data-type=\"inlineCode\" class=\"code\"> - Creates a set of </span>video_id<span data-type=\"inlineCode\" class=\"code\"></p><p>    * </span>qs(tolower(url), \"riid\")<span data-type=\"inlineCode\" class=\"code\"> - Converts the complete URL to lowercase before attempting to match</p><p>    * </span>email(oneof(email, qs(url, \"email\")))<span data-type=\"inlineCode\" class=\"code\"> - Attempts to get the email address from the URL and from the regular fields, chooses whichever is populated and treats it like an email field</p><p>- **urldecode** Perform URL decode on a field. </span>urldecode(field)<span data-type=\"inlineCode\" class=\"code\"></p><p>    * If </span>field<span data-type=\"inlineCode\" class=\"code\"> contains \"my%20value\", </span>urldecode(field)<span data-type=\"inlineCode\" class=\"code\"> will return \"my value\"</p><p>- **urlminusqs** The url minus the querystring portion</p><p>- **useragent** Extract info from user-agent string.   Below examples based on  </span>Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.97 Safari/537.11<span data-type=\"inlineCode\" class=\"code\"></p><p>    * </span>useragent(user_agent, \"bot\")<span data-type=\"inlineCode\" class=\"code\"> - Extracts True/False is this a bot?</p><p>    * </span>useragent(user_agent, \"mobile\")<span data-type=\"inlineCode\" class=\"code\"> - Extracts True/False is this mobile?</p><p>    * </span>useragent(user_agent, \"mozilla\")<span data-type=\"inlineCode\" class=\"code\"> - Extracts \"5.0\"</p><p>    * </span>useragent(user_agent, \"platform\")<span data-type=\"inlineCode\" class=\"code\"> - Extracts \"X11\"</p><p>    * </span>useragent(user_agent, \"os\")<span data-type=\"inlineCode\" class=\"code\"> - Extracts \"Linux x86_64\"</p><p>    * </span>useragent(user_agent, \"engine\")<span data-type=\"inlineCode\" class=\"code\"> - Extracts \"Linux x86_64\"</p><p>    * </span>useragent(user_agent, \"engine_version\")<span data-type=\"inlineCode\" class=\"code\"> - Extracts \"AppleWebKit\"</p><p>    * </span>useragent(user_agent, \"browser\")<span data-type=\"inlineCode\" class=\"code\"> - Extracts \"Chrome\"</p><p>    * </span>useragent(user_agent, \"browser_version\")<span data-type=\"inlineCode\" class=\"code\"> - Extracts \"23.0.1271.97\"</p><p>- **useragent.map(field)** Extract map of all of above.</p><p>**Date &amp; Time Functions**</p><p>Our core date parser recognizes about 50 date formats, so in general these will operate on _any_ format.</p><p>If you are using EU dates, you will need to specify the parser format.</p><p>- **dayofweek** Type: Integer.  0-6 integer of day of week.</p><p>    * Examples:  </span>dayofweek() => 4<span data-type=\"inlineCode\" class=\"code\">  OR  </span>dayofweek(mydatefield)<span data-type=\"inlineCode\" class=\"code\"></p><p>- **epochms** Type: Integer.  Unix MS of the date stamp on the current message being processed</p><p>- **extract**  Can be used to extract parts of date and time.  Example usage on the [strftime](http://strftime.org/) site</p><p>    * </span>extract(reg_date, \"%B\")<span data-type=\"inlineCode\" class=\"code\"> Returns name of month</p><p>    * </span>extract(reg_date, \"%d\")<span data-type=\"inlineCode\" class=\"code\"> Returns day of month</p><p>- **hourofday** Type: Integer.  Hour of day (in 24 hour utc time).     </span>hourofday()<span data-type=\"inlineCode\" class=\"code\">  OR </span>hourofday(field)<span data-type=\"inlineCode\" class=\"code\"></p><p>- **hourofweek** 0-167 integer for hour of week</p><p>- **mm** Type:  Integer.  0-11 month  (alias for monthofyear)   </span>mm()<span data-type=\"inlineCode\" class=\"code\"> => current month, 6 for june, </span>mm(my_date_field)<span data-type=\"inlineCode\" class=\"code\"></p><p>- **monthofyear** Type:  Integer  Output the 0-11 month value</p><p>- **now** Type:  Date   The current message/event times.</p><p>- **seconds** Type: Integer.  Seconds, extracts things like </span>seconds(\"00:30\") => 30<span data-type=\"inlineCode\" class=\"code\"> and </span>seconds(\"10:30\") => 630<span data-type=\"inlineCode\" class=\"code\"></p><p>- **todate** Converts strings to dates.</p><p>    * Datemath:   </span>todate(\"now-3m\")<span data-type=\"inlineCode\" class=\"code\">  Date math relative to message timestamp.</p><p>    * Parser:  </span>todate(\"02/01/2006\")<span data-type=\"inlineCode\" class=\"code\"> More than 30 formats supported. [Date Parser](https://github.com/araddon/dateparse)</p><p>    * Examples with 2 arguments:  </span>todate(\"02/01/2006\",\"07/04/2014\")<span data-type=\"inlineCode\" class=\"code\"> use [golang's time package](http://golang.org/pkg/time/) formatting</p><p>        * </span>todate(\"02/01/2006\",\"07/04/2014\")<span data-type=\"inlineCode\" class=\"code\"> Reformats the date </span>07/04/2014<span data-type=\"inlineCode\" class=\"code\"> from US formatting to UK formatting, with the resulting output being </span>04/07/2014<span data-type=\"inlineCode\" class=\"code\"></p><p>        * </span>todate(\"02/01/2006\",date_field_name)<span data-type=\"inlineCode\" class=\"code\"> Outputs </span>date_field_name<span data-type=\"inlineCode\" class=\"code\"> as European format (where </span>01<span data-type=\"inlineCode\" class=\"code\"> is a placeholder for month, </span>02<span data-type=\"inlineCode\" class=\"code\"> is a placeholder for day, and </span>2006<span data-type=\"inlineCode\" class=\"code\"> is a placeholder for year)</p><p>- **todatein** Converts strings to dates, if no location info is provided in date string such as \"2017-09-30 17:00:00\" this will allow you to apply a timezone.  We still convert back to UTC for storage.</p><p>- **totimeset** Type time slice/array. Takes in times and converts strings to times similar to todate without the formatting parameter.</p><p>- **totimestamp** Convert to Integer Unix Seconds (UTC).</p><p>- **yy** Type: int   Date conversion to YY format, so May 1 2014 is expressed as 14.   yy(dob), or yy() for record time stamp</p><p>- **yymm**  String The YYMM date format, so May 1 2014 is expressed as 1405.     yy(dob), or yy() for record time stamp</p><p>- **timebucket** Creates a tabulation of timestamps which can be used to segment based on timewindows. See [Segments Examples](#segment) for more information.  </span>timebucket(now())<span data-type=\"inlineCode\" class=\"code\"> for collect time, or </span>timebucket(todate(field))<span data-type=\"inlineCode\" class=\"code\"> to bucket on the value of a field</p><p>KINDS (aka Data Types)</p><p>-----------------------------------</p><p>Allows explicitly setting data type.  Often this os optional</p><p>as it is inferred from functional expression.</p><p>- *int*  64 bit signed integer</p><p>- *number*  64 bit signed Float value</p><p>- *bool*  Boolean</p><p>- *date*  Date-Time</p><p>- *string* string</p><p>- *[]time* Array of times</p><p>- *[]string* Array of strings</p><p>- *ts[]string* Time ordered Unique set of strings (useful for keeping track of order in which they performed set of unique events)</p><p>- *map[string]int* Map of key/integers</p><p>- *map[string]number*</p><p>- *map[string]string*</p><p>- *map[string]time*</p><p>Merge Operations</p><p>---------------------</p><p>*MERGEOP* Allow Merge behavior's to determine if given new data we want the new field, or keep the previous.</p><p>* </span>, my_date                   KIND DATE     MERGEOP oldest<span data-type=\"inlineCode\" class=\"code\"> -- Holds the first value seen for my_date</p><p>* </span>, old_score                   KIND INT     MERGEOP oldest<span data-type=\"inlineCode\" class=\"code\"> -- Holds the oldest value passed in to the field</p><p>* </span>set(lists) AS lists KIND []string MERGEOP latest` -- only store latest set (all previous values of set discarded)</p>"
url: "https://www.contentstack.com/docs/developers/apis/lytics-api/v1/query"
product: "Contentstack"
doc_type: "guide"
audience:
  - developers
  - admins
version: "current"
last_updated: "2026-11-05"
---

# Query

Schema management api to add/edit queries and user-fields.

Lytics Query Language

\=============================

The Lytics Query Language is used to define the transformation of uploaded

records, and event data into user Profiles. It transforms row-level event data into Document-oriented User info.

This Query langage is similar to the HIVE or SQL query langauges, however departs from these in

order to offer more of a \*Rich Document\* (json user profile) construction.

\*\*Query example\*\*

\`\`

\# Build a user from web data

SELECT

name -- Simple field, by default = string

, age KIND INT -- cast field as int

, last\_visit\_ts KIND DATE -- cast as date

\-- Showing the aggregate counter function and aliasing name of output column AS

, count(\_ref) AS ref\_ct

\-- Valuect makes a map\[string\]int count of occurences of a key

, valuect(my field) AS myfield\_mapct

\-- showcase every optional syntax element in column

\-- meregeop oldest we don't want to over-write this value, keep oldest

\-- KIND INT normally we don't have to cast as most functions have a specific type

, amt AS first\_order\_amount

IF event == "cart checkout"

SHORTDESC "Amount of First Order"

LONGDESC "Amount of First Order"

KIND INT

MERGEOP OLDEST

\-- lets keep around the date at which they signed up (mergeop oldest)

, now() AS signedup\_date IF event == "signed up" KIND DATE MERGEOP oldest

\-- maps: map all fields that start with "user." into a fact map

, match("user.") AS user\_attributes KIND map\[string\]string

\-- list of strings

, set(event) AS all\_events

\-- Identified By Columns allow merging across streams

, email(EmailAddress) AS email

, \_uid

, fbuid

FROM

default

INTO

user

BY

\_uid OR email OR fbuid

WHERE

\_bot = "f" OR NOT EXISTS \_bot

ALIAS

web\_user;

\# validate the query

curl -s -XPOST "https://api.lytics.io/api/query/\_validate" \\

\-H "Authorization: $LIOKEY" \\

\-H "Content-Type: text/plain" \\

\--data-binary @/tmp/tmp.lql | jq '.'

\# upload the query

curl -s -XPOST "https://api.lytics.io/api/query" \\

\-H "Authorization: $LIOKEY" \\

\-H "Content-Type: text/plain" \\

\--data-binary @your\_file.lql | jq '.'

\# look at schema it output:

curl -s -H "Authorization: $LIOKEY" \\

\-XGET "https://api.lytics.io/api/schema/user" | jq '.'

\`

\*\*Standard Syntax\*\*

\`

Select = "SELECT" COLUMNS FROM INTO BY \[WHERE\] ALIAS

\# required from, the stream to operate on for this query

FROM = "FROM" Identifier

\# Required Identified By field, name of column "AS" from Column

BY = "BY" Identifier \["OR" Identifier\]

\# Required Alias for giving a query a unqique identifier

ALIAS = "ALIAS" Identifier

\# Optional Where Filter, same as SQL where

WHERE = "WHERE" LogicalExpression

COLUMNS = COLUMN \[, COLUMN\]

COLUMN = Expression \["AS" Identifier\]

\["IF" LogicalExpression\] \["SHORTDESC" String\]

\["LONGDESC" String\] \["KIND" Kind\] \["MERGEOP" MergeOp\]

LogicalExpression = NOT

| Comparison

| EXISTS

| IN

| CONTAINS

| LIKE

| Function

| Expression

| "(" LogicalExpression ")"

| LogicalExpression OR LogicalExpression

| LogicalExpression AND LogicalExpression

Expression =

Identifier

| Function

| Literal

Function = Identifier "(" Expression \[, Expression\] ")"

NOT = "NOT" LogicalExpression

Comparison = Identifier ComparisonOp Literal

ComparisonOp = ">" | ">=" | "<" | "<=" | "==" | "!="

EXISTS = "EXISTS" Identifier

IN = Identifier "IN" (Literal, Literal, ...)

CONTAINS = Identifier "CONTAINS" Literal

LIKE = Identifier "LIKE" String # uses \* for wildcards

Literal = String | Int | Float | Bool | Timestamp

Identifier = \[a-zA-Z\]\[a-zA-Z0-9\_\]+ | "" + String + ""

Kind = "int" | "number" | "string" | "date" | "\[\]string" |

"ts\[\]string" | "map\[string\]int" | "map\[string\]number" | "map\[string\]string\*

\# MergeOp's are very seldom used and have to be used on the right Kind

\# ie string can use Latest, Oldest (but not min, max)

MergeOp = "max" | "min" | "latest" | "oldest" | "mapmax"

\`

\* \*\*SELECT\*\* Select data to be added to user profiles. Including Maps, Counts, and other complex data types.

\* \*\*FROM\*\* The stream to select from

\* \*\*INTO\*\* This is USER for all user profiles. (technically you could create other types, such as "account")

\* \*\*WHERE\*\* Filters out entire records to not be included/analyzed. Bots, Employees, Test data.

\* \*\*BY\*\* What field are we going to identify this entity by

\* \*\*ALIAS\*\* When a selection query has an alias, that is the profile-fragment(table) name to use

Functions

\--------------------------------

There are a variety of functions for transformation and logic evaluation.

\*\*Aggregate Functions\*\*

There are a variety of expressions for building document type structures (maps, lists, sets).

These are functional expressions but can only be used in Columns.

\- \*\*cap\*\* Limit the items stored in a field by count or by date. cap(field, int) cap(field, "number\_of\_days") (e.g. cap(field, "30d")). Returns an array containing the values within the capped count or length of time.

\- \*\*count\*\* Count of this key. For instance, count occurences of sessions that have started (ie, visited web site).

\- \*\*set\*\* Create a unique list/array of each value we have seen from this field

\- \*\*min,max\*\* Minimum or Maxium value (for numerics)

\- \*\*sum\*\* Sum values (keep track of total video play time, etc)

\*\*Logical Functions\*\*

Local Evaluation, return boolean true/false.

\- \*\*all\*\* checks for existince of n keys all(key1,key2,key3,...) returns boolean.

\- \*\*any\*\* accepts a list of values and returns True if any are the contents of a field any(fieldname, value1,value2,value3)

\- \*\*exists\*\* Check for field (aka key) existence.

\* exists(purchase\_total) checks to see if purchase\_total is defined for the current message

\* valuect(yymm()) AS visits\_by\_yymm IF exists(\_sesstart) Only fires valuect(yymm()) if \_sesstart exists

\- \*\*in\*\* Determines if a field value is in a set of values.

\* "t" AS is\_student IF role\_type IN ("student","other")

\* dailyContact AS dailyContact IF dailyContact IN ("student","other")

\- \*\*eq\*\* Equal to eq(domain,"google.com")

\- \*\*ne\*\* Not Equal to ne(domain,"google.com")

\- \*\*lt\*\* Less Than lt(seconds(video\_time), 30)

\- \*\*le\*\* Less Than or Equal to le(seconds(video\_time), 30)

\- \*\*gt\*\* Greater Than gt(seconds(video\_time), 30)

\- \*\*ge\*\* Greater Than or Equal to ge(seconds(video\_time), 30)

\- \*\*not\*\* Not not(exists(domain))

\- \*\*or\*\* Or or(exists(domain), contains(domain,"google.com")) AS from\_google

\*\*String Functions\*\*

\- \*\*join\*\* Join together multiple values, coerce them into strings. Last argument is which string to use to join (may be empty string).

\* join("apples","oranges",",") => "apples,oranges"

\* join("apples","oranges","") => "applesoranges"

\- \*\*len\*\* Length (of array, string)

\- \*\*oneof\*\* Choose value from the first field that has a non nil value.

\* oneof(fielda,fieldb,fieldc)

\- \*\*replace\*\* - Replace a matching part of a string with an empty string. Converts to string first.

\* replace(url,"/search/apachesolr\_search/") - Removes /search/apachesolr\_search/ from URL (in this case, leaving the search term

\- \*\*split\*\* Breaks a variable into smaller fragments given a specific delimiter

\* split(cc,",") - Splits the variable cc at each comma it contains

\- \*\*strip(field)\*\* Strips leading and trailing whitespace (spaces, tabs, newline, carriage-return) from string, or arrays of strings.

\- \*\*string.lowercase\*\* Convert strings to lower case

\- \*\*string.uppercase\*\* Convert strings to upper case

\- \*\*string.titlecase\*\* Convert strings to title case

\- \*\*contains\*\* Does this value contain this string? Is a sub-string match, not full match (eq)

\* IF contains(total\_price, "$") - Check to see if total\_price has a $ in it

\* IF not(contains(subscriber\_key,"-")) AND not(contains(subscriber\_key,"@")) check to make sure \- or @ is not in it.

\- \*\*hasprefix\*\* Does this value start with this string?

\* hasprefix(event, "created") - Check to see if event starts with "created"

\- \*\*hassuffix\*\* Does this value start with this string?

\* hassuffix(subscriber\_key, "user") - Check to see if subscriber\_key ends with "user"

\*\*Hash & Encoding Functions\*\*

\- \*\*hash.sip\*\* hash.sip(email) Hash the given value using sip hash to integer output.

\- \*\*hash.md5\*\* hash.md5(email) Hash the given value using md5

\- \*\*hash.sha1\*\* hash.sha1(email) Hash the given value using sha1

\- \*\*hash.sha256\*\* hash.sha256(email) Hash the given value using sha256

\- \*\*hash.sha512\*\* hash.sha512(email) Hash the given value using sha512

\- \*\*encoding.b64encode(field)\*\* base64 encode.

\- \*\*encoding.b64decode(field)\*\* base64 decode.

\*\*Cast & Convert\*\*

\- \*\*toint\*\* Converts strings to integers. Useful for converting a string to a number before applying a number-based expression.

\* toint(order\_total) - Converts order\_total to an int

\* set(toint(split(cc,","))) - Takes the field cc and splits it at commas, and converts the results to integers. Then adds them to a set.

\- \*\*tonumber\*\* Convert to Number

\- \*\*todate\*\* Converts strings to dates, see full doc in Date/Time section below.

\- \*\*tobool(field)\*\* Cast to Boolean.

\*\*Map & Set/Array Functions\*\*

\- \*\*filter\*\* Filter out Values that match specified list of match filter criteria

\* filter(split("apples,oranges",","),"ora\*") => \["apples"\]

\- \*\*len\*\* Length (of array, string)

\- \*\*map\*\* Type: Map map(key1, todate(date\_field))

\* map(key1, todate(date\_field)) KIND map\[string\]time By default the map is generic map, cast to map\[string\]time with

\- \*\*match\*\* Type: Map (generic map, use KIND to cast) Match a key, and then keep a map of key/values with the match value removed

\* , match("topic\_") AS global KIND map\[string\]number

\- \*\*mapkeys\*\* Type: Map input, \[\]string{} output. Given a map, return a list of string of each of the keys.

\- \*\*mapvalues\*\* Type: Map input, \[\]string{} output. Given a map, return a list of string values of each of the values.

\- \*\*mapinvert\*\* Type: Map input, MapString output. Given a map, return a map\[string\]string inverting keys/values.

\- \*\*array.index\*\* Cherry pick a single item out of an array:

\* array.index(split("apples,oranges,peaches",","),1) => \["oranges"\]

\- \*\*array.slice\*\* Slice an array of items selecting some sub-set of them.

\* array.slice(split("apples,oranges,peaches,pineapple",","),2) => \["peaches","pineapple"\]

\* array.slice(split("apples,oranges,peaches,pineapple",","),1,3) => \["oranges","peaches"\]

\*\*Url/Http & Email Functions\*\*

\- \*\*email\*\* Extract email address from "Bob <emailaddress@lytics.io>" format

\- \*\*emailname\*\* Extract \*Bob\* from "Bob <emailaddress@gmail.com>" or email@gmail.com

\- \*\*emaildomain\*\* Extract \*gmail.com\* from "Bob <emailaddress@gmail.com>" or email@gmail.com

\- \*\*domain\*\* Extract domain from url

\- \*\*host\*\* Extract host from url

\- \*\*path\*\* Extract the url path from url (no query string or domain), must be valid url parserable string.

\- \*\*qs\*\* Extract the querystring parameter from url qs(urlfield, "nameOfParam")

\* qs(url, "mc\_eid") - Extracts the MailChimp user ID

\* set(qs(url, "video\_id") - Creates a set of video\_id

\* qs(tolower(url), "riid") - Converts the complete URL to lowercase before attempting to match

\* email(oneof(email, qs(url, "email"))) - Attempts to get the email address from the URL and from the regular fields, chooses whichever is populated and treats it like an email field

\- \*\*urldecode\*\* Perform URL decode on a field. urldecode(field)

\* If field contains "my%20value", urldecode(field) will return "my value"

\- \*\*urlminusqs\*\* The url minus the querystring portion

\- \*\*useragent\*\* Extract info from user-agent string. Below examples based on Mozilla/5.0 (X11; Linux x86\_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.97 Safari/537.11

\* useragent(user\_agent, "bot") - Extracts True/False is this a bot?

\* useragent(user\_agent, "mobile") - Extracts True/False is this mobile?

\* useragent(user\_agent, "mozilla") - Extracts "5.0"

\* useragent(user\_agent, "platform") - Extracts "X11"

\* useragent(user\_agent, "os") - Extracts "Linux x86\_64"

\* useragent(user\_agent, "engine") - Extracts "Linux x86\_64"

\* useragent(user\_agent, "engine\_version") - Extracts "AppleWebKit"

\* useragent(user\_agent, "browser") - Extracts "Chrome"

\* useragent(user\_agent, "browser\_version") - Extracts "23.0.1271.97"

\- \*\*useragent.map(field)\*\* Extract map of all of above.

\*\*Date & Time Functions\*\*

Our core date parser recognizes about 50 date formats, so in general these will operate on \_any\_ format.

If you are using EU dates, you will need to specify the parser format.

\- \*\*dayofweek\*\* Type: Integer. 0-6 integer of day of week.

\* Examples: dayofweek() => 4 OR dayofweek(mydatefield)

\- \*\*epochms\*\* Type: Integer. Unix MS of the date stamp on the current message being processed

\- \*\*extract\*\* Can be used to extract parts of date and time. Example usage on the \[strftime\](http://strftime.org/) site

\* extract(reg\_date, "%B") Returns name of month

\* extract(reg\_date, "%d") Returns day of month

\- \*\*hourofday\*\* Type: Integer. Hour of day (in 24 hour utc time). hourofday() OR hourofday(field)

\- \*\*hourofweek\*\* 0-167 integer for hour of week

\- \*\*mm\*\* Type: Integer. 0-11 month (alias for monthofyear) mm() => current month, 6 for june, mm(my\_date\_field)

\- \*\*monthofyear\*\* Type: Integer Output the 0-11 month value

\- \*\*now\*\* Type: Date The current message/event times.

\- \*\*seconds\*\* Type: Integer. Seconds, extracts things like seconds("00:30") => 30 and seconds("10:30") => 630

\- \*\*todate\*\* Converts strings to dates.

\* Datemath: todate("now-3m") Date math relative to message timestamp.

\* Parser: todate("02/01/2006") More than 30 formats supported. \[Date Parser\](https://github.com/araddon/dateparse)

\* Examples with 2 arguments: todate("02/01/2006","07/04/2014") use \[golang's time package\](http://golang.org/pkg/time/) formatting

\* todate("02/01/2006","07/04/2014") Reformats the date 07/04/2014 from US formatting to UK formatting, with the resulting output being 04/07/2014

\* todate("02/01/2006",date\_field\_name) Outputs date\_field\_name as European format (where 01 is a placeholder for month, 02 is a placeholder for day, and 2006 is a placeholder for year)

\- \*\*todatein\*\* Converts strings to dates, if no location info is provided in date string such as "2017-09-30 17:00:00" this will allow you to apply a timezone. We still convert back to UTC for storage.

\- \*\*totimeset\*\* Type time slice/array. Takes in times and converts strings to times similar to todate without the formatting parameter.

\- \*\*totimestamp\*\* Convert to Integer Unix Seconds (UTC).

\- \*\*yy\*\* Type: int Date conversion to YY format, so May 1 2014 is expressed as 14. yy(dob), or yy() for record time stamp

\- \*\*yymm\*\* String The YYMM date format, so May 1 2014 is expressed as 1405. yy(dob), or yy() for record time stamp

\- \*\*timebucket\*\* Creates a tabulation of timestamps which can be used to segment based on timewindows. See \[Segments Examples\](#segment) for more information. timebucket(now()) for collect time, or timebucket(todate(field)) to bucket on the value of a field

KINDS (aka Data Types)

\-----------------------------------

Allows explicitly setting data type. Often this os optional

as it is inferred from functional expression.

\- \*int\* 64 bit signed integer

\- \*number\* 64 bit signed Float value

\- \*bool\* Boolean

\- \*date\* Date-Time

\- \*string\* string

\- \*\[\]time\* Array of times

\- \*\[\]string\* Array of strings

\- \*ts\[\]string\* Time ordered Unique set of strings (useful for keeping track of order in which they performed set of unique events)

\- \*map\[string\]int\* Map of key/integers

\- \*map\[string\]number\*

\- \*map\[string\]string\*

\- \*map\[string\]time\*

Merge Operations

\---------------------

\*MERGEOP\* Allow Merge behavior's to determine if given new data we want the new field, or keep the previous.

\* , my\_date KIND DATE MERGEOP oldest -- Holds the first value seen for my\_date

\* , old\_score KIND INT MERGEOP oldest -- Holds the oldest value passed in to the field

\* set(lists) AS lists KIND \[\]string MERGEOP latest\` -- only store latest set (all previous values of set discarded)

## Query List

### Query List

**GET** `/api/query`

List of all queries

#### Headers

- **Authorization** (optional)

#### Sample Response

```json
{
  "status": "success",
  "data": [
    {
      "id": "abcdef123",
      "updated": "2014-10-30T21:01:06.493Z",
      "created": "2014-10-30T21:01:06.493Z",
      "text": "SELECT ..."
    }
  ]
}
```

## Query Upsert

### Query Upsert

**POST** `/api/query`

Uses ALIAS name inside parsed query for ID, and either creates/updates a query.

\*\*CHANGE NOTIFICATION\*\* This api in the past has returned a single object

but is changing to return an array of query objects (because posted QL text

may contain more than one statement). To get the old behavior

of single object pass \*version=old\*.

\`\`sh

\# new version, will be default in Sept 2017.

curl -s -XPOST "https://api.lytics.io/api/query?version=new" \\

\-H "Authorization: $LIOKEY" \\

\--data-binary @your\_file.lql

\# old version, returns object

curl -s -XPOST "https://api.lytics.io/api/query?version=old" \\

\-H "Authorization: $LIOKEY" \\

\--data-binary @your\_file.lql

\`\`

#### Query Parameters

- **account_id** (optional)
  Your Lytics account ID.
- **version** (optional)
  Use the "new" array based response or "old" object.

#### Headers

- **Authorization** (optional)

#### Sample Response

```json
{
  "status": "success",
  "data": [
    {
      "id": "abcdef123",
      "updated": "2014-10-30T21:01:06.493Z",
      "created": "2014-10-30T21:01:06.493Z",
      "text": "SELECT ..."
    }
  ]
}
```

## Query Fetch

### Query Fetch

**GET** `/api/query/{idOrAlias}`

Get single query by \*ALIAS OR ID\*

#### URL Parameters

- **idOrAlias** (required)
  Alias of query

#### Query Parameters

- **account_id** (optional)
  Your Lytics account ID.

#### Headers

- **Authorization** (optional)

#### Sample Response

```json
{
  "status": "success",
  "data": {
    "id": "abcdef123",
    "updated": "2014-10-30T21:01:06.493Z",
    "created": "2014-10-30T21:01:06.493Z",
    "text": "SELECT ..."
  }
}
```

## Query Delete

### Query Delete

**DELETE** `/api/query/{idOrAlias}`

Delete A query by idOrAlias.

#### URL Parameters

- **idOrAlias** (required)
  Alias of query

#### Query Parameters

- **account_id** (optional)
  Your Lytics account ID.

#### Headers

- **Authorization** (optional)

#### Sample Response

```json
No Content
```

## Query Validation

### Query Validation

**POST** `/api/query/_validate`

Upload a query for syntax validation only.

Optionally, a \*segments=true\* parameter can be passed that

will validate this query both for syntax, as well as checking

that it doesn't invalidate existing segments. If a segment

uses a field that this query is about to remove/alter such

that the segment is no longer valid, this will warn.

\`\`sh

\# validate query syntax

curl -s -XPOST "https://api.lytics.io/api/query/\_validate" \\

\-H "Authorization: $LIOKEY" \\

\-H "Content-Type: text/plain" \\

\--data-binary @your\_file.lql

\# validate query syntax AND segments still valid

curl -s -XPOST "https://api.lytics.io/api/query/\_validate?segments=true" \\

\-H "Authorization: $LIOKEY" \\

\-H "Content-Type: text/plain" \\

\--data-binary @your\_file.lql

\`\`

#### Query Parameters

- **account_id** (optional)
  Your Lytics account ID.
- **version** (optional)
  Use the "new" array based response or "old" object.

#### Headers

- **Authorization** (optional)

#### Sample Response

```json
{
  "status": "success",
  "data": {
    "id": "abcdef123",
    "updated": "2014-10-30T21:01:06.493Z",
    "created": "2014-10-30T21:01:06.493Z",
    "text": "SELECT ..."
  }
}
```

## Query Test Evaluation

### Query Test Evaluation

**POST** `/api/query/_test`

Upload a query AND data to see how it will be interpreted.

\`\`sh

\# add any name/value paris to query string param for data input

\# then upload query and get evaluation response

curl -s -XPOST "https://api.lytics.io/api/query/\_test?name=value" \\

\-H "Authorization: $LIOKEY" \\

\-H "Content-Type: text/plain" \\

\--data-binary @your\_file.lql

\`\`

#### Headers

- **Authorization** (optional)

#### Sample Response

```json
{
  "status": "success",
  "data": {
    "id": "abcdef123",
    "updated": "2014-10-30T21:01:06.493Z",
    "created": "2014-10-30T21:01:06.493Z",
    "text": "SELECT ..."
  }
}
```