Complex Built-In U-SQL Types

Summary

U-SQL supports the following two built-in complex types:

SQL.MAP<K,V>
SQL.ARRAY<T>

The grammar rules use Complex_Type to refer to these types.

These two types are also available as Microsoft.Analytics.Type.SqlMap<K,V> and Microsoft.Analytics.Types.SqlArray<T> for use in .NET code and can be referred to as SqlMap<K,V> and SqlArray<T> in U-SQL.

The SQL.MAP's value type V and the SQL.ARRAY's item type T can be any built-in U-SQL type, including another SQL.MAP or SQL.ARRAY, while the SQL.MAP's Key type K has to be a comparable scalar built-in type. The SQL.MAP’s value type V has to be a nullable type, e.g. string or int?.

Note

SQL.MAP is not comparable.
SQL.ARRAY is not comparable.

Examples

See EXPLODE (U-SQL) for examples involving the usage of SQL.MAP and SQL.ARRAY and EXPLODE.

SQL.MAP

// Data sets
@ProjectMembers1 = 
    SELECT * FROM 
        ( VALUES
        ("Website", "Chris;UX")
        ) AS T(Project, emp_role);

@ProjectMembers2 = 
    SELECT * FROM 
        ( VALUES
        ("Website", "Mallory", "PM"),
        ("Website", "Bob", "Dev"),
        ("Website", "Alice", "Dev"),
        ("Website", "Stan", "Dev"),
        ("DB", "Ted", "Test"),
        ("DB", "Joe", "Dev"),
        ("DB", "Chuck", "Dev")
        ) AS T(Project, Employee, Role);

@ProjectMembers3 = 
    SELECT * FROM 
        ( VALUES
        ("Operations", new SQL.MAP<string, string>{{"Claire", "DBA"}}),                             // alternative creation
        ("Operations", new SqlMap<string, string>{{"Danes", "Dev"}}),                               // alternative creation
        ("Operations", SqlMap.Create(new SqlMap<string, string>{{"Tina", "NetworkAdmin"}})),        // alternative creation
        ("Operations", new SqlMap<string, string>(new SqlMap<string, string>{{"Lewis", "DBA"}})),   // alternative creation
        ("Operations", new SqlMap<string, string>(new Dictionary<string, string>{{"Rudy", "Dev"}})) // alternative creation
        ) AS T(Project, emp_role);
/**************************************************************/         

// Building a MAP 1
@map1 =
    SELECT  Project,
            new SQL.MAP<string, string>(from p in emp_role select new KeyValuePair<string, string>(emp_role.Split(';')[0], emp_role.Split(';')[1])) AS emp_role
    FROM @ProjectMembers1; 

// Building a MAP 2
@map2 = 
    SELECT  Project, 
            MAP_AGG(Employee, Role) AS emp_role
    FROM @ProjectMembers2 
    WHERE Project == "Website"
    GROUP BY Project;

// Building a MAP 3; alternative method for MAP 2
@map3 = 
    SELECT  Project,
            new SQL.MAP<string, string>{{Employee, Role}} AS emp_role
    FROM @ProjectMembers2
    WHERE Project == "DB";

// UNION all rowsets
@mappedData =
    SELECT * FROM @map1
    UNION ALL
    SELECT * FROM @map2
    UNION ALL
    SELECT * FROM @map3
    UNION ALL
    SELECT * FROM @ProjectMembers3;

// review flattened results
@result1 =
    SELECT  Project,
            Emp.key AS Employee,
            Emp.value AS Role
    FROM @mappedData
    CROSS APPLY EXPLODE(emp_role) AS Emp(key, value);

// return emp_role as string of key/value pairs
@result2 =
    SELECT Project,
           string.Join(",", emp_role) AS emp_role
    FROM @mappedData;

// Filtering on key
// Projects that involve Employee Chuck
@result3 =
    SELECT Project
    FROM @mappedData
    WHERE emp_role.ContainsKey("Chuck");

// Filtering on key
// Projects that involve Employee Chuck; alternative 1
@result4 =
    SELECT Project
    FROM @mappedData
    WHERE emp_role.Keys.Contains("Chuck");

// Filtering on key
// Projects that involve Employee Chuck; alternative 2
@result5 =
    SELECT Project
    FROM @mappedData
    WHERE emp_role.Any(kv => kv.Key.Contains("Chuck"));

