2016-09-06 1 views
10

Мы сталкиваемся с проблемами качества кода из-за встроенных запросов mysql. Имея SELF написанный MySQL запросов действительно загромождает код, а также увеличивает базовый код и т.д.Где хранить команды SQL для выполнения

Наш код завален вещи, как

/* beautify ignore:start */ 
/* jshint ignore:start */ 
var sql = "SELECT *" 
+" ,DATE_ADD(sc.created_at,INTERVAL 14 DAY) AS duedate" 
+" ,distance_mail(?,?,lat,lon) as distance,count(pks.skill_id) c1" 
+" ,count(ps.profile_id) c2" 
+" FROM TABLE sc" 
+" JOIN " 
+" PACKAGE_V psc on sc.id = psc.s_id " 
+" JOIN " 
+" PACKAGE_SKILL pks on pks.package_id = psc.package_id " 
+" LEFT JOIN PROFILE_SKILL ps on ps.skill_id = pks.skill_id and   ps.profile_id = ?" 
+" WHERE sc.type in " 
+" ('a'," 
+" 'b'," 
+" 'c' ," 
+" 'd'," 
+" 'e'," 
+" 'f'," 
+" 'g'," 
+" 'h')" 
+" AND sc.status = 'open'" 
+" AND sc.crowd_type = ?" 
+" AND sc.created_at < DATE_SUB(NOW(),INTERVAL 10 MINUTE) " 
+" AND sc.created_at > DATE_SUB(NOW(),INTERVAL 14 DAY)" 
+" AND distance_mail(?, ?,lat,lon) < 500" 
+" GROUP BY sc.id" 
+" HAVING c1 = c2 " 
+" ORDER BY distance;"; 
/* jshint ignore:end */ 
/* beautify ignore:end */ 

мне пришлось размойте код немного.

Как вы можете видеть, повторение этого кода в коде просто не читается. Также из-за того, что atm мы не можем перейти на ES6, что, по крайней мере, довольно немного, благодаря многострочным строкам.

Вопрос в том, есть способ сохранить эти процедуры SQL в одном месте? В качестве дополнительной информации мы используем node (~ 0.12) и express, чтобы открыть API, получив доступ к MySQL db.

Я уже думал, используя JSON, что приведет к еще большему беспорядку. Кроме того, это может быть даже невозможно, поскольку кодировка JSON немного строгая, и JSON, вероятно, не понравится иметь многострочные строки.

Затем я придумал идею хранения SQL в файле и загрузки при запуске приложения-узла. Это на данный момент мой лучший шанс получить SQL-запросы в ОДНОМ месте и предложить их остальным модулям узлов. Вопрос здесь, используйте ОДИН файл? Использовать один файл для каждого запроса? Использовать один файл в таблице базы данных?

Любая помощь приветствуется, я не могу быть первой на планете, решая это, поэтому, возможно, у кого-то есть рабочее, приятное решение!

PS: Я пытался использовать libs, как squel, но это действительно не помогает, так как наши запросы сложны, как вы можете видеть. Речь идет, главным образом, о том, чтобы НАШИ запросы были в «центральном запросе».

+0

Могу ли я узнать, почему вы не можете пойти на ES6, поскольку я вижу, что это не повлияет на ваш существующий код? Для этого вам просто нужно использовать транспилер. –

+0

Слишком много зависимостей на данный момент, по крайней мере, это то, что мне говорят: S Я также попытался перейти на узел 6.5, но приложение вылетает при запуске. На данный момент я пытаюсь получить SQL-команды в центральную. Если node6.x - возможный решатель проблем, я должен был бы предложить его и заставить его работать. – m4mbax

+0

Вы можете рассмотреть возможность введения представлений в базу данных, чтобы упростить некоторые из запросов SELECT (например, ваш пример). И, возможно, также хранимые процедуры для упрощения запросов, которые каким-то образом обновляют данные. Конечно, есть достоинства и недостатки этого подхода - необходимо сохранить взгляды/хранимые процедуры, и иногда существует опасность их добавления к безвестности (например, представления, которые включают другие виды), возможно, придется немного развернуться, чтобы выяснить, что фактические таблицы). Но это может быть полезно, например. когда одни и те же соединения или критерии WHERE используются снова и снова. –

