结构体、数组和字符串
概述
以下 SmartESQL 示例代码片段说明了如何使用 Field、Struct 和 Array 类来访问结构中的元素。
示例代码使用了以下模式定义:
declare database structuresdb;
struct aPoint
{
int4 x;
int4 y;
};
struct aLine
{
aPoint begin;
aPoint end;
};
struct aPolygon
{
vector<aPoint> points;
};
class aRecord
{
string s;
int2 i2;
aPoint p;
int1 i1;
int8 i8;
float f;
double d;
aLine l;
int1 ai1[100];
vector<aPolygon> vp;
aLine lines[10];
int4 i4;
hash<i4> by_i4[1000];
list;
autoid[1000];
};在类 aRecord 中定义的嵌入式结构 aPoint、aLine 以及向量或 aPolygon 结构。这些结构在以下代码片段的开头处由 C++ 结构定义进行了映射。
const char * db_name = "structuresdb";
…
// 定义全局 SQL 引擎
using namespace McoSql;
McoSqlEngine engine;
// 定义与数据库表相对应的结构
struct _Point
{
int x;
int y;
};
struct _Line
{
_Point begin;
_Point end;
};
struct _Polygon
{
Array* points;
};
struct _Record
{
char* s;
short i2;
_Point p;
char i1;
int64_t i8;
float f;
double d;
_Line l;
Array* ai1;
Array* vp;
Array* lines;
int i4;
};
…
int main( int argc, char* argv[] )
{
…
engine.open( db_name, structuresdb_get_dictionary(), DATABASE_SIZE, MEMORY_PAGE_SIZE);
// 插入记录
insertRecords();
// 显示内容(不含数组)
showRecords();
// 添加ints, Polygons和Lines数组至Records
addArrays();
// 使用数组显示内容
showRecordArrays();
engine.close();
sample_pause_end("\n\nPress any key to continue . . . ");
return 0;
}
void insertRecords()
{
…
for (int i = 1; i <= nRecords; i++)
{
_Record r;
char buf[256];
sprintf(buf, "Record %d", i);
r.s = buf;
r.i2 = i * 10;
r.p.x = i + 1;
r.p.y = i + 2;
r.i1 = i % 10;
r.i8 = i * 100;
r.f = (float)i / 10;
r.d = (double)i / 100;
r.l.begin.x = i + 2;
r.l.begin.y = i + 2;
r.l.end.x = i + 3;
r.l.end.y = i + 4;
r.i4 = i;
r.ai1 = NULL;
r.vp = NULL;
r.lines = NULL;
engine.executeStatement("insert into aRecord %r", &r);
}
}在上述代码片段insertRecords中需要注意的一些编程要点:
_Record对象的数组元素初始化为NULL。这些值在 addArrays() 函数中被赋予。- _Line 元素 l 的起始点和终点是通过简单赋值设置的标量整数值。
void addArrays()
{
int i,j, k;
...
for ( i = 1; i <= nRecords; i++ )
{
// 获取记录 i 以进行更新
QueryResult result(engine.executeQuery( "select * from aRecord where i4=%i for update", i ) );
Cursor* cursor = result->records();
Record* rec = cursor->next();
// 将字段值放入字段变量中
Field* vp = result->findField("vp");
Field* points = vp->element()->findComponent("points");
Field* x = points->element()->findComponent("x");
Field* y = points->element()->findComponent("y");
Field* lines = result->findField("lines");
Field* beg = lines->element()->findComponent("begin");
Field* end = lines->element()->findComponent("end");
// 将记录的当前内容获取到结构体 _Record 中
_Record r;
result->extract(rec, &r, sizeof(r));
// 设置字节数组的值
memset( byteArray, 0, nBytes );
for ( j=0; j < nBytes; j++ )
byteArray[j] = ( i * nBytes) + j;
r.ai1->setBody( byteArray, 0, nBytes );
// 设置nPolygons
r.vp->setSize( nPolygons );
for ( j = 0; j < nPolygons; j++ )
{
Struct* poly = (Struct*)r.vp->updateAt( j );
Array* pa = (Array*)points->update( poly );
pa->setSize(nPoints);
for ( k = 0; k < nPoints; k++)
{
Struct* point = (Struct*)pa->updateAt(k);
x->set( point, new IntValue( (i * 100) + j + k + 1 ) );
y->set( point, new IntValue( (i * 100) - j - k - 1 ) );
}
}
// 设置 lines
for ( j = 0; j < nLines; j++ )
{
Struct* line = (Struct*)r.lines->updateAt(j);
Struct* bp = (Struct*)beg->update(line);
Struct* ep = (Struct*)end->update(line);
x->set( bp, new IntValue( (i * 100) + j + 1 ) );
y->set( bp, new IntValue( (i * 100) - j - 1 ) );
x->set( ep, new IntValue( (i * 100) + j + 3 ) );
y->set( ep, new IntValue( (i * 100) - j - 3 ) );
}
// 更新数据库记录
rec->updateRecord();
}
}
}在上述代码片段 addArray 中需要注意的一些编程要点:
- McoSql 类的 Record(变量
rec)用于定位游标中的每条记录,然后初始化其数组字段。 - McoSql 类的 Field 用于获取结构字段
vp、aPolygon.points和lines以及标量字段x、y、begin和end的指针。然后使用这些指针来初始化数组和结构值。 - 通过调用 Array 类的
setBody()方法来设置 Arrayail(数据库模式中的字节数组)。 - McoSql 类的 Struct(变量
poly)初始化为指向向量vp中的下一个 aPolygon 元素,然后 Array 变量pa初始化为指向 aPolygon 结构中的 aPoints 数组。 - 调用 Array 方法
setSize()来确定 aPoints 数组的大小。 - 再次使用 Struct 类(变量
point)指向 Array 中的每个aPoint结构。 - 使用 Field 变量
x和y通过调用set()方法并使用IntValue构造函数实例化值参数来设置aPoint值。 - 以类似的方式使用 Struct 和 Field 类设置
aRecord的lines数组字段。
void showRecords()
{
printf("\n\tRecord table contents:" );
for ( int i=1; i <= nRecords; i++ )
{
_Record r;
QueryResult result( engine.executeQuery( "select * from aRecord where i4=%i", i ) );
Cursor* cursor = result->records();
if ( cursor->hasNext() )
{
Record* rec = cursor->next();
result->extract( rec, &r, sizeof(r) );
printf( "\n\t%d) %s: i2=%d, i4=%d, x=%d, y=%d, f=%f, d=%f, \n\t\t"
"line(x1,y1,x2,y2)=%d,%d,%d,%d",
i, r.s, r.i2, r.i4, r.p.x, r.p.y, r.f, r.d,
r.l.begin.x, r.l.begin.y, r.l.end.x, r.l.end.y );
}
}
}
void showRecordArrays()
{
int i,j,k;
printf("\n\n\tRecord Array contents:" );
for ( i=1; i <= nRecords; i++ )
{
// 获取记录 i
QueryResult result(engine.executeQuery( "select * from aRecord where i4=%i", i ) );
Cursor* cursor = result->records();
Record* rec = cursor->next();
_Record r;
result->extract(rec, &r, sizeof(r));
printf( "\n\n\t%d) %s: ", i, r.s );
// 显示字节数组
printf( "\n\t\tArray of %d 'i1' values: ", nBytes );
r.ai1->getBody( byteArray, 0, nBytes );
for ( j=0; j < nBytes; j++)
{
printf( "%d%s", byteArray[j], ( j == nBytes-1 ? "" : ", " ) );
}
// 显示多边形顶点
printf( "\n\t\tPoints for %d Polygons : ", nPolygons );
for ( j=0; j < nPolygons; j++ )
{
Struct* poly = (Struct*)r.vp->getAt(j);
Array* points = (Array*)poly->get(0);
printf( "\n\t\t\t Polygon %d:", j+1 );
for ( k=0; k < nPoints; k++ )
{
Struct* point = (Struct*)points->getAt(k);
printf( "\n\t\t\t\t%d) x=%d, y=%d ", k+1, (int)point->get(0)->intValue(), (int)point->get(1)->intValue() );
}
}
// 显示线条端点
printf( "\n\t\tEnd points for %d Lines : ", nLines );
for ( j=0; j < nLines; j++ )
{
Struct* line = (Struct*)r.lines->getAt(j);
Struct* beg = (Struct*)line->get(0);
Struct* end = (Struct*)line->get(1);
printf( "\n\t\t\t\t%d) begin=(%d,%d) end=(%d,%d)", j+1, (int)beg->get(0)->intValue(), (int)beg->get(1)->intValue(),
(int)end->get(0)->intValue(), (int)end->get(1)->intValue() );
}
}
}在上述代码片段“showRecord”和“showRecordArrays”中需要注意的一些编程要点:
- 对于 aRecord 对象的元素点“
p”,其坐标可以直接从 aRecord 结构中通过r.p.x和r.p.y来访问。 - 向量元素
vp内的 aPolygon 结构中的点的坐标必须通过 Struct 和 Array 类来访问;首先通过方法getAt()将 Struct 对象poly初始化为vp的元素j,然后通过 Struct 方法get(0)将数组points设置为aPolygons.points,接着将每个point初始化为指向数组points的元素k,最后通过 Struct 方法get(0)->intValue()提取并转换为整数类型point.x的值,以及get(1)->intValue()提取point.y的值。 - 同样地,
lines数组的坐标begin和end通过 Struct 方法get(n)->IntValue()来访问并转换为整数类型。
// 从 aRecord 中选择一个结构体元素和字符串字段
void showStruct( int x )
{
printf("\n\n\tExtract p.y and s fields from aRecord with p.x=%d:", x );
QueryResult result(engine.executeQuery( "select p.y, s from aRecord where p.x=%i", x ) );
Cursor* cursor = result->records();
Record* rec = cursor->next();
// 使用 Struct::get(int index) 方法通过索引(从 0 开始)引用结果列
int2 y = (int2)rec->get(0)->intValue();
printf( "\n\t\tUsing Struct::get(int index): y=%d, s='%s'", y, rec->get(1)->pointer() );
// 使用字段描述符
printf( "\n\t\tUsing QueryResult::Fields iterator:" );
int iField = 0;
Iterator <Field> * fields = result->fields();
Field * f;
while ( f = fields->next() )
{
switch ( iField )
{
case 0:
printf( "\n\t\t\tField %s=%d", f->name()->cstr(), (int)f->get(rec)->intValue() );
break;
case 1:
printf( "\n\t\t\tField %s='%s'", f->name()->cstr(), f->get(rec)->pointer() );
}
iField++;
}
// 使用 DataSource.extract() 方法。注意:所使用的结构必须与 select 语句中指定的字段数量相同。
struct
{
int y;
char * s;
} row;
result->extract( rec, &row, sizeof( row ) );
printf( "\n\t\tUsing DataSource::extract(): y=%d, s='%s'\n", row.y, row.s );
}在上述代码片段“showStruct()”中需要注意的一些编程要点:
select语句中的条件指定了 "where p.x=%i",用于根据嵌入式 Point 结构体中某个元素的值进行筛选。有三种不同的方法用于提取结果集的值。特别要注意
struct元素p.y的值是如何管理的:- 使用
Struct::get(int index)方法通过索引引用结果行中的列; - 使用
QueryResult::Fields迭代器来遍历结果行中的列; - 使用
DataSource::extract()方法提取一个与结果行中的列数完全对应的struct。
- 使用
数组及用法
SmartESQL 可以接受 SmartEDB 数组(固定长度)和向量(动态长度)作为记录的组成部分。与 SmartEDB 一样,多维数组不受支持。SmartESQL 提供了一组专门的构造来处理数组和向量(以下统称为“数组”):
- 可以通过使用
length()函数获取数组中的元素数量。 - 可以使用
[]运算符获取数组元素。如果索引表达式超出数组范围,则会引发异常。 - 可以使用
in运算符检查数组是否包含由左操作数指定的值。此操作仅适用于原子类型的数组;即布尔型、数值型、引用型或字符串型的数组。 - 通过
exists运算符可以遍历数组元素。在exists关键字之后指定的变量可以在由exists量词引导的表达式中用作数组的索引。此索引变量将遍历所有可能的数组索引值,直到表达式的值为真或索引超出数组范围。
例如,给定以下数据库模式:
class Company
{
string location;
autoid[1000];
};
class Contract
{
autoid_t<Company> company;
unsigned<8> quantity;
date delivery;
autoid[1000];
};
class Detail
{
string name;
vector<autoid_t> contract;
};查询语句如下可用于查询所有在美国公司的Detail记录:
select * from Detail where exists i: (contract[i].company.location = 'US');若选择所有美国以外公司的详细记录,查询语句如下:
not exists i: (contract[i].company.location = 'US')阵列与向量
SmartESQL 支持所有 SmartEDB 数据类型,包括向量(动态数组)类型。数组通过以下方式创建:
McoSql::Array *arr = McoSql::Array::create(allocator, McoSql::tpInt8, 0, nElem);固定大小的项目可以通过指向内部缓冲区的指针进行分配,如下所示
McoSql::Array * arr = McoSql::Array::create(allocator,
McoSql::tpInt8, 0, nElem);
int64_t * carr = (int64_t *)arr->pointer();
for (Py_ssize_t j = 0; j < nelem; j++)
{
int64_t v = PyInt_AsLong(pitem);
carr[j] = v;
}或者,为单个元素赋值的更安全的方法如下:
int64_t val = 0;
arr->setAt(j, &val);SmartEDB 还支持可为空的数组。通过调用 Array 方法 makeNullable() 可以创建可为空的数组。例如:
McoSql::Array *someArray;
...
McoSql::Array *nullableArray = someArray->makeNullable();新创建的 nullableArray 是一个 NullableArray 类的实例,其内容为原始数组的内容以及对应的 nullBitmap。nullBitmap 是一个 McoSql::Array 类型的实例,其元素类型为 uint64_t,其中每个设置的位都表示数组中对应位置的元素为 null。可以使用 getNullBitmap() 方法获取此位图。例如:
McoSql::Array *nullBitmap = nullableArray->getNullBitmap();请注意,应用程序无需直接检查或修改此 nullBitmap。可以通过将指向 McoSql::Null 对象的指针传递给 setAt() 方法来设置一个空元素。例如:
nullableArray->setAt(0, &McoSql::Null);方法 getAt() 和 getCopyAt() 将为 NullableArray 中的空值项返回指向 McoSql::Null 对象的指针。
字符串
所有适用于“数组”的操作也适用于“字符串”,字符串也有其自身的一套操作。例如,可以使用标准的关系运算符对字符串进行相互比较。
like 这种结构可用于将字符串与包含特殊通配符字符“%”和“_”的模式进行匹配。字符“_”匹配任何单个字符,而字符“%”匹配任意数量的字符(包括 0 个)。带有转义部分的 like 运算符的扩展形式仅在特殊转义字符出现在模式中的“%”和“_”之前时,才能将它们作为普通字符处理,该转义字符在 escape 关键字之后指定。
例如,
select * from T where name like '#%x%' escape '#'将选择所有名称字段以“%x”开头的记录。
可以使用 in 运算符在字符串中搜索子字符串。因此,表达式 'blue' in color 对于所有颜色字段包含字符串 'blue' 的记录都为真。例如,
select * from car where 'blue' in color;将选择颜色为“深蓝色”、“蓝色”、“白蓝相间”、“浅蓝色”……的汽车对象。
字符串可以通过使用 + 或 || 运算符进行连接(+ 和 || 可以互换使用)。SmartESQL 在表达式中不支持隐式转换为 string 类型,因此已重新定义了字符串的 + 运算符的语义。换句话说,在许多 SQL 实现中,可以这样写:
1+'1'结果将是 2(这里从字符串到整数进行了隐式转换)。结果为
1||'1'将是“11”。
SmartESQL 不允许字符串的隐式转换,因此 1 + '1' 的结果将是 '11',与 1 || '1' 的结果相同。
应当注意,从基类 Value 继承的每个类都实现了一个名为 String *stringValue 的方法。在该方法的所有实现中(String 类除外),都会创建一个类型为 *String 的新对象,并且在使用完毕后应当将其删除。而 String 类的方法则返回 this。因此,如果应用程序删除了字符串对象,然后又删除了当前对象所继承自的对象,那么该对象就会被删除两次!而且一旦删除,也无法再使用父对象了。
因此,为了避免混淆,建议不要使用 String *stringValue 方法将 Value 转换为 string,而应使用 Ref<String> stringRef(Allocator* allocator) 方法。此方法会监控对象的生命周期,并在适当的时候将其移除。使用此方法有两种方式:
Ref<String> strval(v->stringRef(allocator));或
Ref<String> str = v->stringRef(allocator);引用的作用域与使用如下:
{
Ref<String> strval(v->stringRef(allocator));
/* 将引用作为本地对象字符串使用*/
printf("%s", strval.cstr());
} /* Ref在此处会自动删除。 */或
{
Ref<String> str = v->stringRef(allocator);
/* 将引用作为指向字符串对象的指针 */
printf("%s", str->cstr());
} /* Ref is automatically deleted here */字符串常量
StringLiteral 构造函数有一个大小为 1 的“chars”成员变量。这说明了具有可变大小的结构的标准分配方式。当分配此结构时,必须将对象的固定部分和可变部分的大小相加。在 C++ 中,可以使用带有额外参数的 new 运算符来实现。然而,这样的运算符并未预先定义,您需要自行定义(就像在 DynamicObject 类中所做的那样):
inline void* operator new(size_t fixed, size_t var)
{
return ::new char[fixed + var];
}现在可以按如下方式创建字符串常量:
char const* str = "Hello World";
size_t length = strlen(str);
Value* value = ::new (length) StringLiteral(str, length);有两种选择:
使用 StringRef 类,其构造函数接受指向字符串的指针,并且当字符串不再需要时,由程序员负责释放该字符串。
定义一个继承自 String 类的自定义类:
class UserString : public String
{
public:
virtual int size();
virtual char* body();
virtual char* cstr();
private:
const int length;
char* chars;
public:
UserString(char const* s, int l);
~UserString();
};
int UserString::size()
{
return length;
}
UserString::UserString(char const* s, int l) : length(l)
{
chars = new char[l+1];
memcpy(chars, s, l);
chars[l] = '\0';
}
UserString::~UserString()
{
delete[] chars;
}
char* UserString::body()
{
return chars;
}
char* UserString::cstr()
{
return chars;
}二元数据
可能包含零值的 二进制 和 可变二进制 数据可以存储在 SmartESQL 的 byte 数组或类型为 binary 或 varbinary 的字段中。要将包含嵌入零值的二进制数据插入到类型为 byte 数组的字段中或从该字段中检索数据,请使用 Array 的 putBody() 或 getBody() 方法。例如,在模式定义中:
class t
{
signed<1> b[16];
list;
};字段“b”是一个字节数组,在应用程序代码中将作为数组进行访问 - 使用 Array::getBody() 方法获取数组的内容。
如果需要在二进制数据上建立索引,则该字段必须为 binary 或 varbinary 类型。例如,在模式定义中:
class bin_array
{
unsigned<4> idx;
binary<16> bin_array;
userdef tree<bin_array> binkey;
kdtree <idx, bin_array> binkey_kd;
patricia<bin_array> binkey_p;
};
class bin_vector
{
unsigned<4> idx;
varbinary bin_vector;
userdef tree<idx, bin_vector> binkey;
kdtree <idx, bin_vector> binkey_kd;
patricia<bin_vector> binkey_p;
};C++ 类 Binary 用于访问二进制字段。以下代码片段演示了如何实现二进制数据访问:
struct _bin_cls
{
int idx;
Value *bin;
}
...
_bin_cls bin_cls;
// 将新数据放入数据库
char buf_in[4096];
...
// 假设 buf_in 包含重要数据
// 创建二元对象并复制
Binary* b = Binary::create(engine.getAllocator(), sizeof(buf_in));
memcpy(b->body(), buf_in, sizeof(buf_in));
bin_cls.idx = 1;
bin_cls.bin = (Value *)b;
engine.executeStatement("insert into bin_cls %r", _bin_cls);
DELETE_OBJ(engine.getAllocator(), b);
...
// 获取查询结果
QueryResult result( engine.executeQuery( "select * from bin_cls" ) );
Cursor* cursor = result->records();
Record* rec = cursor->next();
McoSql::ValueRef eref(rec->get(1));
McoSql::Binary *bin = eref.as<McoSql::Binary>(); // will be deleted automatically by resultSet's allocator
size_t len = bin->length;
char buf_out[len];
memcpy(buf_out, bin->body(), len);或者,占位符 %v 可用于将 binary 或 varbinary 值替换到 SQL 字符串中:
// 将新数据放入数据库
char buf_in[4096];
// 假设 buf_in 包含重要数据
// 创建二元对象并复制
Binary* b = Binary::create(engine.getAllocator(), sizeof(buf_in));
memcpy(b->body(), buf_in, sizeof(buf_in));
engine.executeStatement("insert into bin_cls values (%i, %v)", 1, b);
DELETE_OBJ(engine.getAllocator(), b);从 SQL 的角度来看,有两种数据类型:binary(n) 映射到存储类型 binary<n>,以及 varbinary 映射到存储类型 varbinary。(从 SQL C++ API 类 Value 的角度来看,这两种类型在内部都映射到 tpBinary 类型)。二进制字段的数据可以在 SQL 中以十六进制格式的字符串形式传递。例如:
create table t (idx int, bin binary(4));
insert into t values(1, '0A0D0A0D' );还可以通过 SQL 创建 binary 和 varbinary 类型的数组和向量。例如:
create table t (idx int, bin array(binary(4), 10)); // 二维数组
create table t (idx int, bin array(varbinary)); // 二进制变长字符向量Dynamic objects
现在,new 运算符已重载为使用静态的 StdAllocator,并且所有动态对象都必须将分配器作为参数传递给 new 运算符。而在 SmartESQL 6.5 之前的版本中,动态对象是通过如下代码创建的:
int val = 1;
...
return new IntValue(val);现在,动态对象必须在如下代码中使用 StdAllocator:
Allocator * allocator = engine.getAllocator();
int val = 1;
...
return new (allocator) IntValue(val);或者在代码中像下面这样使用静态的 create() 方法:
Allocator * allocator = engine.getAllocator();
int val = 1;
...
return IntValue::create(allocator,val);请注意,在任何需要分配器的 API 调用(如 new)中,可以从 McoSqlEngine 获取分配器的指针,如上述示例代码所示,或者从任何容器子类(如 Record、DataSource、Array、Blob)获取。使用 McoSqlEngine 分配器分配的任何对象都不会被隐式删除;也就是说,除非显式删除,否则它们将一直保留在内存中,直到引擎本身被销毁。但是,如果在查询内部操作,则可以从 QueryResult 对象获取分配器;并且此分配器以及使用它分配的所有对象将在查询完成时被销毁。
若要显式删除使用 new 运算符创建的动态对象,请使用在公共头文件 basedef.h 中提供的 DELETE 或 DESTROY 宏。
使用引用
在 SmartESQL 中,通常通过使用 references 来实现表之间的 SQL“外键”关系,从而通过 AUTOID 快速直接地访问记录。引用字段也可以被索引,并在 ORDER BY 子句中使用。引用可以通过与访问结构组件相同的点符号进行解引用。
例如,以下数据库模式:
class Address
{
string city;
autoid[1000];
hash<city> city_index[1000];
};
class Company
{
autoid_t<Address> address;
autoid[10000];
hash<address> address_index[10000];
};
class Contract
{
autoid_t<Company> company;
autoid[10000];
hash<company> company_index[10000];
};查询语句:
select * from Contract where company.address.city = 'Chicago';将检索出所引用的Company引用的Address的city字段等于“Chicago”的“合同”记录。此请求的查询执行计划如下:
- 使用
city_index (city = 'Chicago')对 Address 表执行索引搜索。 - 对于所有选定的Address记录,使用
address_index查找引用这些地址的Company记录。 - 对于所有选定的Company记录,使用
company_index查找引用这些Company记录的Contract记录。
可以通过 is null 或 is not null 这两个谓词来检查引用是否为 null 。它们还可以相互比较是否相等,以及与特殊的 null 关键字进行比较。当对 null 引用进行解引用时,SmartESQL 会引发异常。
以下示例模式图展示了可用于对“客户 - 订单”应用程序进行建模的三个表:
declare database referencesdb;
class Address
{
int4 zipcode;
string city;
string street;
hash<zipcode> iaddr[100000];
autoid[100000];
list;
};
class Company
{
autoid_t<Address> location;
string name;
hash<name> iname[100000];
hash<location> iaddr[100000];
autoid[100000];
list;
};
class Orders
{
autoid_t<Company> company;
date shipment;
uint8 amount;
string description;
hash<company> icomp[100000];
list;
};请注意,Company 表和 Orders 表之间存在一对多的关系,即多个 Order 类的实例可能具有相同的 autoid 字段company的值。此字段company是对一个 Company 对象的“引用”,从而实现最佳查找时间。同样,Company 表和 Address 表之间的关系通过引用字段location来实现。以下示例代码片段展示了如何在 SmartESQL 中使用这些引用字段:
const char * db_name = "joindb";
// 定义全局 SQL 引擎
using namespace McoSql;
McoSqlEngine engine;
// 定义与数据库表相对应的结构
struct _Address
{
int4 zipcode;
char const* city;
char const* street;
};
int main()
{
…
engine.open( db_name, referencesdb_get_dictionary(), DATABASE_SIZE, MEMORY_PAGE_SIZE);
// 插入记录
insertRecords();
// 按邮政编码搜索地址
searchAddresses();
// 显示Company-Addresses
showJoin1();
// 显示Company-Orders
showJoin2();
// 显示公司订单,其中描述字段包含“1”
showJoin3();
// 释放
deleteRecords();
engine.close();
sample_pause_end("\n\nPress any key to continue . . . ");
return 0;
}
void showJoin1()
{
printf("\n\n\tSELECT C.name FROM Address A,Company C ...");
for (int i = 1; i <= nRecords; i++)
{
QueryResult result( engine.executeQuery( "SELECT C.name FROM Address A,Company C "
"WHERE A.zipcode=%i AND
A.autoid=C.location", i ) );
Cursor* iterator = result->records();
assert(iterator->hasNext());
Record* rec = iterator->next();
String * pName = (String*)rec->get(0);
printf( "\n\t\t%d) %s", i, pName->body() );
}
}
void showJoin2()
{
printf("\n\n\tSELECT C.name, O.description FROM Address A,Company C,Orders O ...");
for (int i = 1; i <= nRecords; i++)
{
QueryResult result( engine.executeQuery( "SELECT C.name, O.description "
"FROM Address A,Company C,Orders O "
"WHERE A.zipcode=%i AND A.autoid=C.location"
" AND C.autoid=O.company", i ) );
Cursor* iterator = result->records();
assert(iterator->hasNext());
Record* rec = iterator->next();
String * pName = (String*)rec->get(0);
String * pDesc = (String*)rec->get(1);
printf( "\n\t\t%d) %s: %s", i, pName->body(), pDesc->body() );
}
}
void showJoin3()
{
printf("\n\n\tSELECT C.name, O.description ... WHERE O.description like '%%1%%' ...");
for (int i = 1; i <= nRecords; i++)
{
QueryResult result( engine.executeQuery( "SELECT C.name, O.description "
"FROM Address A,Company C,Orders O "
"WHERE O.description like '%1%' AND "
"A.zipcode=%i AND A.autoid=C.location "
"AND C.autoid=O.company", i ) );
Cursor* iterator = result->records();
while ( iterator->hasNext() )
{
Record* rec = iterator->next();
String * pName = (String*)rec->get(0);
String * pDesc = (String*)rec->get(1);
printf( "\n\t\t%d) %s: %s", i, pName->body(), pDesc->body() );
}
}
}在上述代码片段 showJoin1、showJoin2 和 showJoin3 中需要注意的一些编程要点:
- 通过调用方法
get(n),McoSQL 类中的 String 用于访问Company.Name和Order.Description字段。 - 通过调用
String方法 "body()" 来打印出string值。
在向量或数组中搜索
可以使用 SQL 的 IN 运算符在向量或数组中进行搜索,例如:
select * from T where ? in T.vector;上述语句适用于 标量 类型的向量,但对于复合类型的向量,则必须使用 SmartEDB 特定的表达式。例如,使用以下模式定义:
struct E
{
uint4 q1;
uint4 q2;
vector<D> q3;
};
class R
{
uint4 r1;
E r2;
tree <r2.q1> by_r2e1;
};一个恰当的 select 语句应该如下所示:
select * from R where exists i: (exists j: (r2.q3[i].d3[j].c1=1));请注意,这样的查询需要对所有可能的索引值进行顺序搜索和二次迭代,因此速度会相当慢(所以不建议这样做)。