// Filtering on value
// Projects that involve Role UX
@result6 =
    SELECT Project
    FROM @mappedData
    WHERE emp_role.Values.Contains("UX");

// Projects that have more than 4 employees
@result7 = 
    SELECT  Project
    FROM @mappedData
    GROUP BY Project
    HAVING SUM(emp_role.Count) > 4;

// The number of employees per Project
@result8 =
    SELECT Project,
           SUM(emp_role.Count) AS NumEmployees,
           SUM(emp_role.Count()) AS NumEmployees2 // less efficient
    FROM @mappedData
    GROUP BY Project;

// The number of Employee Maps per Project
@result9 =
    SELECT Project,
           COUNT( emp_role ) AS numMaps
    FROM @mappedData
    GROUP BY Project;

// update a value 1; output a single record with a new value
@update1 =
    SELECT Project,
           SqlMap.Update(emp_role, new SqlMap<string, string>{{"Claire", "PM"}}) AS emp_role
    FROM @mappedData
    WHERE emp_role.ContainsKey("Claire");

@result10 = 
    SELECT  Project,
            Emp.key AS Employee,
            Emp.value AS Role
    FROM @update1
    CROSS APPLY EXPLODE(emp_role) AS Emp(key, value);

// update a value 2; output all records where one specific map has been updated
@mappedData =
    SELECT Project,
           emp_role.ContainsKey("Claire") ? new SQL.MAP<string, string>(emp_role) {{"Claire", "PM"}} : emp_role AS emp_role
    FROM @mappedData;

@result11 =
    SELECT Project,
           Emp.key AS Employee,
           Emp.value AS Role
    FROM @mappedData
    CROSS APPLY EXPLODE(emp_role) AS Emp(key, value);
    
OUTPUT @result1
TO "ReferenceGuide/DataTpes/BuiltInComplex/MAP/result1.txt"
USING Outputters.Text();

OUTPUT @result2
TO "ReferenceGuide/DataTpes/BuiltInComplex/MAP/result2.txt"
USING Outputters.Text();

OUTPUT @result3
TO "ReferenceGuide/DataTpes/BuiltInComplex/MAP/result3.txt"
USING Outputters.Text();

OUTPUT @result4
TO "ReferenceGuide/DataTpes/BuiltInComplex/MAP/result4.txt"
USING Outputters.Text();

OUTPUT @result5
TO "ReferenceGuide/DataTpes/BuiltInComplex/MAP/result5.txt"
ORDER BY Project
USING Outputters.Text();

OUTPUT @result6
TO "ReferenceGuide/DataTpes/BuiltInComplex/MAP/result6.txt"
USING Outputters.Text();

OUTPUT @result7
TO "ReferenceGuide/DataTpes/BuiltInComplex/MAP/result7.txt"
USING Outputters.Text();

OUTPUT @result8
TO "ReferenceGuide/DataTpes/BuiltInComplex/MAP/result8.txt"
USING Outputters.Text();

OUTPUT @result9
TO "ReferenceGuide/DataTpes/BuiltInComplex/MAP/result9.txt"
USING Outputters.Text();

OUTPUT @result10
TO "ReferenceGuide/DataTpes/BuiltInComplex/MAP/result10.txt"
USING Outputters.Text();

OUTPUT @result11
TO "ReferenceGuide/DataTpes/BuiltInComplex/MAP/result11.txt"
USING Outputters.Text();

SQL.MAP - Additional Examples 1

// Data set where phone values initially appear as a string.
@employees = 
    SELECT * FROM 
        ( VALUES
        ("Noah",   "cell:030-0074321,office:030-0076545"),
        ("Sophia", "office:(5) 555-3745,cell:(5) 555-4729"),
        ("Liam",   "cell:(5) 555-3932"),
        ("Amy",    "office:(171) 555-6750")
        ) AS T(EmpName, PhoneNumbers);

// Parse through PhoneNumbers and create a new data set with a SQL.MAP column type.  All subsequent queries will be against the @map rowset.
@map =
    SELECT EmpName,
           new SQL.MAP<string, string>(from p in PhoneNumbers.Split(',') select new KeyValuePair<string, string>(p.Split(':') [0], p.Split(':') [1])) AS PhoneNumberMap
    FROM @employees;