ответ

11

Я предпочитаю ставить каждый более крупный запрос в одном файле. Таким образом, вы можете выделить синтаксис, и его легко загрузить при запуске сервера. Чтобы структурировать это, я обычно имею одну папку для всех запросов и внутри этой папки для каждой модели.

# queries/mymodel/select.mymodel.sql 
SELECT * FROM mymodel; 

// in mymodel.js 
const fs = require('fs'); 
const queries = { 
    select: fs.readFileSync(__dirname + '/queries/mymodel/select.mymodel.sql', 'utf8') 
}; 
+0

Это исполнитель? Он читает все запросы при запуске, имея его в памяти? Или он читается при обращении к запросам. Выбирается? – m4mbax

+1

он должен быть довольно результативным, так как он только один раз читает запрос (на сервере). Вставить данные, которые я обычно размещаю? а также ?? в запросе, как это понимает модуль mysql. Так что да, он хранит их в памяти –

+0

Поскольку все остальные ответы более или менее похожи на один и тот же подход, но разные реализации, я принял этот ответ. Я действительно разработаю свой собственный модуль, как только мы выпустим нашу текущую версию. Возможно, я надеюсь, что я внес свой вклад в GH, если он будет работать, как вы, ребята, заинтригованы :) Спасибо всем, кто отвечает за ваши ответы, действительно оценил это. Если я однажды сделаю это в модуле, я вернусь и отвечу на свой вопрос с помощью моего модуля. Дайте мне знать, если кто-то хотел бы внести свой вклад! – m4mbax

5

Поместите свой запрос в процедуру базы данных и процедуру вызова в коде, когда это необходимо.

create procedure sp_query() 
select * from table1; 
+0

Я согласен с Pavel – Robin

2

Можете ли вы создать представление, в котором этот запрос.

Затем выберите из вида

Я не вижу каких-либо параметров в запросе, так что я полагаю, создание просмотр возможен.

2

Создайте процедуры хранения для всех запросов и замените var sql = "SELECT..." для вызова процедур, таких как var sql = "CALL usp_get_packages".

Это лучшее для производительности и отсутствие перерывов в зависимости от приложения. В зависимости от количества запросов может быть огромная задача, но для каждого аспекта (ремонтопригодность, производительность, зависимости и т. Д.) - лучшее решение.

3

Я прихожу из разных платформ, поэтому я не уверен, что это то, что вы ищете. как и ваше приложение, у нас было много шаблонных запросов, и нам не нравится, когда он жестко закодирован в приложении.

Мы создали таблицу в MySQL, позволяющую сохранить Template_Name (уникальный), Template_SQL.

Затем мы написали небольшую функцию в нашем приложении, которая возвращает шаблон SQL. что-то вроде этого:

SQL = fn_get_template_sql(Template_name); 

мы затем обработать SQL-то вроде этого: псевдо:

if SQL is not empty 
    SQL = replace all parameters// use escape mysql strings from your parameter 
    execute the SQL 

или вы можете прочитать SQL, создать соединение и добавить параметры с помощью безопасный способ.

Это позволяет редактировать запрос шаблона где и когда угодно. Вы можете создать таблицу аудита таблицы шаблонов, в которой будут записаны все предыдущие изменения, чтобы вернуться к предыдущему шаблону, если это необходимо. Вы можете расширить таблицу и зафиксировать, кто и когда был последним измененным SQL.

с точки зрения производительности, это будет работать «на лету», и вам не обязательно читать какие-либо файлы или перезагружать сервер, если вы зависите от процесса запуска сервера при добавлении новых шаблонов.

3

Вы можете создать совершенно новый модуль npm, предположим, что модуль пользовательских запросов и разместил там все ваши сложные запросы.

Затем вы можете классифицировать все свои запросы по ресурсам и действиям. Например, структура реж может быть:

/index.js -> it will bootstrap all the resources 
/queries 
/queries/sc (random name) 
/queries/psc (random name) 
/queries/complex (random name) 

Следующий запрос может жить под/запросов/комплексного каталога в отдельном файле, и файл будет иметь описательное имя (допустим, retrieveDistance)

