Общий процесс , используемый Oracle выглядит следующим образом :
Query -> Анализировать Tree -> Создать набор планов -> Подсчет очков каждый план -> Выберите план -> Исполнение -> Выход
Альтернативный путь заключается в создании плана выполнения с использованием эвристики — более простой подход и очевидный выбор для многих приложений.
Примерный план выполнения может быть довольно сложным:
План выполнения строк
——– ————————————————-
12 СОРТИРОВКА АГРЕГАТА
2 СОРТИРОВАТЬ ГРУППУ ПО
76563 ВСТРОЕННЫХ
ПЕТЛЕЙ 76575 ВСТРОЕННЫХ ПЕТЛЕЙ
19 ТАБЛИЦЫ ДОСТУПА ПОЛНЫМ
CN_PAYRUNS_ALL 76570 ТАБЛИЦА ДОСТУПА К ИНДЕКСУ ROWID
7_65__POST INDEX RANGE SCAN (идентификатор объекта 178321)
76563 Доступ к таблице по индексу
ROWID CN_PAYMENT_WORKSHEETS_ALL 11432983 INDEX RANGE SCAN (идентификатор объекта 186024)
Это эквивалентно некоторому оригинальному запросу, и потенциально существует много других эквивалентных планов. Мне бы хотелось, чтобы API / библиотека (например, фреймы данных R / pandas) структурировали запросы таким образом — тогда можно было бы построить реализацию SQL, которая создала эти планы, или создать новое поколение языков, эквивалентных SQL.
Давайте попробуем проверить концепцию с помощью простых объектов Javascript. Мы начнем с умопомрачительного способа определения таблиц, чтобы проиллюстрировать, как это может работать:
schema = {
tables: {
people: [
{
first_name: 'gary',
last_name: 'sieling',
company: 1
},
{
first_name: 'ella',
last_name: 'sieling',
company: 2
}
],
companies: [
{
id: 1,
company_name: 'acme corp'
},
{
id: 2,
company_name: 'bubble'
}
]
},
indexes: {
company_pk: {
data: {
a: {
rowid: 2
},
b: {
rowid: 1
}
}
}
}
}
|
В идеале план выполнения может выглядеть примерно так: каждая функция возвращает Promise (или аналогичный), чтобы результаты могли передаваться обратно:
sort(
nested_loop_join(
nested_loop_join(
s({
select: ['rowid', 'first_name', 'last_name', 'company'],
from: schema.table1,
where: function(row) { return true }
}),
s({
select: ['rowid'],
from: schema.indexes.company_pk
}),
['rowid', 'rowid'],
['a', 'b']
),
s({
select: true,
from: schema.table1
})
)
)
|
Давайте начнем с написания простой функции, которая может фактически извлекать строки из таблицы — это поддерживает фильтрацию. Он возвращает замыкание, которое вы вызываете каждый раз, когда вам нужен еще один ряд.
function s(x) {
var i = 0;
var where = function() { return true; }
if (x.where) where = x.where;
return function() {
var next = x.from[i++]
if (!next) {
return next;
}
if (where(next)) {
if (x.select) {
var res = {};
$.each(x.select, function(i, col) {
res[col] = next[col];
});
return res;
} else {
return next;
}
}
}
}
results = s({from:schema.tables.people})
results()
Object {first_name: "gary", last_name: "sieling", company: 1}
results()
Object {first_name: "ella", last_name: "sieling", company: 2}
results()
undefined
results = s({from:schema.tables.people, select: ['first_name']})
results()
Object {first_name: "gary"}
results()
Object {first_name: "ella"}
results()
results = s({from:schema.tables.people,
select: ['first_name'], where: function(row) { return row.first_name = 'gary' } })
results()
Object {first_name: "gary"}
results()
|
Теперь, чтобы сделать что-то более интересное, давайте попробуем реализовать соединение. Это объединение вложенных циклов в приведенном выше плане Oracle — всего два цикла for с предложением where в объединении.
function nested_loop_join(a, b, keys, select) {
output = []
second = [];
while(row_b = b()) {
second[second.length] = row_b;
}
while(row_a = a()) {
$.each(second, function(i, row_b) {
if (row_a[keys[0]] === row_b[keys[1]]) {
var new_row = {};
$.each(select, function(k, col) {
// cheat here for simplicity - should handle aliasing
new_row[col] =
(row_a[col] ? row_a[col] : row_b[col])
})
output[output.length] = new_row;
}
})
}
return s({from: output})
}
joined = nested_loop_join(
s({from:schema.tables.companies}),
s({from:schema.tables.people}),
["id", "company"],
["last_name", "company_name"])
joined()
Object {last_name: "sieling", company_name: "acme corp"}
joined()
Object {last_name: "sieling", company_name: "bubble"}
|
До этого момента я избегал реализации индексов из-за сложности — давайте сделаем соединение другого типа, декартово соединение. Это берет все строки первой и второй таблиц независимо от их соответствия, затем применяет фильтр в конце:
function cartesian_join(a, b, keys, select) {
output = []
second = [];
while(row_b = b()) {
second[second.length] = row_b;
}
var cols = select;
if (cols) {
$.each(keys, function(i, c) {
if ($.inArray(c, select) === -1) {
cols[cols.length] = c;
}
});
}
while(row_a = a()) {
$.each(second, function(i, row_b) {
var new_row = {};
$.each(cols, function(k, col) {
// cheat here for simplicity - should handle aliasing
new_row[col] =
(row_a[col] ? row_a[col] : row_b[col])
});
output[output.length] = new_row;
})
}
return s({from: output, where: function(row) {
return row[keys[0]] === row[keys[1]];
}});
}
joined()
Object {last_name: "sieling", company_name: "acme corp", id: 1, company: 1}
joined()
undefined
joined()
undefined
joined()
Object {last_name: "sieling", company_name: "bubble", id: 2, company: 2}
|
Теперь давайте напишем что-нибудь более в стиле SQL — мы не хотим указывать реализацию.
from( schema.tables.company, schema.tables.person ["company", "id"] ["last_name", "company"] ) |
Эта функция from может взять две таблицы и использовать эвристику, как старый оптимизатор на основе строк, для фильтрации результатов:
function from(table1, table2, keys, cols) {
if (table1.length <= 5 && table2.length <= 5) {
return cartesian_join(s({from:table1}), s({from:table2}), keys, cols)
}
else {
return nested_loop_join(s({from:table1}), s({from:table2}), keys, cols)
}
}
joined = nested_loop_join(
s({from:schema.tables.companies}),
s({from:schema.tables.people}),
["id", "company"],
["last_name", "company_name"])
joined()
Object {last_name: "sieling", company_name: "acme corp"}
joined()
Object {last_name: "sieling", company_name: "bubble"}
|
Чтобы на самом деле построить оптимизатор на основе затрат, вам понадобится возможность отслеживать такие вещи, как размеры выборок таблиц / индексов, гистограммы и т. Д., Но вы можете представить себе написание отдельных реализаций алгоритмов в большой библиотеке.