/********************************/


// Basic query using CROSS APPLY EXPLODE
@result =
    SELECT EmpName,
           r.key.Trim() AS PhoneType, r.value AS PhoneNumber
    FROM @map
         CROSS APPLY
             EXPLODE(PhoneNumberMap) AS r(key, value);

OUTPUT @result
TO "/Output/ReferenceGuide/Concepts/TypesAndLiterals/BuiltIn/Complex/SQL_MAP/ExampleA.csv"
USING Outputters.Csv(outputHeader: true);


// Modified format using OUTER UNION
@result =
    SELECT EmpName, c.value AS Cell
    FROM @map CROSS APPLY EXPLODE(PhoneNumberMap) AS c(key, value) WHERE c.key == "cell"
    OUTER UNION BY NAME ON (EmpName)
    SELECT EmpName, o.value AS Office 
    FROM @map CROSS APPLY EXPLODE(PhoneNumberMap) AS o(key, value) WHERE o.key == "office";

OUTPUT @result
TO "/Output/ReferenceGuide/Concepts/TypesAndLiterals/BuiltIn/Complex/SQL_MAP/ExampleB.csv"
USING Outputters.Csv(outputHeader: true);


//  Cell numbers only
@result =
    SELECT EmpName, c.value AS Cell
    FROM @map
         CROSS APPLY
             EXPLODE(PhoneNumberMap) AS c(key, value)
    WHERE c.key == "cell";

OUTPUT @result
TO "/Output/ReferenceGuide/Concepts/TypesAndLiterals/BuiltIn/Complex/SQL_MAP/ExampleC.csv"
USING Outputters.Csv();


// Using an OUTER JOIN to deliver Cell and Office numbers in a certain order regardless of order in SQL.MAP array.
@result =
    SELECT cc.EmpName ?? oo.EmpName AS Employee,
    cc.Cell, oo.Office
    FROM
    (SELECT EmpName, c.value AS Cell
    FROM @map AS aa CROSS APPLY EXPLODE(PhoneNumberMap) AS c(key, value) WHERE c.key == "cell") AS cc
    FULL OUTER JOIN 
    (SELECT EmpName, o.value AS Office 
    FROM @map CROSS APPLY EXPLODE(PhoneNumberMap) AS o(key, value) WHERE o.key == "office") AS oo
    ON cc.EmpName == oo.EmpName;

OUTPUT @result
TO "/Output/ReferenceGuide/Concepts/TypesAndLiterals/BuiltIn/Complex/SQL_MAP/ExampleD.csv"
USING Outputters.Csv(outputHeader: true);


// Same result as above using conditions and calling SQL.MAP values by index position.
@result =
    SELECT EmpName, 
        (PhoneNumberMap.Count == 2) ? 
            (PhoneNumberMap.Keys[0].Trim() == "cell") ? PhoneNumberMap.Values[0] : PhoneNumberMap.Values[1] : 
        (PhoneNumberMap.Keys[0].Trim() == "cell") ? PhoneNumberMap.Values[0] : "no cell number" AS Cell,
        (PhoneNumberMap.Count == 2) ? 
            (PhoneNumberMap.Keys[0].Trim() == "office") ? PhoneNumberMap.Values[0] : PhoneNumberMap.Values[1] : 
        (PhoneNumberMap.Keys[0].Trim() == "office") ? PhoneNumberMap.Values[0] : "no office number" AS Office            
    FROM @map;

OUTPUT @result
TO "/Output/ReferenceGuide/Concepts/TypesAndLiterals/BuiltIn/Complex/SQL_MAP/ExampleE.csv"
USING Outputters.Csv(outputHeader: true);

SQL.MAP - Additional Examples 2

// Data set where phone values initially appear as SQL.MAP.
DECLARE @ma2p = new SQL.MAP<string, string>{{"office", "030-0076545"}, {"cell", "030-0074321"}, {"a key", "a value"}};

@map = 
    SELECT
        new SqlMap<string, string>{{"cell", "(5) 555-4729"}} AS PhoneNumberMap,
        new SQL.MAP<string, string>{{"office", "(5) 555-3745"}} AS PhoneNumberMap2,
        @ma2p AS PhoneNumberMap3
    FROM 
        (VALUES 
        (1)
        ) AS T(dummyTable);