// You can define some placeholders within this var because possibly you would like to be a bit configurable and reuseable in different parts of your code. 
/* jshint ignore:start */ 
var sql = "SELECT *" 
+" ,DATE_ADD(sc.created_at,INTERVAL 14 DAY) AS duedate" 
+" ,distance_mail(?,?,lat,lon) as distance,count(pks.skill_id) c1" 
+" ,count(ps.profile_id) c2" 
+" FROM TABLE sc" 
+" JOIN " 
+" PACKAGE_V psc on sc.id = psc.s_id " 
+" JOIN " 
+" PACKAGE_SKILL pks on pks.package_id = psc.package_id " 
+" LEFT JOIN PROFILE_SKILL ps on ps.skill_id = pks.skill_id and ps.profile_id = ?" 
+" WHERE sc.type in " 
+" ('a'," 
+" 'b'," 
+" 'c' ," 
+" 'd'," 
+" 'e'," 
+" 'f'," 
+" 'g'," 
+" 'h')" 
+" AND sc.status = 'open'" 
+" AND sc.crowd_type = ?" 
+" AND sc.created_at < DATE_SUB(NOW(),INTERVAL 10 MINUTE) " 
+" AND sc.created_at > DATE_SUB(NOW(),INTERVAL 14 DAY)" 
+" AND distance_mail(?, ?,lat,lon) < 500" 
+" GROUP BY sc.id" 
+" HAVING c1 = c2 " 
+" ORDER BY distance;"; 
/* jshint ignore:end */ 

module.exports = sql; 

Верхний уровень index.js будет экспортировать объект со всеми сложными запросами. Примером может быть:

var sc = require('./queries/sc'); 
var psc = require('./queries/psc'); 
var complex = require('./queries/complex'); 

// Quite important because you want to ensure that no one will touch the queries outside of 
// the scope of this module. Be careful, because the Object.freeze is freezing only the top 
// level elements of the object and it is not recursively freezing the nested objects. 
var queries = Object.freeze({ 
    sc: sc, 
    psc: psc, 
    complex: complex 
}); 

module.exports = queries; 

Наконец, на основной код, который вы можете использовать модуль так:

var cq = require('custom-queries'); 
var retrieveDistanceQuery = cq.complex.retrieveDistance; 
// @todo: replace the placeholders if they exist 

Doing что-то подобное, что вы будете двигаться все шум конкатенации в другое место что вы ожидаете, и вы сможете легко найти в одном месте все ваши сложные запросы.

3

Есть несколько вещей, которые вы хотите сделать. Во-первых, вы хотите хранить многострочные строки без ES6. Вы можете использовать функцию toString.

var getComment = function(fx) { 
 
     var str = fx.toString(); 
 
     return str.substring(str.indexOf('/*') + 2, str.indexOf('*/')); 
 
     }, 
 
     queryA = function() { 
 
     /* 
 
      select blah 
 
       from tableA 
 
      where whatever = condition 
 
     */ 
 
     } 
 

 
    console.log(getComment(queryA));

Теперь вы можете создать модуль и хранить множество этих функций. Например:

//Name it something like salesQry.js under the root directory of your node project. 
 
var getComment = function(fx) { 
 
    var str = fx.toString(); 
 
    return str.substring(str.indexOf('/*') + 2, str.indexOf('*/')); 
 
    }, 
 
    query = {}; 
 

 
query.template = getComment(function() { /*Put query here*/ }); 
 
query.b = getComment(function() { 
 
    /* 
 
    SELECT * 
 
    ,DATE_ADD(sc.created_at,INTERVAL 14 DAY) AS duedate 
 
    ,distance_mail(?,?,lat,lon) as distance,count(pks.skill_id) c1 
 
    ,count(ps.profile_id) c2 
 
    FROM TABLE sc 
 
    JOIN PACKAGE_V psc on sc.id = psc.s_id 
 
    JOIN PACKAGE_SKILL pks on pks.package_id = psc.package_id 
 
    LEFT JOIN PROFILE_SKILL ps on ps.skill_id = pks.skill_id AND ps.profile_id = ? 
 
    WHERE sc.type in ('a','b','c','d','e','f','g','h') 
 
    AND sc.status = 'open' 
 
    AND sc.crowd_type = ? 
 
    AND sc.created_at < DATE_SUB(NOW(),INTERVAL 10 MINUTE) 
 
    AND sc.created_at > DATE_SUB(NOW(),INTERVAL 14 DAY) 
 
    AND distance_mail(?, ?,lat,lon) < 500 
 
    GROUP BY sc.id 
 
    HAVING c1 = c2 
 
    ORDER BY distance; 
 
    */ 
 
}); 
 

 
//Debug 
 
