利用 MySQL 存储引擎和 API 构建, 使用 SQL 查询语言进行复杂分析: 第四部分






4.60/5 (3投票s)
MySQL 控制台中的 NOSQL 数据
引言
在本系列文章的第 3 部分中,我介绍了 MySQL 存储引擎的概念,它将 Facebook 好友显示在 MySQL 控制台上,以便您进行复杂分析。 这不仅允许对您的好友进行复杂分析,还可以对您好友的好友以及他们的好友等进行分析,即您的完整好友圈。您还可以将分析限制在少数好友。本系列文章继续,这次将 NOSQL 数据显示在 MySQL 控制台上,以便您进行复杂分析。 具体来说,这允许对来自 Parse.com 的数据进行复杂分析。
背景
一种传统方法可能是从 Parse 导出数据并插入到 MySQL 中。
- 一个缺点是每次都必须手动从 Parse 导出数据并插入到 MySQL 中
- 另一个缺点是无法访问 Parse 的最新数据。
另一种传统方法可能是将数据从 Parse.com 复制到 MySQL。
- 这种方法的一个缺点是必须在表中插入和删除行,这非常耗时。
- 另一个缺点是需要在查询表之前触发插入和删除表的日志记录应用程序。
- 另一个缺点是即使只需要检索几行,所有数据也都在表中维护。
- 另一个缺点是表在插入或删除期间可能会被锁定,这会给检索实体带来性能瓶颈。
- 另一个缺点是数据重复
- 另一个缺点是需要双倍存储
相反,提议的方法通过以下方式避免了这些问题:
- 无需从表中插入和删除行
- 无需在查询表之前触发日志记录应用程序
- 当只需要几行时,无需获取所有数据
- 没有瓶颈,因为没有插入和删除操作。
ParseDotComEngine
这个 MySQL 存储引擎与第 3 部分介绍的存储引擎不同,因为它从 Parse.com 检索数据并将其提供给 MySQL。创建了一个名为 Employee 的表,其引擎为 ParseDotComEngine。
create table Employee
(id VARCHAR(255),
name VARCHAR(255),
salary INT unsigned
)engine=ParseDotComEngine;
当用户在 Employee 表上输入 select 查询时,控制权传递给 ParseDotComEngine 的 rnd_init 方法。此方法检查表名是否确实是“Employee”。然后检查 where 子句解析树是否存在。如果存在,则遍历 where 子句解析树以转换为 Parse.com REST API 调用。否则,Parse 上 Employee 类中的所有记录都将提供给 MySQL。
在继续 rnd_init 之前,需要解释以下数据结构。
vector<Employee> m_EmployeeVector;
//(belongs to ha_example class and maintains all employees for the query)
int m_EmployeeVectorIndex;
//(belongs to ha_example class and maintains the index to m_EmployeeVector)
struct Employee
{
string id;
string name;
int salary;
};
int ha_example::rnd_init(bool scan)
{
DBUG_ENTER("ha_example::rnd_init");
if(_stricmp(table_share->table_name.str,"Employee")==0)
{
THD *thd=this->ha_thd();
SELECT_LEX *select_lex=&thd->lex->select_lex;
m_EmployeeVectorIndex=0;
if(select_lex->where==0)
{
GetEmployeesFromParse("",m_EmployeeVector);
}
else
{
stack<ParseStackElement> parseStack;
select_lex->where->traverse_cond(My_Cond_traverser,(void*)&parseStack,Item::traverse_order::POSTFIX);
if(!parseStack.empty()&&parseStack.size()==1)
{
ParseStackElement topElem=parseStack.top();
GetEmployees(topElem.employeeWhereClauseUnitVector,m_EmployeeVector);
}
else
{
GetEmployeesFromParse("",m_EmployeeVector);
}
}
}
DBUG_RETURN(0);
}
在 where 子句解析树的后缀遍历期间,创建并维护 ParseStackElement 结构体堆栈。
struct ParseStackElement
{
vector<EmployeeWhereClauseUnit> employeeWhereClauseUnitVector;
Item *item;
};
ParseStackElement 包含 EmployeeWhereClauseUnit 类型的向量
struct EmployeeWhereClauseUnit
{
string id;
string name;
unsigned int salaryRangeStart;
unsigned int salaryRangeEnd;
};
EmployeeWhereClauseUnit 是 where 子句的基本单元,包含 id、name、salaryRangeStart 和 salaryRangeEnd。例如,考虑以下 where 子句
Select * from employee where id=1 and name="sarmad" and salary between 1000000 and 10000000;
等效的 EmployeeWhereClauseUnit 是
- id=1;
- pname=sarmad
- salaryRangeStart=1000000
- salaryRangeEnd=10000000
rnd_init 调用 where 子句解析树根节点上的 traverse_cond 以按后缀顺序遍历它。My_Cond_traverser 函数在遍历期间被指定为调用。解析堆栈传递给此函数。此函数检查以下内容
- In 运算符:在这种情况下,调用 Item_func_in_case 以获取“in 子句”中指定的 id 或名称或薪水的集合并推入堆栈
- 等于运算符:在这种情况下,调用 Item_func_eq_case 以获取指定的 id 或名称或薪水并推入堆栈
- 小于或小于等于运算符:在这种情况下,调用 Item_func_less_case 以获取指定的从零开始的薪水范围并推入堆栈
- 大于或大于等于运算符:在这种情况下,调用 Item_func_greater_case 以获取指定的以 4294967295 结尾的薪水范围并推入堆栈
- between 运算符:在这种情况下,调用 Item_func_between_case 以获取指定的薪水范围并推入堆栈
- And/Or 运算符:在这种情况下,堆栈被弹出,直到项目(My_Cond_traverser 第一个参数)的子项出现。在弹出期间,EmployeeWhereClauseUnit 类型的向量进行 And 运算和 Or 运算以获得结果集。
void My_Cond_traverser (const Item *item, void *arg)
{
stack<ParseStackElement> *parseStack=(stack<ParseStackElement> *)arg;
vector<EmployeeWhereClauseUnit> employeeWhereClauseUnitVector;
if(dynamic_cast<const Item_func_in*>(item))
{
employeeWhereClauseUnitVector=Item_func_in_case(item);
ParseStackElement elem;
elem.employeeWhereClauseUnitVector=employeeWhereClauseUnitVector;
elem.item=(Item *)item;
parseStack->push(elem);
}
else if(dynamic_cast<const Item_func_eq*>(item))
{
employeeWhereClauseUnitVector=Item_func_eq_case(item);
ParseStackElement elem;
elem.employeeWhereClauseUnitVector=employeeWhereClauseUnitVector;
elem.item=(Item *)item;
parseStack->push(elem);
}
else if(dynamic_cast<const Item_func_ge*>(item)||dynamic_cast<const Item_func_gt*>(item))
{
employeeWhereClauseUnitVector=Item_func_greater_case(item);
ParseStackElement elem;
elem.employeeWhereClauseUnitVector=employeeWhereClauseUnitVector;
elem.item=(Item *)item;
parseStack->push(elem);
}
else if(dynamic_cast<const Item_func_le*>(item)||dynamic_cast<const Item_func_lt*>(item))
{
employeeWhereClauseUnitVector=Item_func_less_case(item);
ParseStackElement elem;
elem.employeeWhereClauseUnitVector=employeeWhereClauseUnitVector;
elem.item=(Item *)item;
parseStack->push(elem);
}
else if(dynamic_cast<const Item_func_between*>(item))
{
employeeWhereClauseUnitVector=Item_func_between_case(item);
ParseStackElement elem;
elem.employeeWhereClauseUnitVector=employeeWhereClauseUnitVector;
elem.item=(Item *)item;
parseStack->push(elem);
}
else if(dynamic_cast<const Item_cond*>(item))
{
const Item_cond *itemCondC=dynamic_cast<const Item_cond*>(item);
Item_cond *itemCond=(Item_cond *)itemCondC;
vector<EmployeeWhereClauseUnit> result;
bool isAnd=false;
if(dynamic_cast<const Item_cond_and*>(item))
{
isAnd=true;
}
if (!parseStack->empty()&&isChildOf((Item_cond*)item,parseStack->top().item))
{
result=parseStack->top().employeeWhereClauseUnitVector;
parseStack->pop();
}
while (!parseStack->empty()&&isChildOf((Item_cond*)item,parseStack->top().item))
{
if(result.empty()&&!parseStack->top().employeeWhereClauseUnitVector.empty())
{
result=parseStack->top().employeeWhereClauseUnitVector;
}
else if(!result.empty()&&parseStack->top().employeeWhereClauseUnitVector.empty())
{
result=result;
}
else
{
if(isAnd)
{
result=And(result,parseStack->top().employeeWhereClauseUnitVector);
}
else
{
result=Or(result,parseStack->top().employeeWhereClauseUnitVector);
}
}
parseStack->pop();
}
ParseStackElement elem;
elem.employeeWhereClauseUnitVector=result;
elem.item=(Item *)item;
parseStack->push(elem);
}
}
以下函数提取用户在 equal 子句中输入的 id 或名称或薪水。代码检查项目是否为 Item_func 类型。然后检查第一个参数是否为字段,第二个参数是否不是字段。然后检查字段是否为 id 或名称或薪水。
vector<EmployeeWhereClauseUnit> Item_func_eq_case(const Item *item)
{
vector<EmployeeWhereClauseUnit> employeeWhereClauseUnitVector;
const Item_func * itemFunction=dynamic_cast<const Item_func*>(item);
Item **arguments=0;
if(itemFunction)
{
arguments=itemFunction->arguments();
}
else
{
return employeeWhereClauseUnitVector;
}
Item *field=0;
Item *value=0;
if(dynamic_cast <Item_field*>(arguments[0]))
{
field = arguments[0];
}
else
{
return employeeWhereClauseUnitVector;
}
if(dynamic_cast <Item_field*>(arguments[1]))
{
return employeeWhereClauseUnitVector;
}
else
{
value = arguments[1];
}
Item_field *f;
if(field&&field->item_name.ptr()&&_stricmp(field->item_name.ptr(),"id")==0)
{
EmployeeWhereClauseUnit myEmployeeWhereClauseUnit;
myEmployeeWhereClauseUnit.id=value->item_name.ptr();
myEmployeeWhereClauseUnit.name="";
myEmployeeWhereClauseUnit.salaryRangeStart=MYSQL_UNSIGNED_INT_MIN_VALUE;
myEmployeeWhereClauseUnit.salaryRangeEnd=MYSQL_UNSIGNED_INT_MAX_VALUE;
employeeWhereClauseUnitVector.push_back(myEmployeeWhereClauseUnit);
}
else if(field&&field->item_name.ptr()&&_stricmp(field->item_name.ptr(),"name")==0)
{
EmployeeWhereClauseUnit myEmployeeWhereClauseUnit;
myEmployeeWhereClauseUnit.id="";
myEmployeeWhereClauseUnit.name=value->item_name.ptr();
myEmployeeWhereClauseUnit.salaryRangeStart=MYSQL_UNSIGNED_INT_MIN_VALUE;
myEmployeeWhereClauseUnit.salaryRangeEnd=MYSQL_UNSIGNED_INT_MAX_VALUE;
employeeWhereClauseUnitVector.push_back(myEmployeeWhereClauseUnit);
}
else if(field&&field->item_name.ptr()&&_stricmp(field->item_name.ptr(),"salary")==0)
{
EmployeeWhereClauseUnit myEmployeeWhereClauseUnit;
myEmployeeWhereClauseUnit.id="";
myEmployeeWhereClauseUnit.name="";
myEmployeeWhereClauseUnit.salaryRangeStart=value->val_int();
myEmployeeWhereClauseUnit.salaryRangeEnd=value->val_int();
employeeWhereClauseUnitVector.push_back(myEmployeeWhereClauseUnit);
}
return employeeWhereClauseUnitVector;
}
以下函数提取“in 子句”中指定的 id 或名称或薪水的集合。首先检查项目是否为 Item_func 类型。然后检查第一个参数是否为字段,以及后续参数是否不是字段类型。
vector<EmployeeWhereClauseUnit> Item_func_in_case(const Item *item)
{
vector<EmployeeWhereClauseUnit> employeeWhereClauseUnitVector;
const Item_func * itemFunction=dynamic_cast<const Item_func*>(item);
Item **arguments=0;
int inArgcount=0;
if(itemFunction)
{
arguments=itemFunction->arguments();
inArgcount=itemFunction->arg_count;
}
else
{
return employeeWhereClauseUnitVector;
}
Item *field=0;
Item *value=0;
if(dynamic_cast <Item_field*>(arguments[0]))
{
field = arguments[0];
}
else
{
return employeeWhereClauseUnitVector;
}
if(field&&field->item_name.ptr()&&_stricmp(field->item_name.ptr(),"id")==0)
{
LONG index;
for (index = 1; index < inArgcount; index++)
{
if(dynamic_cast <Item_field*>(arguments[index]))
{
continue;
}
else
{
value = arguments[index];
}
EmployeeWhereClauseUnit myEmployeeWhereClauseUnit;
myEmployeeWhereClauseUnit.id=value->item_name.ptr();
myEmployeeWhereClauseUnit.name="";
myEmployeeWhereClauseUnit.salaryRangeStart=MYSQL_UNSIGNED_INT_MIN_VALUE;
myEmployeeWhereClauseUnit.salaryRangeEnd=MYSQL_UNSIGNED_INT_MAX_VALUE;
employeeWhereClauseUnitVector.push_back(myEmployeeWhereClauseUnit);
}
}
else if(field&&field->item_name.ptr()&&_stricmp(field->item_name.ptr(),"name")==0)
{
LONG index;
for (index = 1; index < inArgcount; index++)
{
if(dynamic_cast <Item_field*>(arguments[index]))
{
continue;
}
else
{
value = arguments[index];
}
EmployeeWhereClauseUnit myEmployeeWhereClauseUnit;
myEmployeeWhereClauseUnit.id="";
myEmployeeWhereClauseUnit.name=value->item_name.ptr();
myEmployeeWhereClauseUnit.salaryRangeStart=MYSQL_UNSIGNED_INT_MIN_VALUE;
myEmployeeWhereClauseUnit.salaryRangeEnd=MYSQL_UNSIGNED_INT_MAX_VALUE;
employeeWhereClauseUnitVector.push_back(myEmployeeWhereClauseUnit);
}
}
else if(field&&field->item_name.ptr()&&_stricmp(field->item_name.ptr(),"salary")==0)
{
LONG index;
for (index = 1; index < inArgcount; index++)
{
if(dynamic_cast <Item_field*>(arguments[index]))
{
continue;
}
else
{
value = arguments[index];
}
EmployeeWhereClauseUnit myEmployeeWhereClauseUnit;
myEmployeeWhereClauseUnit.id="";
myEmployeeWhereClauseUnit.name="";
myEmployeeWhereClauseUnit.salaryRangeStart=value->val_int();
myEmployeeWhereClauseUnit.salaryRangeEnd=value->val_int();
employeeWhereClauseUnitVector.push_back(myEmployeeWhereClauseUnit);
}
}
return employeeWhereClauseUnitVector;
}
以下函数提取“小于或小于等于子句”中指定的薪水。首先检查项目是否为 Item_func 类型。然后检查第一个参数是否为字段,以及后续参数是否不是字段类型。小于子句转换为 where 子句单元,其中 salaryRangeStart 为零,salaryRangeEnd 等于小于子句中指定的值。
vector<EmployeeWhereClauseUnit> Item_func_less_case(const Item *item)
{
vector<EmployeeWhereClauseUnit> employeeWhereClauseUnitVector;
const Item_func * itemFunction=dynamic_cast<const Item_func*>(item);
Item **arguments=0;
if(itemFunction)
{
arguments=itemFunction->arguments();
}
else
{
return employeeWhereClauseUnitVector;
}
Item *field=0;
Item *value=0;
if(dynamic_cast <Item_field*>(arguments[0]))
{
field = arguments[0];
}
else
{
return employeeWhereClauseUnitVector;
}
if(dynamic_cast <Item_field*>(arguments[1]))
{
return employeeWhereClauseUnitVector;
}
else
{
value = arguments[1];
}
Item_field *f;
if(field&&field->item_name.ptr()&&_stricmp(field->item_name.ptr(),"salary")==0)
{
EmployeeWhereClauseUnit myEmployeeWhereClauseUnit;
myEmployeeWhereClauseUnit.id="";
myEmployeeWhereClauseUnit.name="";
myEmployeeWhereClauseUnit.salaryRangeStart=MYSQL_UNSIGNED_INT_MIN_VALUE;
myEmployeeWhereClauseUnit.salaryRangeEnd=value->val_int();
employeeWhereClauseUnitVector.push_back(myEmployeeWhereClauseUnit);
}
return employeeWhereClauseUnitVector;
}
以下函数提取“大于或大于等于子句”中指定的薪水。首先检查项目是否为 Item_func 类型。然后检查第一个参数是否为字段,以及后续参数是否不是字段类型。大于子句转换为 where 子句单元,其中 salaryRangeStart 为大于子句中指定的值,salaryRangeEnd 等于无符号整数的最大值。
vector<EmployeeWhereClauseUnit> Item_func_greater_case(const Item *item)
{
vector<EmployeeWhereClauseUnit> employeeWhereClauseUnitVector;
const Item_func * itemFunction=dynamic_cast<const Item_func*>(item);
Item **arguments=0;
if(itemFunction)
{
arguments=itemFunction->arguments();
}
else
{
return employeeWhereClauseUnitVector;
}
Item *field=0;
Item *value=0;
if(dynamic_cast <Item_field*>(arguments[0]))
{
field = arguments[0];
}
else
{
return employeeWhereClauseUnitVector;
}
if(dynamic_cast <Item_field*>(arguments[1]))
{
return employeeWhereClauseUnitVector;
}
else
{
value = arguments[1];
}
Item_field *f;
if(field&&field->item_name.ptr()&&_stricmp(field->item_name.ptr(),"salary")==0)
{
EmployeeWhereClauseUnit myEmployeeWhereClauseUnit;
myEmployeeWhereClauseUnit.id="";
myEmployeeWhereClauseUnit.name="";
myEmployeeWhereClauseUnit.salaryRangeStart=value->val_int();
myEmployeeWhereClauseUnit.salaryRangeEnd=MYSQL_UNSIGNED_INT_MAX_VALUE;
employeeWhereClauseUnitVector.push_back(myEmployeeWhereClauseUnit);
}
return employeeWhereClauseUnitVector;
}
以下函数提取“between 子句”中指定的薪水。首先检查项目是否为 Item_func 类型。然后检查第一个参数是否为字段,以及后续参数是否不是字段类型。between 子句转换为 where 子句单元,其中 salaryRangeStart 和 salaryRangeEnd 为 between 子句中指定的值。
vector<EmployeeWhereClauseUnit> Item_func_between_case(const Item *item)
{
vector<EmployeeWhereClauseUnit> employeeWhereClauseUnitVector;
const Item_func * itemFunction=dynamic_cast<const Item_func*>(item);
Item **arguments=0;
if(itemFunction)
{
arguments=itemFunction->arguments();
}
else
{
return employeeWhereClauseUnitVector;
}
Item *field=0;
Item *value1=0;
Item *value2=0;
if(dynamic_cast <Item_field*>(arguments[0]))
{
field = arguments[0];
}
else
{
return employeeWhereClauseUnitVector;
}
if(dynamic_cast <Item_field*>(arguments[1]))
{
return employeeWhereClauseUnitVector;
}
else
{
value1 = arguments[1];
}
if(dynamic_cast <Item_field*>(arguments[2]))
{
return employeeWhereClauseUnitVector;
}
else
{
value2 = arguments[2];
}
Item_field *f;
if(field&&field->item_name.ptr()&&_stricmp(field->item_name.ptr(),"salary")==0)
{
EmployeeWhereClauseUnit myEmployeeWhereClauseUnit;
myEmployeeWhereClauseUnit.id="";
myEmployeeWhereClauseUnit.name="";
myEmployeeWhereClauseUnit.salaryRangeStart=value1->val_int();
myEmployeeWhereClauseUnit.salaryRangeEnd=value2->val_int();;
employeeWhereClauseUnitVector.push_back(myEmployeeWhereClauseUnit);
}
return employeeWhereClauseUnitVector;
}
以下是 My_Cond_traverser 用来检查父子关系的一个辅助函数。
bool isChildOf(Item_cond *parent,Item *child)
{
List_iterator<Item> li(*(parent->argument_list()));
Item *it= NULL;
while ((it= li++))
{
if(child==it)
return true;
}
return false;
}
以下函数对 EmployeeWhereClauseUnit 的向量 A 和 EmployeeWhereClauseUnit 的向量 B 进行 And 运算。在此函数中,两个向量都以嵌套循环方式遍历。如果来自 A 的 EmployeeWhereClauseUnit 类型的一个结构与来自 B 的相同类型的另一个结构匹配,则将此 EmployeeWhereClauseUnit 添加到结果向量中。通过检查来自 A 和 B 的结构(EmployeeWhereClauseUnit)的 id、名称和薪水来找到匹配项。如果两个 id 都不为空,则它们应该匹配,否则跳过它们。同样,如果两个名称都不为空,则它们应该匹配,否则跳过它们。如果其中一个 id 为空而另一个不为空,则公共 id 等于不为空的那个。同样,如果其中一个名称为空而另一个不为空,则公共名称等于不为空的那个。同样,如果来自 A 和 B 的 EmployeeWhereClauseUnit 中的薪水范围之间存在重叠,则将公共值插入到结果向量中。
vector<EmployeeWhereClauseUnit> And(vector<EmployeeWhereClauseUnit> A,vector<EmployeeWhereClauseUnit> B)
{
vector<EmployeeWhereClauseUnit> result;
for(vector<EmployeeWhereClauseUnit>::iterator iter1=A.begin();iter1!=A.end();iter1++)
{
for(vector<EmployeeWhereClauseUnit>::iterator iter2=B.begin();iter2!=B.end();iter2++)
{
string id="";
if(iter1->id!=""&&iter2->id!="")
{
if(iter1->id==iter2->id)
{
id=iter1->id;
}
else
{
continue;
}
}
else if(iter1->id!=""&&iter2->id=="")
{
id=iter1->id;
}
else if(iter1->id==""&&iter2->id!="")
{
id=iter2->id;
}
else if(iter1->id==""&&iter2->id=="")
{
}
string name="";
if(iter1->name!=""&&iter2->name!="")
{
if(iter1->name==iter2->name)
{
name=iter1->name;
}
else
{
continue;
}
}
else if(iter1->name!=""&&iter2->name=="")
{
name=iter1->name;
}
else if(iter1->name==""&&iter2->name!="")
{
name=iter2->name;
}
else if(iter1->name==""&&iter2->name=="")
{
}
unsigned int salaryRangeStart;
unsigned int salaryRangeEnd;
bool common=FindAndBetweenTwoRanges(iter1->salaryRangeStart,iter1->salaryRangeEnd,iter2->salaryRangeStart,iter2->salaryRangeEnd,salaryRangeStart,salaryRangeEnd);
if(common==false)
{
continue;
}
EmployeeWhereClauseUnit myEmployeeWhereClauseUnit;
myEmployeeWhereClauseUnit.id=id;
myEmployeeWhereClauseUnit.name=name;
myEmployeeWhereClauseUnit.salaryRangeStart=salaryRangeStart;
myEmployeeWhereClauseUnit.salaryRangeEnd=salaryRangeEnd;
result.push_back(myEmployeeWhereClauseUnit);
}
}
return result;
}
以下函数找到两个薪水范围之间的 AND 运算。如果范围 1 的开始小于范围 2 的开始,并且范围 2 的开始小于范围 1 的结束,则存在重叠。结果重叠是从范围 2 的开始到范围 1 的结束或范围 2 的结束,具体取决于哪个更小,反之亦然。
bool FindAndBetweenTwoRanges(unsigned int range1Start,unsigned int range1End,unsigned int range2Start,unsigned int range2End,unsigned int &resRangeStart,unsigned int &resRangeEnd)
{
bool success=false;
if(range1Start<range2Start)
{
if(range2Start<=range1End)
{
resRangeStart=range2Start;
if(range1End<range2End)
{
resRangeEnd=range1End;
}
else
{
resRangeEnd=range2End;
}
success=true;
}
}
else
{
if(range1Start<=range2End)
{
resRangeStart=range1Start;
if(range2End<range1End)
{
resRangeEnd=range2End;
}
else
{
resRangeEnd=range1End;
}
success=true;
}
}
return success;
}
以下函数对 EmployeeWhereClauseUnit 的向量 A 和 EmployeeWhereClauseUnit 的向量 B 进行 Or 运算。在此函数中,两个向量都以嵌套循环方式遍历。如果来自 A 的 EmployeeWhereClauseUnit 类型的一个结构与来自 B 的相同类型的另一个结构有共同点,则将公共值添加到结果向量中。通过匹配来自 A 和 B 的结构(EmployeeWhereClauseUnit)的 id、名称和薪水来找到共同点。如果两个 id 都不为空,则它们应该匹配,否则跳过它们。同样,如果两个名称都不为空,则它们应该匹配,否则跳过它们。如果其中一个 id 为空而另一个不为空,则公共 id 为空。同样,如果其中一个名称为空而另一个不为空,则公共名称为空。同样,如果薪水范围之间存在重叠,则将公共值插入到结果向量中,并从向量 A 和 B 中删除。嵌套循环完成后,遍历向量 A 并将所有元素添加到结果向量中。同样,遍历向量 B 并将所有元素添加到结果向量中。
vector<EmployeeWhereClauseUnit> Or(vector<EmployeeWhereClauseUnit> A,vector<EmployeeWhereClauseUnit> B)
{
vector<EmployeeWhereClauseUnit> result;
for(vector<EmployeeWhereClauseUnit>::iterator iter1=A.begin();A.size()>0&&iter1!=A.end();iter1++)
{
for(vector<EmployeeWhereClauseUnit>::iterator iter2=B.begin();B.size()>0&&iter2!=B.end();iter2++)
{
bool commonFound=false;
string id="";
if(iter1->id!=""&&iter2->id!="")
{
if(iter1->id==iter2->id)
{
id=iter1->id;
commonFound=true;
}
else
{
continue;
}
}
string name="";
if(iter1->name!=""&&iter2->name!="")
{
if(iter1->name==iter2->name)
{
name=iter1->name;
commonFound=true;
}
else
{
continue;
}
}
unsigned int salartRangeStart;
unsigned int salaryRangeEnd;
if(FindOrBetweenTwoRanges(iter1->salaryRangeStart,iter1->salaryRangeEnd,iter2->salaryRangeStart,iter2->salaryRangeEnd,salartRangeStart,salaryRangeEnd))
{
commonFound=true;
}
if(commonFound==true)
{
EmployeeWhereClauseUnit myEmployeeWhereClauseUnit;
myEmployeeWhereClauseUnit.id=id;
myEmployeeWhereClauseUnit.name=name;
myEmployeeWhereClauseUnit.salaryRangeStart=salartRangeStart;
myEmployeeWhereClauseUnit.salaryRangeEnd=salaryRangeEnd;
result.push_back(myEmployeeWhereClauseUnit);
A.erase(iter1);
B.erase(iter2);
iter1=A.begin();
iter2=B.begin();
}
}
}
for(vector<EmployeeWhereClauseUnit>::iterator iter1=A.begin();iter1!=A.end();iter1++)
{
result.push_back(*iter1);
}
for(vector<EmployeeWhereClauseUnit>::iterator iter2=B.begin();iter2!=B.end();iter2++)
{
result.push_back(*iter2);
}
return result;
}
以下函数找到两个薪水范围之间的 OR 运算。如果范围 1 的开始小于范围 2 的开始,并且范围 2 的开始小于范围 1 的结束,则存在重叠。结果重叠是从范围 1 的开始到范围 1 的结束或范围 2 的结束,具体取决于哪个更大,反之亦然。
bool FindOrBetweenTwoRanges(unsigned int range1Start,unsigned int range1End,unsigned int range2Start,unsigned int range2End, unsigned int &resRangeStart,unsigned int &resRangeEnd)
{
bool success=false;
if(range1Start<range2Start)
{
if(range2Start<=range1End)
{
resRangeStart=range1Start;
if(range1End>=range2End)
{
resRangeEnd=range1End;
}
else
{
resRangeEnd=range2End;
}
success=true;
}
}
else
{
if(range1Start<=range2End)
{
resRangeStart=range2Start;
if(range2End>=range1End)
{
resRangeEnd=range2End;
}
else
{
resRangeEnd=range1End;
}
success=true;
}
}
return success;
}
当 traverse_cond 函数完成时,会检查解析堆栈是否只包含一个 ParseStackElement 类型的元素。ParseStackElement 中存在的 employeeWhereClauseUnitVector 向量被传递给 GetEmployees 函数,该函数遍历该向量并准备 Parse where 子句,最后调用 GetEmployeesFromParse。
void GetEmployees(vector<EmployeeWhereClauseUnit> employeeWhereClauseUnitVector, vector<Employee> &employees)
{
vector<EmployeeWhereClauseUnit>::iterator iter=employeeWhereClauseUnitVector.begin();
string parseWhereClause="?where={\"$or\":[";
char num[100];
int i=0;
for(;iter!=employeeWhereClauseUnitVector.end();iter++,i++)
{
parseWhereClause=parseWhereClause+"{";
if(iter->id!="")
{
parseWhereClause=parseWhereClause+"\"employeeid\":\""+iter->id+"\",";
}
if(iter->name!="")
{
parseWhereClause=parseWhereClause+"\"name\":\""+iter->name+"\",";
}
_ultoa(iter->salaryRangeStart,num,10);
parseWhereClause=parseWhereClause+"\"salary\":{\"$gte\":"+num;
_ultoa(iter->salaryRangeEnd,num,10);
parseWhereClause=parseWhereClause+",\"$lte\":"+num+"}}";
if(i<employeeWhereClauseUnitVector.size()-1)
{
parseWhereClause=parseWhereClause+",";
}
}
parseWhereClause=parseWhereClause+"]}";
GetEmployeesFromParse(parseWhereClause,employees);
}
GetEmployeesFromParse 函数形成一个 REST API 调用,从 Parse.com 上的 Employee 类中检索数据。遍历结果 JSON 以检索单个员工记录并将其推入 myEmployees。
void GetEmployeesFromParse(string parseWhereClause,vector<Employee> &myEmployees)
{
string url=commonUrl+"Employee";
url=url+parseWhereClause;
string employees=CurlClientResponse(url);
int index=0;
char num[100];
itoa(index,num,10);
string count=num;
string employeeId=GetJsonPathString(employees,"$.results["+count+"].employeeid");
index++;
while(employeeId!="")
{
Employee e;
e.id=employeeId;
e.name=GetJsonPathString(employees,"$.results["+count+"].name");
e.salary=GetJsonPathNum(employees,"$.results["+count+"].salary");
myEmployees.push_back(e);
itoa(index,num,10);
count=num;
employeeId=GetJsonPathString(employees,"$.results["+count+"].employeeid");
index++;
}
}
以下函数使用 curl 库执行 REST API 请求以从 Parse.com 检索对象。请提供您的 X-Parse-Application-Id 和 X-Parse-REST-API-Key。
string CurlClientResponse(string url)
{
string response="";
CURL *curl;
CURLcode res;
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if(curl)
{
struct curl_slist *headers = NULL;
headers=curl_slist_append(headers, "X-Parse-Application-Id:Be2UmXVLHRcHFHd7Tkjy2ih2rB756DD0NWb4ZDX8");
headers=curl_slist_append( headers, "X-Parse-REST-API-Key:NyNHbYuWyyYxKBdfC9bMcfnxDd1ToKXgPegM5SKD");
//curl_easy_setopt(curl, CURLOPT_HTTPGET,1);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback_func);
char *responsePtr = NULL;
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responsePtr);
/* Perform the request, res will get the return code */
res = curl_easy_perform(curl);
/* Check for errors */
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
response=responsePtr;
free(responsePtr);
/* always cleanup */
curl_easy_cleanup(curl);
}
curl_global_cleanup();
return response;
}
MySQL 调用 rnd_next 来提供下一行。如果 m_EmployeeVectorIndex 小于 m_EmployeeVector 的大小,这意味着有行要提供,则在 id、名称和薪水上调用 field -> store。之后递增 m_EmployeeVectorIndex。如果没有更多员工要提供,则返回 HA_ERR_END_OF_FILE,否则返回 0
int ha_example::rnd_next(uchar *buf)
{
int rc;
DBUG_ENTER("ha_example::rnd_next");
MYSQL_READ_ROW_START(table_share->db.str, table_share->table_name.str,
TRUE);
if(m_EmployeeVectorIndex<m_EmployeeVector.size())
{
Field **field=table->field;
bitmap_set_bit(table->write_set, (*field)->field_index);
(*field)->set_notnull();
(*field)->store(m_EmployeeVector[m_EmployeeVectorIndex].id.c_str(),strlen(m_EmployeeVector[m_EmployeeVectorIndex].id.c_str()), system_charset_info);
field++;
bitmap_set_bit(table->write_set, (*field)->field_index);
(*field)->set_notnull();
(*field)->store(m_EmployeeVector[m_EmployeeVectorIndex].name.c_str(),strlen(m_EmployeeVector[m_EmployeeVectorIndex].name.c_str()), system_charset_info);
field++;
bitmap_set_bit(table->write_set, (*field)->field_index);
(*field)->set_notnull();
(*field)->store(m_EmployeeVector[m_EmployeeVectorIndex].salary,true);
field++;
m_EmployeeVectorIndex++;
rc=0;
}
else
{
rc= HA_ERR_END_OF_FILE;
}
MYSQL_READ_ROW_DONE(rc);
DBUG_RETURN(rc);
}
如何运行
在 parse.com 上创建一个帐户
登录 Parse
点击创建新应用
输入应用名称
记下应用程序 ID 和 REST API 密钥。
转到数据浏览器
点击新建类
输入类名 Employee 并点击创建类
添加列 employeeid (字符串)
添加列 name (字符串)
添加列 salary (数字)
向类添加行
- 从以下链接下载 MySQL 5.6.19
- https://dev.mysqlserver.cn/downloads/mysql/
- 确保下载 32 位版本 (mysql-5.6.19-win32.zip)。
- 从 ParseDotComEngineBinaries.zip 复制 ParseDotComEngine.dll
- 转到您解压 MYSQL 的目录 (mysql-5.6.19-win32)。
- 找到子目录 lib 并打开它。
- 找到并打开里面的 plugin 目录。
- 粘贴 ParseDotComEngine.dll。
- 从 ParseDotComEngineBinaries.zip 复制 curllib.dll、libeay32.dll、libsasl.dll、openldap.dll 和 ssleay32.dll
- 找到子目录 bin 并打开它。
- 粘贴复制的 dll
- 启动 mysqld.exe(以管理员身份运行)
- 启动命令提示符(以管理员身份运行)
- 将目录更改为 mysql-5.6.19-win32/bin
- 运行以下命令
- mysql –uroot
- 这将启动 MYSQL 控制台
- 运行以下命令。
- Install plugin ParseDotComEngine soname 'ParseDotComEngine.dll';
- 这将安装 ParseDotComEngine。
- 创建一个名为 test 的数据库。
- 现在创建一个名为 Employee 的表,并将 ParseDotComEngine 指定为存储引擎。
create table Employee
(id VARCHAR(255),
name VARCHAR(255),
salary INT unsigned
)engine=ParseDotComEngine;
在继续之前,如果您在防火墙后面,请允许 mysqld.exe 通过。
输入以下查询
select * from Employee;
现在输入您选择的任何查询,然后查看结果。
接下来,我们将演示如何使用 Tableau 对您的员工进行复杂分析。
在上述 GUI 中,单击“连接到数据”。
从“在服务器上”列表中选择 MySQL
在 MySQL 连接对话框中输入服务器名称、用户名和密码,然后单击“连接”
选择服务器上的 test 数据库
选择 Employee 表
选择实时连接
将 id 拖到列。
将名称拖到行
现在尝试使用不同的方法来分析数据。
在哪里获取源代码
- 从以下链接下载 MYSQL 源代码。
- https://dev.mysqlserver.cn/downloads/mysql/
- 将源代码解压到 C:/mysql-5.6.19
如何构建源代码
- 请按照以下地址提供的说明进行操作:
- https://dev.mysqlserver.cn/doc/refman/5.6/en/installing-source-distribution.html
- 我使用 Visual Studio 2012 按照以下说明构建了源代码:
- cmake . -G "Visual Studio 11"
- 下载本文附件 ParseDotComEngineSourceCode.zip。
- 它包含 ParseDotComEngine 的源代码,位于 example 文件夹中。
- 将 example 文件夹复制到 mysql-5.6.19\storage
结论
我们找到了一种利用 MySQL 解析器功能对 Parse.com 数据进行分析的方法,这在以前从未实现过。这为新类型的分析开辟了新的视野,并且这种方法可以推广。我将在本系列文章的后续部分中继续基于这种方法进行构建。