// Querying by index position
@result =
    SELECT 
        PhoneNumberMap3.Keys[0] AS Key0,
        PhoneNumberMap3.Values[0]AS Value0
    FROM @map;

OUTPUT @result
TO "/Output/ReferenceGuide/Concepts/TypesAndLiterals/BuiltIn/Complex/SQL_MAP/Example1.csv"
USING Outputters.Csv();


// Keys only
@result =
    SELECT r.key AS PhoneType
    FROM @map
         CROSS APPLY
             EXPLODE(PhoneNumberMap3.Keys) AS r(key);

OUTPUT @result
TO "/Output/ReferenceGuide/Concepts/TypesAndLiterals/BuiltIn/Complex/SQL_MAP/Example2.csv"
USING Outputters.Csv();


// Combining results
@result =
    SELECT a.key.Trim() AS PhoneType1,
           a.value AS PhoneNumber1,
           b.key.Trim() AS PhoneType2,
           b.value AS PhoneNumber2
    FROM @map
         CROSS APPLY
             EXPLODE(PhoneNumberMap) AS a(key, value)
         CROSS APPLY
             EXPLODE(PhoneNumberMap2) AS b(key, value);

OUTPUT @result
TO "/Output/ReferenceGuide/Concepts/TypesAndLiterals/BuiltIn/Complex/SQL_MAP/Example3.csv"
USING Outputters.Csv();  


// Combining results using UNION 
@result =
    SELECT r.key.Trim() AS PhoneType,
           r.value AS PhoneNumber
    FROM @map
         CROSS APPLY
             EXPLODE(PhoneNumberMap) AS r(key, value)
    UNION
    SELECT r.key.Trim() AS PhoneType,
           r.value AS PhoneNumber
    FROM @map
         CROSS APPLY
             EXPLODE(PhoneNumberMap2) AS r(key, value);

OUTPUT @result
TO "/Output/ReferenceGuide/Concepts/TypesAndLiterals/BuiltIn/Complex/SQL_MAP/Example4.csv"
USING Outputters.Csv();

User-Defined Function - ReadStringMap/WriteQuotedStringMap
c# code is placed in the associated Code-Behind .cs file. See usage in next section, below.

using System;
using System.Linq;
using System.Collections.Generic;
using Microsoft.Analytics.Types.Sql;

// slightly modied version from:
// https://github.com/Azure/usql/blob/master/Examples/AmbulanceDemos/AmbulanceDemoCode/Class1.cs

namespace ReferenceGuide_Examples
{
    public class MyClass
    {
        // transforms the input string val into a SQL.MAP instance using the provided delimiters to separate key-value pairs and the key and value in each pair.
        // Both the key and value types are string.
        public static SqlMap<string, string> ReadStringMap(string val, string map_item_delim, string map_kv_delim)
        {
            return new SqlMap<string, string>(
                from p in val.Split(new string[]
                {
                    map_item_delim
                }, StringSplitOptions.None)
                select new KeyValuePair<string, string>(p.Split(new string[]
                {
                    map_kv_delim
                }, StringSplitOptions.None)[0], p.Split(new string[]
                {
                    map_kv_delim
                }, StringSplitOptions.None)[1]));
        }
        
        // transforms a SQL.MAP<string, string> into a quoted string, using the provided delimiters to delimit keys and values and key-value pairs.
        public static string WriteQuotedStringMap(SqlMap<string, string> m, string map_item_delim = ",", string map_kv_delim = ":")
        {
            return "\"" + string.Join(map_item_delim,
                from p in m
                select string.Format("{0}{1}{2}", p.Key, map_kv_delim, p.Value)) + "\"";
        }
    }
}

Using User-Defined Function - ReadStringMap/WriteQuotedStringMap
Function ReadStringMap transforms the input string val into a SQL.MAP instance using the provided delimiters to separate key-value pairs and the key and value in each pair. Function WriteQuotedStringMap transforms a SQL.MAP<string, string> into a quoted string, using the provided delimiters to delimit keys and values and key-value pairs. Using Code-Behind from previous section, above.

