Simulate MySql group_concat in MSSQL [duplicate] - mysql
I'm trying to migrate a MySQL-based app over to Microsoft SQL Server 2005 (not by choice, but that's life).
In the original app, we used almost entirely ANSI-SQL compliant statements, with one significant exception -- we used MySQL's group_concat function fairly frequently.
group_concat, by the way, does this: given a table of, say, employee names and projects...
SELECT empName, projID FROM project_members;
returns:
ANDY | A100
ANDY | B391
ANDY | X010
TOM | A100
TOM | A510
... and here's what you get with group_concat:
SELECT
empName, group_concat(projID SEPARATOR ' / ')
FROM
project_members
GROUP BY
empName;
returns:
ANDY | A100 / B391 / X010
TOM | A100 / A510
So what I'd like to know is: Is it possible to write, say, a user-defined function in SQL Server which emulates the functionality of group_concat?
I have almost no experience using UDFs, stored procedures, or anything like that, just straight-up SQL, so please err on the side of too much explanation :)
No REAL easy way to do this. Lots of ideas out there, though.
Best one I've found:
SELECT table_name, LEFT(column_names , LEN(column_names )-1) AS column_names
FROM information_schema.columns AS extern
CROSS APPLY
(
SELECT column_name + ','
FROM information_schema.columns AS intern
WHERE extern.table_name = intern.table_name
FOR XML PATH('')
) pre_trimmed (column_names)
GROUP BY table_name, column_names;
Or a version that works correctly if the data might contain characters such as <
WITH extern
AS (SELECT DISTINCT table_name
FROM INFORMATION_SCHEMA.COLUMNS)
SELECT table_name,
LEFT(y.column_names, LEN(y.column_names) - 1) AS column_names
FROM extern
CROSS APPLY (SELECT column_name + ','
FROM INFORMATION_SCHEMA.COLUMNS AS intern
WHERE extern.table_name = intern.table_name
FOR XML PATH(''), TYPE) x (column_names)
CROSS APPLY (SELECT x.column_names.value('.', 'NVARCHAR(MAX)')) y(column_names)
I may be a bit late to the party but this method works for me and is easier than the COALESCE method.
SELECT STUFF(
(SELECT ',' + Column_Name
FROM Table_Name
FOR XML PATH (''))
, 1, 1, '')
SQL Server 2017 does introduce a new aggregate function
STRING_AGG ( expression, separator).
Concatenates the values of string expressions and places separator
values between them. The separator is not added at the end of string.
The concatenated elements can be ordered by appending WITHIN GROUP (ORDER BY some_expression)
For versions 2005-2016 I typically use the XML method in the accepted answer.
This can fail in some circumstances however. e.g. if the data to be concatenated contains CHAR(29) you see
FOR XML could not serialize the data ... because it
contains a character (0x001D) which is not allowed in XML.
A more robust method that can deal with all characters would be to use a CLR aggregate. However applying an ordering to the concatenated elements is more difficult with this approach.
The method of assigning to a variable is not guaranteed and should be avoided in production code.
Possibly too late to be of benefit now, but is this not the easiest way to do things?
SELECT empName, projIDs = replace
((SELECT Surname AS [data()]
FROM project_members
WHERE empName = a.empName
ORDER BY empName FOR xml path('')), ' ', REQUIRED SEPERATOR)
FROM project_members a
WHERE empName IS NOT NULL
GROUP BY empName
Have a look at the GROUP_CONCAT project on Github, I think I does exactly what you are searching for:
This project contains a set of SQLCLR User-defined Aggregate functions (SQLCLR UDAs) that collectively offer similar functionality to the MySQL GROUP_CONCAT function. There are multiple functions to ensure the best performance based on the functionality required...
To concatenate all the project manager names from projects that have multiple project managers write:
SELECT a.project_id,a.project_name,Stuff((SELECT N'/ ' + first_name + ', '+last_name FROM projects_v
where a.project_id=project_id
FOR
XML PATH(''),TYPE).value('text()[1]','nvarchar(max)'),1,2,N''
) mgr_names
from projects_v a
group by a.project_id,a.project_name
With the below code you have to set PermissionLevel=External on your project properties before you deploy, and change the database to trust external code (be sure to read elsewhere about security risks and alternatives [like certificates]) by running ALTER DATABASE database_name SET TRUSTWORTHY ON.
using System;
using System.Collections.Generic;
using System.Data.SqlTypes;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.SqlServer.Server;
[Serializable]
[SqlUserDefinedAggregate(Format.UserDefined,
MaxByteSize=8000,
IsInvariantToDuplicates=true,
IsInvariantToNulls=true,
IsInvariantToOrder=true,
IsNullIfEmpty=true)]
public struct CommaDelimit : IBinarySerialize
{
[Serializable]
private class StringList : List<string>
{ }
private StringList List;
public void Init()
{
this.List = new StringList();
}
public void Accumulate(SqlString value)
{
if (!value.IsNull)
this.Add(value.Value);
}
private void Add(string value)
{
if (!this.List.Contains(value))
this.List.Add(value);
}
public void Merge(CommaDelimit group)
{
foreach (string s in group.List)
{
this.Add(s);
}
}
void IBinarySerialize.Read(BinaryReader reader)
{
IFormatter formatter = new BinaryFormatter();
this.List = (StringList)formatter.Deserialize(reader.BaseStream);
}
public SqlString Terminate()
{
if (this.List.Count == 0)
return SqlString.Null;
const string Separator = ", ";
this.List.Sort();
return new SqlString(String.Join(Separator, this.List.ToArray()));
}
void IBinarySerialize.Write(BinaryWriter writer)
{
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(writer.BaseStream, this.List);
}
}
I've tested this using a query that looks like:
SELECT
dbo.CommaDelimit(X.value) [delimited]
FROM
(
SELECT 'D' [value]
UNION ALL SELECT 'B' [value]
UNION ALL SELECT 'B' [value] -- intentional duplicate
UNION ALL SELECT 'A' [value]
UNION ALL SELECT 'C' [value]
) X
And yields: A, B, C, D
Tried these but for my purposes in MS SQL Server 2005 the following was most useful, which I found at xaprb
declare #result varchar(8000);
set #result = '';
select #result = #result + name + ' '
from master.dbo.systypes;
select rtrim(#result);
#Mark as you mentioned it was the space character that caused issues for me.
About J Hardiman's answer, how about:
SELECT empName, projIDs=
REPLACE(
REPLACE(
(SELECT REPLACE(projID, ' ', '-somebody-puts-microsoft-out-of-his-misery-please-') AS [data()] FROM project_members WHERE empName=a.empName FOR XML PATH('')),
' ',
' / '),
'-somebody-puts-microsoft-out-of-his-misery-please-',
' ')
FROM project_members a WHERE empName IS NOT NULL GROUP BY empName
By the way, is the use of "Surname" a typo or am i not understanding a concept here?
Anyway, thanks a lot guys cuz it saved me quite some time :)
2021
#AbdusSalamAzad's answer is the correct one.
SELECT STRING_AGG(my_col, ',') AS my_result FROM my_tbl;
If the result is too big, you may get error "STRING_AGG aggregation result exceeded the limit of 8000 bytes. Use LOB types to avoid result truncation." , which can be fixed by changing the query to this:
SELECT STRING_AGG(convert(varchar(max), my_col), ',') AS my_result FROM my_tbl;
For my fellow Googlers out there, here's a very simple plug-and-play solution that worked for me after struggling with the more complex solutions for a while:
SELECT
distinct empName,
NewColumnName=STUFF((SELECT ','+ CONVERT(VARCHAR(10), projID )
FROM returns
WHERE empName=t.empName FOR XML PATH('')) , 1 , 1 , '' )
FROM
returns t
Notice that I had to convert the ID into a VARCHAR in order to concatenate it as a string. If you don't have to do that, here's an even simpler version:
SELECT
distinct empName,
NewColumnName=STUFF((SELECT ','+ projID
FROM returns
WHERE empName=t.empName FOR XML PATH('')) , 1 , 1 , '' )
FROM
returns t
All credit for this goes to here:
https://social.msdn.microsoft.com/Forums/sqlserver/en-US/9508abc2-46e7-4186-b57f-7f368374e084/replicating-groupconcat-function-of-mysql-in-sql-server?forum=transactsql
For SQL Server 2017+, use STRING_AGG() function
SELECT STRING_AGG(Genre, ',') AS Result
FROM Genres;
Sample result:
Result
Rock,Jazz,Country,Pop,Blues,Hip Hop,Rap,Punk
Related
SET variable for use as field in query?
I feel like I've done this before but missing something. I'm going through a database I inherited and want to see a bunch of DISTINCT values. I was thinking I could do something like this instead of writing cf_840 (or whatever number) a bunch of times and just change the actual field name in one spot... SET #var = 'cf_840'; SELECT DISTINCT #var, COUNT(#var) AS counter FROM vtiger_leadscf GROUP BY #var ORDER BY #var; But this isn't working right and I feel like I'm missing something simple but can't find the right thing to search on SO.
You cannot use variables to directly specify fields outside of dynamically constructing a query; at best you can choose a value from a field conditionally, like CASE #var WHEN 840 THEN cf_840 WHEN 1 THEN cf_1 .... etc END AS fieldVal Otherwise, you need to dynamically construct a query string with the field name "baked" into the query that gets executed. C# style: var query = String.Format("SELECT {0}, COUNT(DISTINCT {0}) AS counter FROM .... blah blah blah", fieldName); SQL Proc Style: SET query := 'SELECT ' + fieldName + ', COUNT(DISTINCT ' + fieldName.....and so on, then PREPARE and EXECUTE. Edit: I'm not sure why you're selecting #var if you're getting the count of its values. I am guessing you want the field name included in the result, so perhaps the examples would be better as SELECT '{0}' as theField, COUNT(DISTINCT {0}) AS counter ... and 'SELECT ''' + fieldName + ''' AS fieldName, COUNT(DISTINCT ' + fieldName ....` Also, you should not need GROUP BY or ORDER BY clauses, these queries will have only one result row.
To use a variable in the current query, declare it in the body of the SELECT SELECT DISTINCT #var := 'cf_840' AS fieldName, COUNT(#var) AS counter FROM vtiger_leadscf GROUP BY fieldName ORDER BY counter;
Generate a JSON string containing the differences in two other JSON strings using T-SQL
Say I have two JSON strings as follows: [{"RowId":102787,"UserId":1,"Activity":"This is another test","Timestamp":"2017-11-25T14:37:30.3700000"}] [{"RowId":102787,"UserId":2,"Activity":"Testing the Update function","Timestamp":"2017-11-25T14:37:30.3700000"}] Both have the same properties but two of the properties in the second string have different values than the first (UserId and Activity). Is it possible, in Azure SQL Database T-SQL, to generate a third JSON string that contains the values in the second string that are different from the first? In other words, I'd like a string returned that looks like this: [{"UserId":2,"Activity":"Testing the Update function"}] Also, the solution should assume that the properties in the JSON strings are not known. I need this to be a generic solution for any two JSON strings.
Have not tried this on Azure, but it seems to work on SQL Server 2017 There is probably a more elegant way to get to the final JSON string other than through string manipulation, perhaps we can update the answer as better ways are found. -- Expected : [{"UserId":2,"Activity":"Testing the Update function"}] DECLARE #jsonA NVARCHAR(MAX) = '[{"RowId":102787,"UserId":1,"Activity":"This is another test","Timestamp":"2017-11-25T14:37:30.3700000"}]' ,#jsonB NVARCHAR(MAX) = '[{"RowId":102787,"UserId":2,"Activity":"Testing the Update function","Timestamp":"2017-11-25T14:37:30.3700000"}]' ,#result NVARCHAR(MAX) = '' SELECT #jsonA = REPLACE(REPLACE(#jsonA, ']', ''), '[', '') ,#jsonB = REPLACE(REPLACE(#jsonB, ']', ''), '[', '') ;WITH DSA AS ( SELECT * FROM OPENJSON(#jsonA) ) ,DSB AS ( SELECT * FROM OPENJSON(#jsonB) ) SELECT #result += CONCAT ( '"', B.[key], '":' ,IIF(B.[type] = 2, B.[value], CONCAT('"', B.[value], '"')) -- havent checked types other than 1 and 2; think there's a bool type? ,',' ) FROM DSA A JOIN DSB B ON A.[key] = B.[key] WHERE A.[value] != B.[value] SELECT CONCAT('[{', LEFT(#result, LEN(#result) - 1), '}]')
What is bet way to Join two tables with many to many relationship [duplicate]
I'm trying to migrate a MySQL-based app over to Microsoft SQL Server 2005 (not by choice, but that's life). In the original app, we used almost entirely ANSI-SQL compliant statements, with one significant exception -- we used MySQL's group_concat function fairly frequently. group_concat, by the way, does this: given a table of, say, employee names and projects... SELECT empName, projID FROM project_members; returns: ANDY | A100 ANDY | B391 ANDY | X010 TOM | A100 TOM | A510 ... and here's what you get with group_concat: SELECT empName, group_concat(projID SEPARATOR ' / ') FROM project_members GROUP BY empName; returns: ANDY | A100 / B391 / X010 TOM | A100 / A510 So what I'd like to know is: Is it possible to write, say, a user-defined function in SQL Server which emulates the functionality of group_concat? I have almost no experience using UDFs, stored procedures, or anything like that, just straight-up SQL, so please err on the side of too much explanation :)
No REAL easy way to do this. Lots of ideas out there, though. Best one I've found: SELECT table_name, LEFT(column_names , LEN(column_names )-1) AS column_names FROM information_schema.columns AS extern CROSS APPLY ( SELECT column_name + ',' FROM information_schema.columns AS intern WHERE extern.table_name = intern.table_name FOR XML PATH('') ) pre_trimmed (column_names) GROUP BY table_name, column_names; Or a version that works correctly if the data might contain characters such as < WITH extern AS (SELECT DISTINCT table_name FROM INFORMATION_SCHEMA.COLUMNS) SELECT table_name, LEFT(y.column_names, LEN(y.column_names) - 1) AS column_names FROM extern CROSS APPLY (SELECT column_name + ',' FROM INFORMATION_SCHEMA.COLUMNS AS intern WHERE extern.table_name = intern.table_name FOR XML PATH(''), TYPE) x (column_names) CROSS APPLY (SELECT x.column_names.value('.', 'NVARCHAR(MAX)')) y(column_names)
I may be a bit late to the party but this method works for me and is easier than the COALESCE method. SELECT STUFF( (SELECT ',' + Column_Name FROM Table_Name FOR XML PATH ('')) , 1, 1, '')
SQL Server 2017 does introduce a new aggregate function STRING_AGG ( expression, separator). Concatenates the values of string expressions and places separator values between them. The separator is not added at the end of string. The concatenated elements can be ordered by appending WITHIN GROUP (ORDER BY some_expression) For versions 2005-2016 I typically use the XML method in the accepted answer. This can fail in some circumstances however. e.g. if the data to be concatenated contains CHAR(29) you see FOR XML could not serialize the data ... because it contains a character (0x001D) which is not allowed in XML. A more robust method that can deal with all characters would be to use a CLR aggregate. However applying an ordering to the concatenated elements is more difficult with this approach. The method of assigning to a variable is not guaranteed and should be avoided in production code.
Possibly too late to be of benefit now, but is this not the easiest way to do things? SELECT empName, projIDs = replace ((SELECT Surname AS [data()] FROM project_members WHERE empName = a.empName ORDER BY empName FOR xml path('')), ' ', REQUIRED SEPERATOR) FROM project_members a WHERE empName IS NOT NULL GROUP BY empName
Have a look at the GROUP_CONCAT project on Github, I think I does exactly what you are searching for: This project contains a set of SQLCLR User-defined Aggregate functions (SQLCLR UDAs) that collectively offer similar functionality to the MySQL GROUP_CONCAT function. There are multiple functions to ensure the best performance based on the functionality required...
To concatenate all the project manager names from projects that have multiple project managers write: SELECT a.project_id,a.project_name,Stuff((SELECT N'/ ' + first_name + ', '+last_name FROM projects_v where a.project_id=project_id FOR XML PATH(''),TYPE).value('text()[1]','nvarchar(max)'),1,2,N'' ) mgr_names from projects_v a group by a.project_id,a.project_name
With the below code you have to set PermissionLevel=External on your project properties before you deploy, and change the database to trust external code (be sure to read elsewhere about security risks and alternatives [like certificates]) by running ALTER DATABASE database_name SET TRUSTWORTHY ON. using System; using System.Collections.Generic; using System.Data.SqlTypes; using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using Microsoft.SqlServer.Server; [Serializable] [SqlUserDefinedAggregate(Format.UserDefined, MaxByteSize=8000, IsInvariantToDuplicates=true, IsInvariantToNulls=true, IsInvariantToOrder=true, IsNullIfEmpty=true)] public struct CommaDelimit : IBinarySerialize { [Serializable] private class StringList : List<string> { } private StringList List; public void Init() { this.List = new StringList(); } public void Accumulate(SqlString value) { if (!value.IsNull) this.Add(value.Value); } private void Add(string value) { if (!this.List.Contains(value)) this.List.Add(value); } public void Merge(CommaDelimit group) { foreach (string s in group.List) { this.Add(s); } } void IBinarySerialize.Read(BinaryReader reader) { IFormatter formatter = new BinaryFormatter(); this.List = (StringList)formatter.Deserialize(reader.BaseStream); } public SqlString Terminate() { if (this.List.Count == 0) return SqlString.Null; const string Separator = ", "; this.List.Sort(); return new SqlString(String.Join(Separator, this.List.ToArray())); } void IBinarySerialize.Write(BinaryWriter writer) { IFormatter formatter = new BinaryFormatter(); formatter.Serialize(writer.BaseStream, this.List); } } I've tested this using a query that looks like: SELECT dbo.CommaDelimit(X.value) [delimited] FROM ( SELECT 'D' [value] UNION ALL SELECT 'B' [value] UNION ALL SELECT 'B' [value] -- intentional duplicate UNION ALL SELECT 'A' [value] UNION ALL SELECT 'C' [value] ) X And yields: A, B, C, D
Tried these but for my purposes in MS SQL Server 2005 the following was most useful, which I found at xaprb declare #result varchar(8000); set #result = ''; select #result = #result + name + ' ' from master.dbo.systypes; select rtrim(#result); #Mark as you mentioned it was the space character that caused issues for me.
About J Hardiman's answer, how about: SELECT empName, projIDs= REPLACE( REPLACE( (SELECT REPLACE(projID, ' ', '-somebody-puts-microsoft-out-of-his-misery-please-') AS [data()] FROM project_members WHERE empName=a.empName FOR XML PATH('')), ' ', ' / '), '-somebody-puts-microsoft-out-of-his-misery-please-', ' ') FROM project_members a WHERE empName IS NOT NULL GROUP BY empName By the way, is the use of "Surname" a typo or am i not understanding a concept here? Anyway, thanks a lot guys cuz it saved me quite some time :)
2021 #AbdusSalamAzad's answer is the correct one. SELECT STRING_AGG(my_col, ',') AS my_result FROM my_tbl; If the result is too big, you may get error "STRING_AGG aggregation result exceeded the limit of 8000 bytes. Use LOB types to avoid result truncation." , which can be fixed by changing the query to this: SELECT STRING_AGG(convert(varchar(max), my_col), ',') AS my_result FROM my_tbl;
For my fellow Googlers out there, here's a very simple plug-and-play solution that worked for me after struggling with the more complex solutions for a while: SELECT distinct empName, NewColumnName=STUFF((SELECT ','+ CONVERT(VARCHAR(10), projID ) FROM returns WHERE empName=t.empName FOR XML PATH('')) , 1 , 1 , '' ) FROM returns t Notice that I had to convert the ID into a VARCHAR in order to concatenate it as a string. If you don't have to do that, here's an even simpler version: SELECT distinct empName, NewColumnName=STUFF((SELECT ','+ projID FROM returns WHERE empName=t.empName FOR XML PATH('')) , 1 , 1 , '' ) FROM returns t All credit for this goes to here: https://social.msdn.microsoft.com/Forums/sqlserver/en-US/9508abc2-46e7-4186-b57f-7f368374e084/replicating-groupconcat-function-of-mysql-in-sql-server?forum=transactsql
For SQL Server 2017+, use STRING_AGG() function SELECT STRING_AGG(Genre, ',') AS Result FROM Genres; Sample result: Result Rock,Jazz,Country,Pop,Blues,Hip Hop,Rap,Punk
How to Concat data using COALESCE in MySql
This is my SQL query DECLARE #strManualRefundIdList AS VARCHAR(MAX) SELECT #strManualRefundIdList = COALESCE(#strManualRefundIdList + '|', '') + CONVERT(VARCHAR(MAX), ManualRefund_strReasonCode) FROM tblManualRefunds WHERE ManualRefund_lngId IN ( 20 ,21 ,22 ) SELECT #strManualRefundIdList; This gives like pivot, all the rows concatenated in a single row. The Same i need to convert into MySql Query. I tried like this SELECT CONCAT(COALESCE(CONCAT(v_strManualRefundIdList, '|'), '') , ( ManualRefund_lngId)) INTO v_strManualRefundIdList FROM tblManualRefunds WHERE Trans_lngId IN ( 20 ,21 ,22 ) ; But it throws error as Error Code: 1172. Result consisted of more than one row How to translate that query. I am new to database. Please help me in figuring out this. UPDATE : The way i found out was assigning into a cursor and loop through and concatenate it. But is that the only way ? or any better way is available ?
SELECT group_concat(ManualRefund_strReasonCode SEPARATOR '|') FROM tblManualRefunds WHERE manualRefund_lngId in (20,21,22) SQL Fiddle
As per xQbert Comment i tried this SELECT GROUP_CONCAT(COALESCE(CONCAT('shan', '|'), '') , ( ManualRefund_lngId)) INTO #v_strManualRefundIdList FROM tblManualRefunds ; Its working fine. Thanks Man.. !!!
convert mssql query to mysql query
I have the following mssql query that I found on the net that is supposed to help me with a complex mysql query that I have been struggling with for a few days now. SELECT inv.typeID AS typeID, inv.typeName AS typeName, invGroups.groupName AS groupName, inv.published AS published, inv.description AS description, rankVal.valueFloat AS rank, replace (( SELECT skills.attributeName AS [data()] FROM dgmTypeAttributes tattr -- Link between skillbook and attributes INNER JOIN dgmAttributeTypes skills ON (skills.attributeID = tattr.valueInt) WHERE (tattr.typeID = inv.typeID) AND (tattr.attributeID IN (180, 181)) -- Primary and secondary attributes ORDER BY inv.typeID FOR xml path('')), ' ', ',') AS prisec, replace (( SELECT RTRIM(CAST(inv2.typeID AS varchar)) + ',' AS [data()] FROM (SELECT * FROM dgmTypeAttributes WHERE (attributeID in (182, 183, 184)) -- Pre-req skills 1, 2, and 3 AND (typeID = inv.typeID)) tattr2 INNER JOIN invTypes inv2 ON (tattr2.valueInt = inv2.typeID) ORDER BY inv.typeID FOR xml path('')), ' ', ' ') AS prereq, replace (( SELECT RTRIM(CAST(tattr2.valueInt AS varchar)) + ',' AS [data()] FROM (SELECT * FROM dgmTypeAttributes WHERE (attributeID in (277, 278, 279)) AND (typeID = inv.typeID)) tattr2 -- Link between skillbook and attributes ORDER BY inv.typeID FOR xml path('')), ' ', ' ') AS prereqlvl FROM invTypes inv INNER JOIN invGroups ON (inv.groupID = invGroups.groupID) INNER JOIN dgmTypeAttributes rankVal ON (inv.typeID = rankVal.typeID) WHERE invGroups.categoryID = 16 -- Skillbooks category AND rankVal.attributeID = 275 -- Skill rank attribute AND inv.published = 1 GROUP BY inv.typeID, inv.typeName, invGroups.groupName, inv.published, inv.description, rankVal.valueFloat ORDER BY invGroups.groupName, inv.typeName I am so so with mysql but I know nothing of mssql. Can somebody recommend a good method of converting this query that is low or now cost? I do not expect somebody to convert it for me as that would be asking too much, but some suggestions that would point me in the rite direction (aside from learning mssql lolz) would be very nice. Thank you for your time and patience.
'Recommendation: extract the data out of you MySQL database in a delimited file (csv) using the utf8 (unicode) character set. Import into SQL Server using bcp specifying utf8 with "-Jutf8" parameter and character mode "-c".' See this site. Also, there's a nice tool for this.
Those subqueries with FOR XML PATH('') seem to be used to concatenate strings1. See if you can replace them with GROUP_CONCAT in MySQL. The other bits seem to be standard SQL.