diff --git a/packages/@dataform/sql/BUILD b/packages/@dataform/sql/BUILD index a1b6172dd..5a3ea8aaa 100644 --- a/packages/@dataform/sql/BUILD +++ b/packages/@dataform/sql/BUILD @@ -22,7 +22,7 @@ pkg_json( ], main = "bundle.js", types = "bundle.d.ts", - version = "0.2.0", + version = "0.3.0", ) pkg_bundle( diff --git a/sql/README b/sql/README.md similarity index 57% rename from sql/README rename to sql/README.md index e99e6b105..3404f537e 100644 --- a/sql/README +++ b/sql/README.md @@ -6,7 +6,7 @@ Useful functions for constructing SQL statements in Dataform packages. - Redshift - Snowflake -*If you would like us to add support for another warehouse, please get in touch via [email](mailto:team@dataform.co) or [Slack](https://dataform.co/slack)* +_If you would like us to add support for another warehouse, please get in touch via [email](mailto:team@dataform.co) or [Slack](https://dataform.co/slack)_ ## Installation @@ -17,34 +17,35 @@ Useful functions for constructing SQL statements in Dataform packages. ### Common SQL functions #### surrogateKey + Creates a unique hash from a list of fields. Useful for generating a surrogate key for a table. `${sql.surrogateKey(["field_one", "field_two"])}` #### windowFunction + Creates a window function with the given configuration. -`${sql.windowFunction({ - name: "window_function_name", - value: "target_column", - ignoreNulls: true, - windowSpecification: { - partitionFields: ["field_to_partition_by_one", "field_to_partition_by_two"], - orderFields: ["field_to_order_by_one", "field_to_order_by_two"], - frameClause: "rows between 0 preceding and unbounded following" - } -})}` +`${sql.windowFunction({ name: "window_function_name", value: "target_column", ignoreNulls: true, windowSpecification: { partitionFields: ["field_to_partition_by_one", "field_to_partition_by_two"], orderFields: ["field_to_order_by_one", "field_to_order_by_two"], frameClause: "rows between 0 preceding and unbounded following" } })}` #### asTimestamp + Casts the field to timestamp type `${sql.asTimestamp("field")}` #### asString + Casts the field to timestamp type `${sql.asString("field")}` +#### stringAgg + +Groups the values in a field into a concatenated string, with an optional delimiter value that defaults to "," + +`${sql.stringAgg("field", "delimiter")}` + ### Timestamp functions Calculate the time difference between two timestamps. diff --git a/sql/index.ts b/sql/index.ts index 3ccc6b185..52f2f5698 100644 --- a/sql/index.ts +++ b/sql/index.ts @@ -227,6 +227,15 @@ export class Sql { } } + // String aggregation + + public stringAgg(field: string, delimiter = ",") { + if (this.dialect === "snowflake" || this.dialect === "redshift") { + return `listagg(${field}, '${delimiter}')`; + } + return `string_agg(${field}, '${delimiter}')`; + } + // Convenience methods for builders. public json(data: S[]) { return new JSONBuilder(this, data); diff --git a/sql/integration.spec.ts b/sql/integration.spec.ts index f79930966..8a5be8ec4 100644 --- a/sql/integration.spec.ts +++ b/sql/integration.spec.ts @@ -240,6 +240,31 @@ suite("builders", { parallel: true }, ({ before, after }) => { expect(result[0].max).equals(1); expect(result[0].first_value).equals(1); }); + // skipping this test for redshift as the function is only supported on user-defined-tables + // i.e. not when just querying a select statement with no table + if (name !== "redshift") { + test("string agg", async () => { + const rows = [ + { + a: "foo" + }, + { + a: "bar" + } + ]; + const query = sql.from(sql.json(rows)).select({ + agg: sql.stringAgg("a"), + agg_hyphen: sql.stringAgg("a", "-") + }); + + const result: any = await execute(query); + expect(result.length).equals(1); + expect(result[0]).deep.equals({ + agg: "foo,bar", + agg_hyphen: "foo-bar" + }); + }); + } test("timestamp diff", async () => { const rows = [