@employees = 
    SELECT * FROM 
        ( VALUES
        ("Noah",   "cell:030-0074321,office:030-0076545"),
        ("Sophia", "office:(5) 555-3745,cell:(5) 555-4729"),
        ("Liam",   "cell:(5) 555-3932"),
        ("Amy",    "office:(171) 555-6750")
        ) AS T(EmpName, PhoneNumbers);

@map = 
    SELECT  EmpName,
            ReferenceGuide_Examples.MyClass.ReadStringMap(PhoneNumbers, ",", ":") AS PhoneNumberMap
    FROM @employees;

// Exploded
@result =
    SELECT EmpName,
           r.key.Trim() AS PhoneType, r.value AS PhoneNumber
    FROM @map
         CROSS APPLY
             EXPLODE(PhoneNumberMap) AS r(key, value);

OUTPUT @result
TO "/Output/ReferenceGuide/Concepts/TypesAndLiterals/BuiltIn/Complex/SQL_MAP/ExampleA_ReadStringMap.csv"
USING Outputters.Csv(outputHeader: true);

// Return the new SQL.MAP created above back to a string
@result =
    SELECT EmpName,
           ReferenceGuide_Examples.MyClass.WriteQuotedStringMap(PhoneNumberMap) AS BackToString
    FROM @map;

OUTPUT @result
TO "/Output/ReferenceGuide/Concepts/TypesAndLiterals/BuiltIn/Complex/SQL_MAP/ExampleA_WriteQuotedStringMap.csv"
USING Outputters.Csv(outputHeader: true);

SQL.ARRAY

// Data sets
@popularCities1 = 
    SELECT * FROM 
        ( VALUES
        ("Washington", "Seattle;Spokane;Tacoma;Vancouver;Bellevue"),
        ("Arizona", "Phoenix;Tucson;Mesa;Chandler;Glendale")
        ) AS T(State, Cities);

@popularCities2 = 
    SELECT * FROM 
        ( VALUES
        ("Oregon", "Portland"),
        ("Oregon", "Eugene"),
        ("Oregon", "Salem"),
        ("Oregon", "Gresham"),
        ("Oregon", "Hillsboro")
        ) AS T(State, City);

@popularCities3 = 
    SELECT * FROM 
        ( VALUES
        ("Utah", "Salt Lake City", "West Valley City", "Provo")
        ) AS T(State, City1, City2, City3);

@popularCities4 = 
    SELECT * FROM 
        ( VALUES
        ("Idaho",   new SQL.ARRAY<string>{"Boise","Nampa","Meridian","Idaho Falls"}),
        ("Montana", new SqlArray<string>{"Billings","Missoula","Great Falls","Bozeman","Butte"}),              // alternative creation
        ("Alaska",  SqlArray.Create(new [] {"Birmingham","Montgomery","Mobile","Huntsville","Tuscaloosa"})),   // alternative creation
        ("Nevada",  new SQL.ARRAY<string>(new [] {"Las Vegas","Henderson","North Las Vegas","Reno;Sparks"}))   // alternative creation
        ) AS T(State, Cities);
/**************************************************************/

// Building an ARRAY 1
@array1 =
    SELECT  State,
            new SQL.ARRAY<string>(Cities.Split(';')) AS Cities
    FROM @popularCities1
    WHERE State == "Washington";

// Building an ARRAY 2
@array2 =
    SELECT  State,
            SqlArray.Create(Cities.Split(';')) AS Cities
    FROM @popularCities1
    WHERE State == "Arizona";

// Building an ARRAY 3
@array3 =
    SELECT  State,
            ARRAY_AGG(City) AS Cities
    FROM @popularCities2
    GROUP BY State;

// Building a ARRAY 4
@array4 = 
    SELECT  State,
            new SQL.ARRAY<string>{City1, City2, City3} AS Cities
    FROM @popularCities3;

// UNION all arrays
@combinedCities =
    SELECT * FROM @array1
    UNION ALL
    SELECT * FROM @array2
    UNION ALL
    SELECT * FROM @array3
    UNION ALL
    SELECT * FROM @array4
    UNION ALL
    SELECT * FROM @popularCities4;

// Flatten results
@result1 =
    SELECT  State,
            r.City AS City
    FROM @combinedCities
    CROSS APPLY EXPLODE(Cities) AS r(City);

