Общий процесс , используемый 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"} |
Чтобы на самом деле построить оптимизатор на основе затрат, вам понадобится возможность отслеживать такие вещи, как размеры выборок таблиц / индексов, гистограммы и т. Д., Но вы можете представить себе написание отдельных реализаций алгоритмов в большой библиотеке.