console.log(query.template); 
 
console.log(query.b); 
 

 
//module.exports.query = query //Uncomment this.

Вы можете require необходимые пакеты и строить свою логику прямо в этом модуле или построить универсальный модуль оболочки для лучшего дизайна OO.

//Name it something like SQL.js. in the root directory of your node project. 
var mysql = require('mysql'), 
    connection = mysql.createConnection({ 
    host: 'localhost', 
    user: 'me', 
    password: 'secret', 
    database: 'my_db' 
    }); 

module.exports.load = function(moduleName) { 
    var SQL = require(moduleName); 
    return { 
    query: function(statement, param, callback) { 
     connection.connect(); 
     connection.query(SQL[statement], param, function(err, results) { 
     connection.end(); 
     callback(err, result); 
     }); 
    } 
    }); 

Чтобы использовать его, вы делаете что-то вроде:

var Sql = require ('./SQL.js').load('./SalesQry.js'); 

Sql.query('b', param, function (err, results) { 
    ... 
    }); 
3

Это, несомненно, миллион доллар вопрос, и я думаю, что правильное решение всегда зависит от конкретного случая.

Вот мои мысли. Надежда могла бы помочь:

Один простой трюк (который, на самом деле, я читал, что он на удивление более эффективен, чем объединение строк с «+») - использовать массивы строк для каждой строки и присоединяться к ним.

Он продолжает быть беспорядком, но, по крайней мере, для меня, более ясным (особенно при использовании, как и я, «\ n» в качестве разделителя вместо пробелов, чтобы сделать получаемые строки более читабельными при распечатке для отладки) ,

Пример:

var sql = [ 
    "select foo.bar", 
    "from baz", 
    "join foo on (", 
    " foo.bazId = baz.id", 
    ")", // I always leave the last comma to avoid errors on possible query grow. 
].join("\n"); // or .join(" ") if you prefer. 

В качестве подсказки, я использую этот синтаксис в моей SQL "building" library. Он может не работать в слишком сложных запросах, но если у вас есть случаи, когда параметры могут меняться, очень полезно избегать (также субоптимальных) «коалесцирующих» беспорядок, полностью удаляя ненужные части запроса. Он также находится на GitHub (и это не слишком сложный код), поэтому вы можете продлить его, если считаете его полезным.

Если вы предпочитаете отдельные файлы:

О, имеющих один или несколько файлов, имеющих несколько файлов является менее эффективным с точки зрения чтения эффективности (более файла открытия/закрытия накладных расходов и труднее кэширование на уровне ОС) , Но, если вы загружаете все их за один раз при запуске, это на самом деле едва заметная разница.

Таким образом, единственным недостатком (для меня) является то, что слишком сложно иметь «глобальный взгляд» на вашу коллекцию запросов. Даже если у вас очень много запросов, я думаю, что лучше сочетать оба подхода. То есть: связанные с группой запросы в одном файле, поэтому у вас есть один файл для каждого модуля, подмодель или любые выбранные вами критерии.

Конечно: отдельный файл приведет к относительно «огромному» файлу, также с которым трудно справиться «сначала». Но я (вряд ли) использую фальсификацию маркера vim (foldmethod=marker), которая очень полезна для обработки этих файлов.