// return Cities as one comma delimited string per State
@result2 =
    SELECT State,
           string.Join(";", Cities) AS Cities
    FROM @combinedCities;


// Use array index
@result3 =
    SELECT State,
           Cities[0]AS FirstCity
    FROM @combinedCities;

// Array length; The number of Cities per State
@result4 =
    SELECT State,
           Cities.Count AS CityCount1,
           Cities.Count() AS CityCount2 // less efficient
    FROM @combinedCities;

// The number of Cities Arrays per State
@result5 =
    SELECT State,
           COUNT(Cities) AS numArrays
    FROM @combinedCities
    GROUP BY State;

// Filtering
@result6a =
    SELECT  State, r.City
    FROM @combinedCities
    CROSS APPLY EXPLODE(Cities) AS r(City)
    WHERE r.City.StartsWith("B");

// Filtering
@result6b =
    SELECT State
    FROM @combinedCities
    WHERE Cities.Contains("Seattle");

// Creating an array from another array
/*
@data1 =
    SELECT State,
           new SQL.ARRAY<string>(Cities)  AS Cities
    FROM @combinedCities;
*/

// Adding an item to the array; adding a city
@array1 =
    SELECT State,
           new SQL.ARRAY<string>(Cities){"Redmond"}  AS Cities
    FROM @array1;

@result7a =
    SELECT State,
           r.City AS City
    FROM @array1
    CROSS APPLY EXPLODE(Cities) AS r(City);


// Adding an item to a specified array; adding a city to Washington and Idaho
@combinedCities =
    SELECT State,
           (State == "Washington") ? new SQL.ARRAY<string>(Cities){"Yakima"} :                    // method 1
           (State == "Idaho") ? SqlArray.Append(Cities, new []{"Pocatello"}) : Cities AS Cities   // method 2
    FROM @combinedCities;

@result7b =
    SELECT State,
           r.City AS City
    FROM @combinedCities
    CROSS APPLY EXPLODE(Cities) AS r(City);


OUTPUT @result1
TO "ReferenceGuide/DataTpes/BuiltInComplex/ARRAY/result1.txt"
ORDER BY State
USING Outputters.Text();

OUTPUT @result2
TO "ReferenceGuide/DataTpes/BuiltInComplex/ARRAY/result2.txt"
USING Outputters.Text();

OUTPUT @result3
TO "ReferenceGuide/DataTpes/BuiltInComplex/ARRAY/result3.txt"
USING Outputters.Text();

OUTPUT @result4
TO "ReferenceGuide/DataTpes/BuiltInComplex/ARRAY/result4.txt"
USING Outputters.Text();

OUTPUT @result5
TO "ReferenceGuide/DataTpes/BuiltInComplex/ARRAY/result5.txt"
USING Outputters.Text();

OUTPUT @result6a
TO "ReferenceGuide/DataTpes/BuiltInComplex/ARRAY/result6a.txt"
USING Outputters.Text();

OUTPUT @result6b
TO "ReferenceGuide/DataTpes/BuiltInComplex/ARRAY/result6b.txt"
USING Outputters.Text();

OUTPUT @result7a
TO "ReferenceGuide/DataTpes/BuiltInComplex/ARRAY/result7a.txt"
USING Outputters.Text();

OUTPUT @result7b
TO "ReferenceGuide/DataTpes/BuiltInComplex/ARRAY/result7b.txt"
USING Outputters.Text();

SQL.ARRAY - Additional Examples
This example converts the Books values into the SQL.ARRAY type AS BooksArray. Then each value in the array is returned separately by passing the index position.

@someBooks = 
    SELECT * FROM 
        ( VALUES
        ("The Book Thief; Markus Zusak; 2005"),
        ("The Girl with the Dragon Tattoo; Stieg Larsson; 2005"),
        ("The Silver Linings Playbook; Matthew Quick; 2008"),
        ("Sarah's Key; Tatiana de Rosnay; 2006")
        ) AS T(Books);
        
@array =
    SELECT new SQL.ARRAY<string>(Books.Split(';')) AS BooksArray
    FROM @someBooks;

@result =
    SELECT BooksArray[0] AS Book,
           BooksArray[1].TrimStart() AS Author,
           BooksArray[2].TrimStart() AS [Publication Year]
    FROM @array;

