# Comparison of SQL & SPL: Set-oriented Operations

【Abstract】

Set-oriented operations include a set of basic operations, including concatenation, intersection, union, difference, XOR, and inclusion. As structured data often exists in the form of sets, both SQL and SPL, the two commonly used programming languages, provide a wealth of set-oriented methods and functions to handle the data. This essay explores solutions and basic principles of the two languages for handling set-oriented operations through examples and corresponding sample programs and tries to introduce the more convenient and more efficient way for you.

## 1. Concatenation

Concatenation of set A and set B is a set including all members of set A and all members of set B. It contains duplicate members.

Example 1A vendor has two sales channels, online and store. The sales data for them is stored in two separate tables, SALES_ONLINE and SALES_STORE. The task is to calculate the total sales amount of each product in the year 2014. Below are parts of the two source tables:

SALES_ONLINE

 ID CUSTOMERID ORDERDATE PRODUCTID AMOUNT 1 HANAR 2014/01/01 17 3868.6 2 ERNSH 2014/01/03 42 1005.9 3 LINOD 2014/01/06 64 400.0 4 OTTIK 2014/01/07 10 1194.0 5 OCEAN 2014/01/09 41 319.2 … … … … …

SALES_STORE

 ID CUSTOMERID ORDERDATE SELLERID PRODUCTID AMOUNT 1 EASTC 2014/01/01 1 27 3063.0 2 ERNSH 2014/01/02 8 70 2713.5 3 MAGAA 2014/01/03 2 74 1675.0 4 SAVEA 2014/01/07 7 45 2018.2 5 FOLIG 2014/01/08 8 30 1622.4 … … … … … …

The two tables have different structures. SALES_STORE has SELLERID field for storing IDs of salespeople. But both have ID, CUSTOMERID, ORDERDATE, PRODUCTID and AMOUNT fields.

SQL solution:

select

PRODUCTID, sum(AMOUNT) AMOUNT

from

(select PRODUCTID, AMOUNT

from SALES_ONLINE

where extract (year from ORDERDATE)=2014

union all

select PRODUCTID, AMOUNT

from SALES_STORE

where extract (year from ORDERDATE)=2014)

group by PRODUCTID

order by PRODUCTID

SQL UNION operation is used to combine two or more sub result sets of same structure. They need to have same number of columns and each column should have the same data type. UNION ALL combines all records, including the duplicates.

Here we use ORCALE to do the SQL calculations. Since ORACLE does not have YEAR function, we use extract (year from date) to get the year.

SPL solution:

SPL uses the vertical line “|” to calculate concatenation of sets. A|B represents the concatenation of set A and set B.

 A 1 =T("SalesOnline.csv").select(year(ORDERDATE)==2014) 2 =T("SalesStore.csv").select(year(ORDERDATE)==2014) 3 =A1|A2 4 =A3.groups(PRODUCTID; sum(AMOUNT):AMOUNT)

A1: Import SalesOnline table from the source file and select sales records of 2014.

A2: Import SalesStore table from the source file and select sales records of 2014.

A3: Use the sign “|” to calculate concatenation of the two sets.

A4: Group A3’s result set by product and calculate the total sales amount.

The SPL sequence supports members of different structures, so it does not require that the involved sets have same structure when concatenate them. It can directly access their common fields, like PRODUCTID and AMOUNT in this case, as it accesses an ordinary data table. It’s superbly convenient.

SPL supports retrieving a data table from the database, we can change A1 in the above script as follows:

 A 1 =connect("db").query("select   * from SALES_STORE where extract (year from ORDERDATE)=2014")

Example 2Based on the following scores table, find the top 4 math scores, top 3 English scores and top 2 PE scores. Below is part of the source table:

 CLASS STUDENTID SUBJECT SCORE 1 1 English 84 1 1 Math 77 1 1 PE 69 1 2 English 81 1 2 Math 80 … … … …

SQL solution:

SQL lacks a special method of calculating concatenation of two or more sets. It uses UNION ALL operator to do this. Oracle, for instance, defines an open table, uses ROW_NUMBER() OVER to calculate the rankings of all subjects ordered by scores in descending order, and then get top N for each subject:

with cte1 as

(select

CLASS, STUDENTID, SUBJECT, SCORE,

ROW_NUMBER() OVER(PARTITION BY SUBJECT

ORDER BY SCORE DESC) grouprank

from SCORES)