Конечно: если вы еще не используете vim (поистине?), У вас не было бы этого варианта, но, конечно, в вашем редакторе есть еще одна альтернатива. Если нет, вы всегда можете использовать сложение синтаксиса и что-то вроде функции (my_tag) {"в качестве маркеров.

Например:

---(Query 1)---------------------/*{{{*/ 
select foo from bar; 
---------------------------------/*}}}*/ 

---(Query 2)---------------------/*{{{*/ 
select foo.baz 
from foo 
join bar using (foobar) 
---------------------------------/*}}}*/ 

... в сложенном виде, я вижу это как:

+-- 3 línies: ---(Query 1)------------------------------------------------ 

+-- 5 línies: ---(Query 2)------------------------------------------------ 

Который, используя правильно выбранные метки, является гораздо более удобным в управлении и, из анализирующая точка зрения, нетрудно разобрать все запросы на разделение файлов этими разделительными строками и использовать метки в качестве ключей для индексации запросов.

Грязный пример:

#!/usr/bin/env node 
"use strict"; 

var Fs = require("fs"); 

var src = Fs.readFileSync("./test.sql"); 

var queries = {}; 


var label = false; 

String(src).split("\n").map(function(row){ 
    var m = row.match(/^-+\((.*?)\)-+[/*{]*$/); 
    if (m) return queries[label = m[1].replace(" ", "_").toLowerCase()] = ""; 
    if(row.match(/^-+[/*}]*$/)) return label = false; 
    if (label) queries[label] += row+"\n"; 
}); 

console.log(queries); 
// { query_1: 'select foo from bar;\n', 
// query_2: 'select foo.baz \nfrom foo\njoin bar using (foobar)\n' } 

console.log(queries["query_1"]); 
// select foo from bar; 

console.log(queries["query_2"]); 
// select foo.baz 
// from foo 
// join bar using (foobar) 

Наконец (идея), если вы делаете, как много усилий, не было бы плохой идеей, чтобы добавить некоторую булеву метку вместе с каждым ярлыком запроса говорить, если этот запрос является предназначенные для использования часто или только изредка. Затем вы можете использовать эту информацию для подготовки этих инструкций при запуске приложения или только тогда, когда они будут использоваться более одного раза.

5

Предлагаю вам сохранить ваши запросы в .sql файлах вдали от вашего js-кода. Это отделит проблемы и сделает код & более читаемым. У вас должны быть разные каталоги с вложенной структурой на основе вашего бизнеса.

например:

queries 
├── global.sql 
├── products 
│ └── select.sql 
└── users 
    └── select.sql 

Теперь вам просто нужно требовать, чтобы все эти файлы при запуске приложения. Вы можете сделать это вручную или использовать некоторую логику. Приведенный ниже код будет читать все файлы (синхронизации) и произвести объект с той же иерархии, что и папка выше

var glob = require('glob') 
var _ = require('lodash') 
var fs = require('fs') 

// directory containing all queries (in nested folders) 
var queriesDirectory = 'queries' 

// get all sql files in dir and sub dirs 
var files = glob.sync(queriesDirectory + '/**/*.sql', {}) 

// create object to store all queries 
var queries = {} 

_.each(files, function(file){ 
    // 1. read file text 
    var queryText = fs.readFileSync(__dirname + '/' + file, 'utf8') 

    // 2. store into object 
    // create regex for directory name 
    var directoryNameReg = new RegExp("^" + queriesDirectory + "/") 

    // get the property path to set in the final object, eg: model.queryName 
    var queryPath = file 
     // remove directory name 
     .replace(directoryNameReg,'') 
     // remove extension 
     .replace(/\.sql/,'') 
     // replace '/' with '.' 
     .replace(/\//g, '.') 

    // use lodash to set the nested properties 
    _.set(queries, queryPath, queryText) 
}) 

// final object with all queries according to nested folder structure 
console.log(queries) 

вывода журнала

{ 
    global: '-- global query if needed\n', 
    products: { 
     select: 'select * from products\n' 
    }, 

    users: { 
     select: 'select * from users\n' 
    } 
} 

так что вы можете получить доступ к всем запросам, как этот queries.users.select

0

Процедура вызова в коде после ввода запроса в процедуру db. @paval также уже ответил , вы также можете обратиться к here.

создать процедуру sp_query()
выбрать * из таблицы1;

1

Другой подход с отдельными файлами с использованием шаблонов строк ES6.

Конечно, это не отвечает на исходный вопрос, потому что для этого требуется ES6, но уже есть принятый ответ, который я не собираюсь заменять. Я просто подумал, что это интересно с точки зрения обсуждения альтернатив хранения и управления запросами.

// myQuery.sql.js 
"use strict"; 

var p = module.parent; 
var someVar = p ? '$1' : ':someVar'; // Comments if needed... 
var someOtherVar = p ? '$2' : ':someOtherVar'; 

module.exports = ` 
[email protected]@[email protected]@ 
    select foo from bar 
    where x = ${someVar} and y = ${someOtherVar} 
[email protected]@/[email protected]@ 
`; 

module.parent || console.log(module.exports); 
// (or simply "p || console.log(module.exports);") 

Преимущества этого подхода являются:

  • Это очень читаемый, даже немного Javascript накладные расходы.

  • Параметры помещаются как читаемые имена переменных вместо глупых «$ 1, $ 2» и т. Д. ... и явно объявлены в верхней части файла, поэтому просто проверить, в каком порядке они должны быть предоставлены.

  • Может потребоваться как myQuery = require("path/to/myQuery.sql.js") получение правильной строки запроса с позиционными параметрами $ 1, $ 2 и т. Д. В указанном порядке.

  • но, кроме того, могут быть непосредственно выполнены с node path/to/myQuery.sql.js получения действительного SQL будет выполняться в SQL интерпретатор

    • Таким образом, вы можете избежать путаницы копирования туда и обратно запрос и заменить спецификацию параметров (или значения) каждый раз от среды тестирования запросов до кода приложения: просто используйте тот же файл.

    • Примечание: Я использовал синтаксис PostgreSQL для имен переменных. Но с другими базами данных, если они отличаются, их довольно легко адаптировать.

Пример:

(
    echo "\set someVar 3" 
    echo "\set someOtherVar 'foo'" 
    node path/to/myQuery.sql.js 
) | psql dbName 

Примечание: '@@ @@ SQL' и '@@/SQL @@' (или аналогичный) этикетки полностью необязательно, но очень полезно для надлежащего подсветка синтаксиса, at least in Vim

на самом деле, я на самом деле не написать ниже (...) | psql... кода непосредственно в консоль, а просто (в буфере ВИМ):

echo "\set someVar 3" 
echo "\set someOtherVar 'foo'" 
node path/to/myQuery.sql.js 

... как много раз, как условия испытаний, которые я хочу, чтобы проверить и выполнить их с помощью визуального выбора необходимого блока и ввода :!bash | psql ...

+1

На самом деле, большое спасибо за этот ввод. Мы очень быстро переходим от «огромного» SQL к DAO, используя короткие фрагменты SQL, то, что вы написали, очень удобно. Деффо будет помнить об этом! – m4mbax

1

Я опоздал на вечеринку, но если вы хотите сохранить связанные запросы в одном файле, YAML хорошо подходит, потому что она обрабатывает произвольные пробелы лучше, чем почти любой другой формат сериализации данных, и она имеет некоторые другие интересные функции, такие как комментарии:

someQuery: |- 
    SELECT * 
    ,DATE_ADD(sc.created_at,INTERVAL 14 DAY) AS duedate 
    ,distance_mail(?,?,lat,lon) as distance,count(pks.skill_id) c1 
    ,count(ps.profile_id) c2 
    FROM TABLE sc 
    -- ... 

# Here's a comment explaining the following query 
someOtherQuery: |- 
    SELECT 1; 

Таким образом, с помощью модуля как js-yaml вы можете легко загрузить все запросы в объект при запуске и доступ каждому по разумным имя:

const fs = require('fs'); 
const jsyaml = require('js-yaml'); 
export default jsyaml.load(fs.readFileSync('queries.yml')); 

Вот отрывок из него в действии (используя строку шаблона, вместо файла):

const yml = 
 
`someQuery: |- 
 
    SELECT * 
 
    FROM TABLE sc; 
 
someOtherQuery: |- 
 
    SELECT 1;`; 
 

 
const queries = jsyaml.load(yml); 
 
console.dir(queries); 
 
console.log(queries.someQuery);
<script src="https://unpkg.com/[email protected]/dist/js-yaml.min.js"></script>

Смежные вопросы