OUTPUT @result
TO "/Output/ReferenceGuide/Concepts/TypesAndLiterals/BuiltIn/Complex/exampleA.csv"
USING Outputters.Csv();

SQL.ARRAY - Alternate example
Similiar as above; however, values are inputted directly as SQL.ARRAY.

@someBooks2 = 
    SELECT * FROM 
        ( VALUES
        (new SQL.ARRAY<string>{"The Book Thief", "Markus Zusak", "2005"}),
        (new SQL.ARRAY<string>{"The Girl with the Dragon Tattoo", "Stieg Larsson", "2005"}),
        (new SQL.ARRAY<string>{"The Silver Linings Playbook", "Matthew Quick", "2008"}),
        (new SQL.ARRAY<string>{"Sarah's Key", "Tatiana de Rosnay", "2006"})
        ) AS T(BooksArray);

@result2 =
    SELECT BooksArray[0] AS Book,
           BooksArray[1].TrimStart() AS Author,
           BooksArray[2].TrimStart() AS [Publication Year]
    FROM @someBooks2;

OUTPUT @result2
TO "/Output/ReferenceGuide/Concepts/TypesAndLiterals/BuiltIn/Complex/exampleB.csv"
USING Outputters.Csv();

User-Defined Function - ReadIntArray/WriteQuotedIntArray
c# code is placed in the associated Code-Behind .cs file. See usage in next section, below.

using System;
using System.Linq;
using Microsoft.Analytics.Types.Sql;

// slightly modied version from:
// https://github.com/Azure/usql/blob/master/Examples/AmbulanceDemos/AmbulanceDemoCode/Class1.cs

namespace ReferenceGuide_Examples
{
    public class MyClass
    {
        // returns a SQL.ARRAY<int> from the input string val using the provided array item delimiter.
        public static SqlArray<int> ReadIntArray(string val, string array_item_delim)
        {
            return new SqlArray<int>(
                from x in val.Split(new string[]
                {
                    array_item_delim
                }, StringSplitOptions.None)
                select Convert.ToInt32(x));
        }

        // transforms a SQL.ARRAY<int> into a quoted string using the provided array item delimiter.
        public static string WriteQuotedIntArray(SqlArray<int> a, string array_item_delim = ",")
        {
            return "\"" + string.Join<int>(array_item_delim, a) + "\"";
        }
    }
}

Using User-Defined Function - ReadIntArray/WriteQuotedIntArray
Function ReadIntArray returns a SQL.ARRAY<int> from the input string val using the provided array item delimiter. Function WriteQuotedIntArray transforms a SQL.ARRAY<int> into a quoted string using the provided array item delimiter. Using Code-Behind from previous section, above.

@someStrings = 
    SELECT * FROM 
        ( VALUES
        ("0, 1, 2, 3, 4, 5, 6, 7, 8, 9")
        ) AS T(strings);

// transform to array
@newArray =
    SELECT ReferenceGuide_Examples.MyClass.ReadIntArray(strings, ",") AS newArray
    FROM @someStrings;

// view array by position
@result =
    SELECT newArray[0] AS position0,
           newArray[3] AS position3
    FROM @newArray;

OUTPUT @result
TO "/Output/ReferenceGuide/Concepts/TypesAndLiterals/BuiltIn/Complex/SQL_ARRAY/ReadIntArray_position.csv"
USING Outputters.Csv(outputHeader: true);


// view array exploded
@array_exploded =  
    SELECT array_exploded 
    FROM @newArray
    CROSS APPLY  
    EXPLODE(newArray) AS r(array_exploded);

OUTPUT @array_exploded
TO "/Output/ReferenceGuide/Concepts/TypesAndLiterals/BuiltIn/Complex/SQL_ARRAY/ReadIntArray_exploded.csv"
USING Outputters.Csv(outputHeader: true);


// return back to string
@result =
    SELECT ReferenceGuide_Examples.MyClass.WriteQuotedIntArray(newArray) AS BackToString
    FROM @newArray;

OUTPUT @result
TO "/Output/ReferenceGuide/Concepts/TypesAndLiterals/BuiltIn/Complex/SQL_ARRAY/WriteQuotedIntArray.csv"
USING Outputters.Csv(outputHeader: true);

See Also