select CLASS, STUDENTID, SUBJECT, SCORE

from cte1

where grouprank <= 4 and SUBJECT='Math'

UNION ALL

select CLASS, STUDENTID, SUBJECT, SCORE

from cte1

where grouprank <= 3 and SUBJECT='English'

UNION ALL

select CLASS, STUDENTID, SUBJECT, SCORE

from cte1

where grouprank <= 2 and SUBJECT='PE'

SPL solution:

SPL uses A.conj() function to calculate the concatenation of member sets when A is a set of sets.

 A 1 =T("Scores.csv") 2 [Math,English,PE] 3 [4,3,2] 4 =A3.conj(A1.select(SUBJECT==A2(A3.#)).top(-~;SCORE))

A1: Import student scores table.

A2: Define a set of subjects.

A3: Define a set of ranks corresponding to the set of subjects.

A4: Get top N scores for each specified subject, and use A.conj() function to concatenate multiple sets.

As there isn’t a special method of calculating concatenation of sets, we use UNION ALL to combine two sets each time. The code becomes more and more complicated when the number of sets involved increases. SPL has the special function A.conj() to do the job. It can calculate concatenation of any number of sets.

## 2. Intersection

The intersection of set A and set B is a set including all members belonging to both A and B.

Example 3A vendor has two sales channels, online and store. The sales data for them is stored in two separate tables, SALES_ONLINE and SALES_STORE. The task is to find the customers who purchase through both sales channels in the year 2014. Below is part of the source table:

SALES_ONLINE

 ID CUSTOMERID ORDERDATE PRODUCTID AMOUNT 1 HANAR 2014/01/01 17 3868.6 2 ERNSH 2014/01/03 42 1005.9 3 LINOD 2014/01/06 64 400.0 4 OTTIK 2014/01/07 10 1194.0 5 OCEAN 2014/01/09 41 319.2 … … … … …

SALES_STORE

 ID CUSTOMERID ORDERDATE SELLERID PRODUCTID AMOUNT 1 EASTC 2014/01/01 1 27 3063.0 2 ERNSH 2014/01/02 8 70 2713.5 3 MAGAA 2014/01/03 2 74 1675.0 4 SAVEA 2014/01/07 7 45 2018.2 5 FOLIG 2014/01/08 8 30 1622.4 … … … … … …

SQL solution:

SQL INTERSECT operator is used to calculate the intersection of two or more sub result sets. The sub result sets must have same data structure and same number of columns, and their columns should contain data of same types. Below is SQL query:

select

DISTINCT CUSTOMERID

from

SALES_ONLINE

where

extract (year from ORDERDATE)=2014

INTERSECT

select

DISTINCT CUSTOMERID

from

SALES_STORE

where

extract (year from ORDERDATE)=2014

Early databases probably do not support INTERSECT operator. So we can first calculate concatenation (UNION ALL) and then perform a filtering to get the target through grouping operation and count operation:

select

CUSTOMERID, COUNT(*)

from

(select DISTINCT CUSTOMERID

from SALES_ONLINE

where extract (year from ORDERDATE)=2014

UNION ALL

select DISTINCT CUSTOMERID

from SALES_STORE

where extract (year from ORDERDATE)=2014)

group by

CUSTOMERID

HAVING

COUNT(*)>1

order by

CUSTOMERID

The additional layer of grouping and aggregation makes the SQL query extremely complicated.

SPL solution:

SPL uses the sign “^” to calculate intersection of sets. A^B represents the intersection of set A and set B.

 A 1 =T("SalesOnline.csv").select(year(ORDERDATE)==2014) 2 =T("SalesStore.csv").select(year(ORDERDATE)==2014) 3 =A1.id(CUSTOMERID)^A2.id(CUSTOMERID)

A1: Import SalesOnline table from the source file and select sales records of 2014.

A2: Import SalesStore table from the source file and select sales records of 2014.

A3: A.id() function gets the set of unique customer IDs. The sign “^” is used to calculate intersection of the two sets, which are customers who purchase products in both in online and at stores.

Example 4Based on the following sales data table, find top 10 customers in terms of monthly sales amount in the year 2014. Below is part of the source table:

 ID CUSTOMERID ORDERDATE SELLERID PRODUCTID AMOUNT 10400 EASTC 2014/01/01 1 27 3063.0 10401 HANAR 2014/01/01 1 17 3868.6 10402 ERNSH 2014/01/02 8 70 2713.5 10403 ERNSH 2014/01/03 4 42 1005.9 10404 MAGAA 2014/01/03 2 74 1675.0 … … … … … …

SQL solution:

with cte1 as

(select

extract (month from ORDERDATE) ORDERMONTH,

CUSTOMERID, SUM(AMOUNT) AMOUNT

from SALES

where extract (year from ORDERDATE)=2014

group by extract (month from ORDERDATE),CUSTOMERID

order by ORDERMONTH ASC, AMOUNT DESC),

cte2 as

(select

ORDERMONTH,CUSTOMERID,AMOUNT,

ROW_NUMBER()OVER(PARTITION BY ORDERMONTH ORDER BY AMOUNT DESC) grouprank

from cte1)

select

CUSTOMERID,count(*)

from cte2

where grouprank<=10

group by CUSTOMERID

having count(*)=12

SQL has not a method of calculating intersection of sets. The language uses INTERSECT operator to do the job. It is unsuitable to get top 10 customers in each month and then use INTERSECT operator to get the combination. Here we group records and perform a count operation. If a customer ranking in monthly top 10 for 12 times, it enters in the top 10 in each month.

SPL solution:

SPL uses A.isect() function to calculate intersection of all member sets if A is a set of sets.

 A 1 =T("Sales.csv").select(year(ORDERDATE)==2014) 2 =A1.group(month(ORDERDATE)) 3 =A2.(~.groups(CUSTOMERID;sum(AMOUNT):AMOUNT)) 4 =A3.(~.top(-10;AMOUNT).(CUSTOMERID)) 5 =A4.isect()

A1: Import Sales table from the source file and select records of the year 2014.

A2: Group A1’s table by month.

A3: Group records in each month by customer.

A4: Get customers whose monthly sales amounts rank in top 10 in each month.

A5: A.isect() function calculates intersection of all selected customers in all months.

Though we employ a trick to calculate the intersection, the SQL query is still complex. One reason is that SQL does not have a special method to calculate intersection of sets, the other is that SQL cannot retain the post-grouping subsets for a further grouping operation or other computations. The grouping operation will be explained separately later.

## 3. Union

The union of set A and set B is a set including all members of A and members of B, except for the duplicates.

Example 5A vendor has two sales channels, online and store. The sales data for them is stored in two separate tables, SALES_ONLINE and SALES_STORE. The task is to find the products whose total online sales amounts in the year 2014 is above 10,000 or that are purchased over 5 times at stores. Below are parts of the source tables:

SALES_ONLINE

 ID CUSTOMERID ORDERDATE PRODUCTID AMOUNT 1 HANAR 2014/01/01 17 3868.6 2 ERNSH 2014/01/03 42 1005.9 3 LINOD 2014/01/06 64 400.0 4 OTTIK 2014/01/07 10 1194.0 5 OCEAN 2014/01/09 41 319.2 … … … … …

SALES_STORE

 ID CUSTOMERID ORDERDATE SELLERID PRODUCTID AMOUNT 1 EASTC 2014/01/01 1 27 3063.0 2 ERNSH 2014/01/02 8 70 2713.5 3 MAGAA 2014/01/03 2 74 1675.0 4 SAVEA 2014/01/07 7 45 2018.2 5 FOLIG 2014/01/08 8 30 1622.4 … … … … … …

SQL solution:

select

PRODUCTID

from

(select PRODUCTID, sum(AMOUNT)

from SALES_ONLINE

where extract (year from ORDERDATE)=2014

group by PRODUCTID

having sum(AMOUNT)>10000)

union

select

PRODUCTID

from

(select PRODUCTID, count(*)

from SALES_STORE

where  extract (year from ORDERDATE)=2014

group by PRODUCTID

having count(*)>5)

As we have mentioned, SQL UNION operator is used to combine results sets of two or more SELECT statements. When UNION is used without ALL, the operator removes duplicate records while combining records.

SPL solution:

SPL offers the and sign “&” to calculate the union of sets. A&B represents the union of set A and set B.

 A 1 =T("SalesOnline.csv").select(year(ORDERDATE)==2014) 2 =T("SalesStore.csv").select(year(ORDERDATE)==2014) 3 =A1.groups(PRODUCTID;   sum(AMOUNT):AMOUNT).select(AMOUNT>10000) 4 =A2.groups(PRODUCTID; count(~):COUNT).select(COUNT>5) 5 =A3.(PRODUCTID)&A4.(PRODUCTID)

A1: Import SalesOnline table from the source file and select records of the year 2014.

A2: Import SalesStore table from the source file and select records of the year 2014.

A3: Group A1’s records by product ID, calculate the total sales amount for each product, and select records where the totals are above 10,000.

A4: Group A2’s records by product ID, count purchase frequencies for each product, and select records where the frequency count is greater than 5.

A5: Use “&” to calculate the union of records purchased both through online and at stores.

Example 6Based on the following sales table, find the products whose yearly purchase frequency ranks in top 10. Below is part of the source table:

 ID CUSTOMERID ORDERDATE SELLERID PRODUCTID AMOUNT 10400 EASTC 2014/01/01 1 27 3063.0 10401 HANAR 2014/01/01 1 17 3868.6 10402 ERNSH 2014/01/02 8 70 2713.5 10403 ERNSH 2014/01/03 4 42 1005.9 10404 MAGAA 2014/01/03 2 74 1675.0 … … … … … …

SQL solution:

with cte1 as

(select

extract (year from ORDERDATE) ORDERYEAR,

PRODUCTID, COUNT(*) ORDERCOUNT

from SALES

group by extract (year from ORDERDATE),PRODUCTID

order by ORDERYEAR ASC, ORDERCOUNT DESC),

cte2 as

(select

ORDERYEAR,PRODUCTID,ORDERCOUNT,

ROW_NUMBER()OVER(PARTITION BY ORDERYEAR ORDER BY ORDERCOUNT DESC) grouprank

from cte1)

select

DISTINCT PRODUCTID

from cte2

where grouprank<=10

SQL does not offer a special method of calculating union of sets, so we group data by year, calculate the total purchase frequency of each product per year, sort the result records, get rankings through row numbers after sorting, then select IDs of products that rank in top 10 in each year in terms of purchase frequency, and finally, use DISTINCT operator to remove the duplicates to get the union.

SPL solution:

SPL uses A.union() function to calculate union of all member sets when A is a set of sets.

 A 1 =T("Sales.csv") 2 =A1.group(year(ORDERDATE)) 3 =A2.(~.groups(PRODUCTID;count(~):COUNT)) 4 =A3.(~.top(-5;COUNT).(PRODUCTID)) 5 =A4.union()

A1: Import Sales table from the source file.

A2: Group A1’s table by year.

A3: Group records of each year by product and calculate their purchase frequency.

A4: Get IDs of products whose yearly purchase frequencies rank in top 10.

A5: A.union() function calculates union of desired records of products in all years.

## 4. Difference

The difference of set A and set B is a set including all members of A that do not belong to set B.

Example 7A vendor has two sales channels, online and store. The sales data for them is stored in two separate tables, SALES_ONLINE and SALES_STORE. The task is to find customers whose total purchase amount at stores is above 1000 but who have not any online purchase records. Below is part of the source table:

SALES_ONLINE

 ID CUSTOMERID ORDERDATE PRODUCTID AMOUNT 1 HANAR 2014/01/01 17 3868.6 2 ERNSH 2014/01/03 42 1005.9 3 LINOD 2014/01/06 64 400.0 4 OTTIK 2014/01/07 10 1194.0 5 OCEAN 2014/01/09 41 319.2 … … … … …

SALES_STORE

 ID CUSTOMERID ORDERDATE SELLERID PRODUCTID AMOUNT 1 EASTC 2014/01/01 1 27 3063.0 2 ERNSH 2014/01/02 8 70 2713.5 3 MAGAA 2014/01/03 2 74 1675.0 4 SAVEA 2014/01/07 7 45 2018.2 5 FOLIG 2014/01/08 8 30 1622.4 … … … … … …

SQL solution:

select

CUSTOMERID

from

(select

CUSTOMERID,count(*)

from

SALES_STORE

where

extract (year from ORDERDATE)=2014

group by

CUSTOMERID

having

count(*)>3)

MINUS

select

DISTINCT CUSTOMERID

from

SALES_ONLINE

where

extract (year from ORDERDATE)=2014

Take Oracle SQL as an example. The MINUS operator (some databases use EXCEPT) is used to calculate difference. MINUS requires that the involved sub result sets must have same data structures and same number of columns, and that corresponding columns should have same or similar data types.

SPL solution:

SPL uses the backslash “\” to calculate difference of sets. A\B represents the difference of set A and set B.

 A 1 =T("SalesOnline.csv").select(year(ORDERDATE)==2014) 2 =T("SalesStore.csv").select(year(ORDERDATE)==2014) 3 =A2.groups(CUSTOMERID;   count(~):COUNT).select(COUNT>3) 4 =A3.id(CUSTOMERID)\A1.id(CUSTOMERID)

A1: Import SalesOnline table from the source file and select records of the year 2014.

A2: Import SalesStore table from the source file and select records of the year 2014.

A3: Group A2’s records by customer IDs, calculate the total purchase frequency for each customer, and select records where the total frequency is greater than 3.

A4: Use “\” to calculate difference of customers who purchase products online and those who buy things at stores.

Example 8Based on the following sales table, find customers whose total sales amounts rank in top 10 only in January in the year 2014. Below is part of the source table:

 ID CUSTOMERID ORDERDATE SELLERID PRODUCTID AMOUNT 10400 EASTC 2014/01/01 1 27 3063.0 10401 HANAR 2014/01/01 1 17 3868.6 10402 ERNSH 2014/01/02 8 70 2713.5 10403 ERNSH 2014/01/03 4 42 1005.9 10404 MAGAA 2014/01/03 2 74 1675.0 … … … … … …

SQL solution:

with cte1 as

(select

extract (month from ORDERDATE) ORDERMONTH,

CUSTOMERID, SUM(AMOUNT) AMOUNT

from SALES

where extract (year from ORDERDATE)=2014

group by extract (month from ORDERDATE),CUSTOMERID

order by ORDERMONTH ASC, AMOUNT DESC),

cte2 as

(select

ORDERMONTH,CUSTOMERID,AMOUNT,

ROW_NUMBER()OVER(PARTITION BY ORDERMONTH ORDER BY AMOUNT DESC) grouprank

from cte1)

select CUSTOMERID

from cte2

where grouprank<=10 and ORDERMONTH=1

MINUS

select CUSTOMERID

from cte2

where grouprank<=10 and ORDERMONTH<>1

SQL does not have a particular method for calculating difference of sets, so we group data by month, calculate the total sales amount of each customer per month, sort the result records, get rankings through row numbers after sorting, select customers of January, and use MINUS operator to calculate difference January’s customers and the set of customers in other months.

SPL solution:

SPL offers A.diff() function to calculate difference of the first member set and all the other member sets when A is a set of sets.

 A 1 =T("Sales.csv").select(year(ORDERDATE)==2014) 2 =A1.group(month(ORDERDATE)) 3 =A2.(~.groups(CUSTOMERID;sum(AMOUNT):AMOUNT)) 4 =A3.(~.top(-10;AMOUNT).(CUSTOMERID)) 5 =A4.diff()

A1: Import Sales table from the source file and select records of the year 2014.

A2: Group A1’s records by month.

A3: Group records of each month by customer IDs and calculate total sales of each customer.

A4: Get customers in each month whose total sales amounts rank in top 10.

A5: Use A.diff() function to calculate difference between customers of January and those in the other months.

## 5. XOR

XOR of set A and set B is a set made up members that belong to A or B but not belong to both.

Example 9Students’ scores in two semesters are stored in two tables respectively. The task is to find students in class 1 whose total scores rank in top 10 only once in both semesters. Below is part of the source table:

SCORES1_SEMESTER1:

 CLASS STUDENTID SUBJECT SCORE 1 1 English 84 1 1 Math 77 1 1 PE 69 1 2 English 81 1 2 Math 80 … … … …

SCORES2_SEMESTER2:

 CLASS STUDENTID SUBJECT SCORE 1 1 English 97 1 1 Math 64 1 1 PE 97 1 2 English 56 1 2 Math 82 … … … …

SQL solution:

SQL does not define an operator for performing XOR. There are two methods if we choose to use set operators to do this:

1. (A UNION B) EXCEPT (A INTERSECT B);

2. (A EXCEPT B) UNION (B EXCEPT A);

Both methods are not convenient because they use multiple views and thus drag performance down. In the following query, we use FULL OUTER JOIN to counter the disadvantage:

with cte1 as

(select STUDENTID,TOTALSCORE

from

(select STUDENTID, sum(SCORE) TOTALSCORE

from SCORES1

group by STUDENTID

order by TOTALSCORE DESC)

where rownum <= 10),

cte2 as

(select STUDENTID,TOTALSCORE

from

(select STUDENTID, sum(SCORE) TOTALSCORE

from SCORES2

group by STUDENTID

order by TOTALSCORE DESC)

where rownum <= 10)

select

COALESCE(cte1.STUDENTID, cte2.STUDENTID) STUDENTID,

cte1.TOTALSCORE, cte2.TOTALSCORE

from cte1

FULL OUTER JOIN cte2

ON cte1.STUDENTID=cte2.STUDENTID

where cte1.TOTALSCORE IS NULL

OR cte2.TOTALSCORE IS NULL

With Oracle SQL, we can use LEFT JOIN UNION RIGHT JOIND to implement FULL JOIN if the involved database is one that does not support FULL JOIN, such as MySQL (Detail query is skipped here).

SPL solution:

SPL uses the percent sign “%” to calculate XOR. A%B represents XOR of set A and set B.

 A 1 =T("Scores1.csv") 2 =T("Scores2.csv") 3 =A1.groups(STUDENTID; sum(SCORE):SCORE) 4 =A2.groups(STUDENTID; sum(SCORE):SCORE) 5 =A3.top(-10;SCORE).(STUDENTID) 6 =A4.top(-10;SCORE).(STUDENTID) 7 =A5%A6

A1: Import Scores1 table of from source file.

A2: Import Scores2 table of from source file.

A3: Group A1’s table by student ID and sum scores of each student.

A4: Group A2’s table by student ID and sum scores of each student.

A5: Get IDs of students whose total scores rank in top 10 in semester 1.

A6: Get IDs of students whose total scores rank in top 10 in semester 2.

A7: Use “%” to calculate XOR of student total scores in semester 1 and those in semester 2.

The SQL query is enormously complicated because there isn’t a particular operator in SQL to calculate XOR. SPL, however, is convenient by offering the percent sign “%” to do it.

Example 10Based on the following table, find whether customer RATTC ranked in top 3 in the year 2014 in terms of sales amount in a single month. Below is part of the source data:

 ID CUSTOMERID ORDERDATE SELLERID PRODUCTID AMOUNT 10400 EASTC 2014/01/01 1 27 3063.0 10401 HANAR 2014/01/01 1 17 3868.6 10402 ERNSH 2014/01/02 8 70 2713.5 10403 ERNSH 2014/01/03 4 42 1005.9 10404 MAGAA 2014/01/03 2 74 1675.0 … … … … … …

SQL solution:

with cte1 as

(select

extract (month from ORDERDATE) ORDERMONTH,

CUSTOMERID, SUM(AMOUNT) AMOUNT

from SALES

where extract (year from ORDERDATE)=2014

group by extract (month from ORDERDATE),CUSTOMERID

order by ORDERMONTH ASC, AMOUNT DESC),

cte2 as

(select

ORDERMONTH,CUSTOMERID,AMOUNT,

ROW_NUMBER()OVER(PARTITION BY ORDERMONTH ORDER BY AMOUNT DESC) grouprank

from cte1)

select count(*) CUSTOMER_EXISTS

from cte2

where grouprank<=3 and CUSTOMERID='RATTC'

SQL does not have a specific method for getting XOR, so we group data by month, calculate the total sales amount of each customer per month, sort the result records, get rankings through row numbers after sorting, select customers entering top 3 in each month, and count records of customer "RATTC". If the count result is 1, it means true; and if it is 0, it means false.

SPL solution:

SPL offers A.cor() function to calculate XOR of member sets when A is a set of sets.

 A 1 =T("Sales.csv").select(year(ORDERDATE)==2014) 2 =A1.group(month(ORDERDATE)) 3 =A2.(~.groups(CUSTOMERID;   sum(AMOUNT):AMOUNT)) 4 =A3.new(~.top(-3; AMOUNT):TOP3) 5 =A4.(TOP3.(CUSTOMERID).pos("RATTC")>0) 6 =A5.cor()

A1: Import Sales table from the source file and select records of the year 2014.

A2: Group Sales table by month.

A3: Group records of each month by customer and calculate total sales amount of each customer in each group.

A4: Get customers whose sales amounts rank in top 3 in each month.

A5: Find if there is customer "RATTC" in each group.

A6: Use A.cor() function to find XOR, that is, whether customer "RATTC" is included in each month. The customer exists if the result is true, and it does not exist if the result is false.

## 6. Belong to & include

Suppose there are two sets A and B. If all members of set A are members of set B, we call that B includes A. The “belong to” relationship is between a member and a set. When there is a member x in set A, we call that x belongs to A.

Example 11Based on the following employee table, calculate the average salary in each department in the states of California, New York, Texas and Washington. Below is part of the source table:

 ID NAME SURNAME STATE DEPT SALARY 1 Rebecca Moore California R&D 7000 2 Ashley Wilson New   York Finance 11000 3 Rachel Johnson New   Mexico Sales 9000 4 Emily Smith Texas HR 7000 5 Ashley Smith Texas R&D 16000 … … … … … …

SQL solution:

SQL IN operator is used to define a “belong to” relationship in WHERE sub statement. The SQL query is as follows:

select

DEPT, avg(SALARY) AVGSALARY

from

EMPLOYEE

where

STATE in ('California','New York','Texas','Washington')

group by

DEPT

SPL solution:

SPL supplies A.contain(x) function to check whether member x belongs to set A.

 A 1 =T("Employee.csv") 2 [California,New York,Texas,Washington] 3 =A1.select(A2.contain(STATE)) 4 =A3.groups(DEPT; avg(SALARY):SALARY)

A1: Import Employee table from the source file.

A2: Define a constant set of states.

A3: Select records from A1’s table where the states belong to A2’s set.

A4: Group the selected records in A3 by department and calculate the average salary in each department.

Example 12Based on COURSE table and SELECT_COURSE table, find students who select both Matlab and Modern wireless communication system. Below is part of the s source table:

COURSE:

 ID NAME TEACHERID 1 Environmental   protection and sustainable development 5 2 Mental   health of College Students 1 3 Matlab 8 4 Electromechanical   basic practice 7 5 Introduction to modern life science 3 … … …

SELECT_COURSE:

 ID STUDENT_NAME COURSE 1 Rebecca   Moore 2,7 2 Ashley   Wilson 1,8 3 Rachel   Johnson 2,7,10 4 Emily   Smith 1,10 5 Ashley   Smith 5,6 … … …

The task can be described in another way. It checks whether the COURSE field value in SELECT_COURSE table contains the set [3,6], which are IDs of Matlab and Modern wireless communication system.

SQL solution:

The SQL field does not support set data type, so we cannot use the set include relationship to get this task done. With Oracle database here, we use REGEXP_SUBSTR function to split each COURSE value string according to a specific regular expression, left join SELECT_COURSE table and COURSE table to get records selecting both courses, group these records by IDs, get groups containing at least two records, that is, those selecting both courses, and then locate corresponding records from SELECT_COURSE table according to the selected IDs. Below is SQL query:

with cte1 as

(SELECT ID,REGEXP_SUBSTR(t1.COURSE ,'[^,]+',1,l) COURSE

FROM SELECT_COURSE t1,

(SELECT LEVEL l

FROM DUAL

CONNECT BY LEVEL<=10) t2

WHERE l<=LENGTH(t1.COURSE) - LENGTH(REPLACE(COURSE,','))+1)

select *

from SELECT_COURSE t5

inner join (

select ID, count(*)

from (

select t3.ID, COURSE

from cte1 t3

inner join (

select ID

from COURSE

where NAME='Matlab' or

NAME='Modern wireless communication system'

) t4

on t3.COURSE=t4.ID

)

group by ID

having count(*)>=2

) t6

on t5.ID=t6.ID

SPL solution:

SPL uses A.pos(B) function locate the position of a member of set B in set A and returns null if the member does not exist in set A.

 A 1 =T("Course.txt") 2 =T("SelectCourse.txt") 3 =A1.select(NAME=="Matlab" || NAME=="Modern wireless   communication system").(ID) 4 =A2.run(COURSE=COURSE.split@cp()) 5 =A4.select(COURSE.pos(A3)!=null)

A1: Import Course table from the source file.

A2: Import SelectCourse table from the source file.

A3: Get the set of IDs of the target two courses.

A4: Split each Course value by comma and parse the numbers into a set.

A5: Use A.pos() function to locate IDs of the target courses in COURSE value of  SELECT_COURSE table, and a record that does not make it return null is a desired one.

As SQL lacks support of set type field values, it is a little complicated to handle this case. The language does snot provide a method of checking a set include relationship, so it handles the job using the filtering join. SPL, however, supports set type field values and offers a rich library of functions to locate members of a set, which facilitates the handling of set include relationship judgment cases.

Example 13Based on the following weather data in a certain area, find the dates when west wind occurs and when north wind visits in the previous dates. Below is part of the source table:

 WEATHER_DATE RAIN_FALL WIND_GUST_DIR WIND_GUST_SPEED RAIN_TODAY RAIN_TOMORROW 2008/12/01 0.6 W 44 No No 2008/12/02 0.0 WNW 44 No No 2008/12/03 0.0 WSW 46 No No 2008/12/04 0.0 NE 24 No No 2008/12/05 1.0 W 41 No No … … … … … …

SQL solution:

The task is simple. It aims to find an ordered subset [N,N,W] in WIND_GUST_DIR set. SQL has a weak support for order-based calculations due to its unordered-set-based theoretic foundation (which is explained in Comparison of SQL & SPL: Order-based calculation). When the SQL you are using does not support window functions, you can only do this through table joins. Below is SQL query:

select

curr.WEATHER_DATE, RAIN_FALL,curr.WIND_GUST_DIR,

WIND_GUST_SPEED,RAIN_TODAY,RAIN_TOMORROW

from

weather curr

inner join

(select

pre1.WEATHER_DATE,pre1.WIND_GUST_DIR

from weather pre1

left join

(select

WEATHER_DATE,WIND_GUST_DIR

from weather) pre2

on

pre1.WEATHER_DATE=pre2.WEATHER_DATE+1

where

pre1.WIND_GUST_DIR='N' and pre2.WIND_GUST_DIR='N') yest

on

curr.WEATHER_DATE=yest.WEATHER_DATE+1

where

curr.WIND_GUST_DIR='W'

order by WEATHER_DATE

The SQL query is roundabout. Each inter-row access requires a self-join. It is inefficient. SQL introduced window functions in the year 2003 and brought in the concept of order. That has made the order-based calculations slightly easier:

select

WEATHER_DATE,RAIN_FALL,WIND_GUST_DIR,

WIND_GUST_SPEED,RAIN_TODAY,RAIN_TOMORROW

from

(select

WEATHER_DATE,RAIN_FALL,WIND_GUST_DIR,

WIND_GUST_SPEED,RAIN_TODAY,RAIN_TOMORROW,

LAG(WIND_GUST_DIR,1)

OVER (

ORDER BY WEATHER_DATE ASC

) YESTERDAY_DIR,

LAG(WIND_GUST_DIR,2)

OVER (

ORDER BY WEATHER_DATE ASC

) BEFORE_YESTERDAY_DIR

from WEATHER)

where

WIND_GUST_DIR='W' and YESTERDAY_DIR='N'

and BEFORE_YESTERDAY_DIR='N'

order by WEATHER_DATE

SPL solution:

SPL supports accessing a record previous to the current one or after it for order-based calculations.

 A 1 =T("weather.csv") 2 =A1.select(WIND_GUST_DIR[-2]=="N" &&   WIND_GUST_DIR[-1]=="N" && WIND_GUST_DIR=="W")

A1: Import weather data from the source file.

A2: Select records where WIND_GUST_DIR values are west and the previous two values are north.

## Summary

SQL gives a good support for concatenation, intersection, union, and difference of two sets but a not good one for XOR. The language does not support theses operations on more sets, and it resorts to roundabout ways to get do jobs. SPL provides special functions for each type of set-oriented operations. This makes SPL code concise, efficient, and easy to understand.

SQL does not support set type field values. When a field value is separated by a certain identifier, SQL cannot perform set include operation on it. The language is awkward and produces complicated code in handling the “belong to” relationship on an ordered subset even with the window function. SPL designs a lot of overall location functions to deal with the set “belong to” relationship. It also supplies a complete set of supports for order-based calculations, which makes it easy to handle inter-row operations.

When the query is complicated, the complexity of SQL query increases by multiple times. It involves the use of temporary table and nested query, etc, which makes it harder to write and maintain the SQL query. SPL, however, can compose succinct code step by step according to the natural way of thinking.

The SPL-driven esProc is the professional data computation engine. It is ordered-set-based and offers a complete set of set-oriented functions, which combines the advantages of both Java and SQL. With SPL, a set-oriented operation becomes simple and easy.