在哪里存储SQL命令执行

由于内联mysql查询,我们面临代码质量问题。 有自已写的mysql查询确实混乱的代码,也增加了代码库等

我们的代码杂乱无章

/* 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,由于多行string,这至less会让string变得相当漂亮。

现在的问题是,有没有办法将SQL过程存储在一个地方? 作为附加信息,我们使用节点(〜0.12)并表示暴露一个API,访问MySQL数据库。

我已经想过,使用JSON,这将导致更大的混乱。 再加上它可能是不可能的,因为JSON字符集有点严格,JSON可能不会喜欢多行string。

然后我想出了将SQL存储在文件中并在节点应用启动时加载的想法。 目前,这是我在一个地方获取SQL查询并将其提供给其余节点模块的最佳select。 这里的问题是,使用一个文件? 每个查询使用一个文件? 每个数据库表使用一个文件?

任何帮助表示赞赏,我不能成为这个星球上的第一个解决这个问题,所以也许有人有一个工作,很好的解决scheme!

PS:我尝试使用类似于squel的库,但这并没有真正的帮助,因为我们的查询是复杂的,你可以看到。 主要是把我们的查询变成“查询中心”。

我更喜欢把每个更大的查询放在一个文件中。 这样你可以有语法突出显示,并且在服务器启动时很容易加载。 要构造这个,我通常有一个文件夹的所有查询,并在每个模型的一个文件夹。

 # 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') }; 

把你的查询放到数据库过程中,并在代码中调用过程。

 create procedure sp_query() select * from table1; 

我build议你将你的查询存储在远离你的js代码的.sql文件中。 这将分离问题并使代码和查询更具可读性。 根据您的业务,您应该有不同的嵌套结构目录。

例如:

 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一样访问所有的查询

我来自不同的平台,所以我不知道这是你在找什么。 像您的应用程序一样,我们有许多模板查询,我们不喜欢在应用程序中进行硬编码。

我们在MySQL中创build了一个表格,允许保存Template_Name(unique),Template_SQL。

然后我们在我们的应用程序中编写了一个返回SQL模板的小函数。 像这样的东西:

 SQL = fn_get_template_sql(Template_name); 

然后我们处理这样的SQL:pseudo:

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

或者你可以读取SQL,使用你最安全的方式创build连接和添加参数。

这使您可以随时编辑模板查询。 您可以为模板表创build一个审计表,捕获所有先前的更改,以便在需要时恢复到之前的模板。 您可以扩展表格并捕获最后编辑的SQL和谁的时间。

从性能的angular度来看,这将以即时方式工作,而且在添加新模板时,您不必读取任何文件或重新启动服务器。

您可以创build一个全新的npm模块,让我们假设自定义查询模块,并将所有复杂的查询放在那里。

然后,您可以按资源和行动对所有查询进行分类。 例如,目录结构可以是:

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

以下查询可以在它自己的文件中的/ queries / complex目录下生存,并且该文件将具有描述性名称(让我们假设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 

做这样的事情,你会把string连接的所有噪音移动到你期望的另一个地方,你将能够很容易地在一个地方find所有复杂的查询。

这毫无疑问是一百万美元的问题,我认为正确的解决scheme总是取决于案件。

这里是我的想法。 希望可以帮助:

一个简单的技巧(实际上,我认为它比使用“+”连接string更有效率)是为每行使用string数组并join它们。

它仍然是一个混乱,但至less对我来说,更清楚一点(特别是当我使用“\ n”作为分隔符而不是空格时,使打印出来的debugging结果string更可读)。

例:

 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“构build”库中使用了这个语法。 它可能不适用于过于复杂的查询,但是,如果您有提供的参数可能会有所不同的情况,通过完全删除不需要的查询部分来避免(也是subotptimal)“合并”混淆非常有帮助。 它也在GitHub上 ,(它不是太复杂的代码),所以你可以扩展它,如果你觉得它有用。

如果你喜欢单独的文件:

关于单个或多个文件,从读取效率(更多的文件打开/closures开销和更难的操作系统级caching)的angular度来看,拥有多个文件效率较低。 但是,如果您在启动时单次加载所有这些文件,实际上并不是一个难以察觉的差异。

所以唯一的缺点就是对查询集合进行“全局浏览”太困难了。 即使如果您有大量的疑问,我认为最好将这两种方法混合使用。 也就是说:在同一个文件中分组相关的查询,所以每个模块,子模型或者你select的任何标准都有单个文件。

当然:单个文件会导致相对“巨大”的文件,也难以处理“起初”。 但我(几乎)不使用vim的基于标记的折叠( foldmethod = marker ),这是非常有用的处理该文件。

当然,如果你还没有使用vim(真正的??),那么你就没有这个select,但是确定你的编辑器还有另一个select。 如果没有,你总是可以使用语法折叠和像“function(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)------------------------------------------------ 

其中,使用正确select的标签,更方便pipe理,从parsing的angular度来看,并不难分析整个文件拆分查询的分隔行,并使用标签作为关键字索引查询。

肮脏的例子:

 #!/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) 

最后(想法),如果你做了很多的努力,将一些布尔标记与每个查询标签一起添加,这样做不是一个坏主意,因为它告诉查询是否意图频繁或偶尔使用。 然后,您可以使用这些信息来在应用程序启动时准备这些语句,或者仅当它们将被使用超过一次时。

有几件事情你想要做。 首先,你要存储多线没有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)); 

你可以创build一个查看哪个查询。

然后从视图中select

我没有看到在查询中的任何参数,所以我认为创build视图是可能的。

为所有查询创build存储过程,并replacevar sql = "SELECT..."来调用像var sql = "CALL usp_get_packages"

这对于性能来说是最好的,并且在应用程序上没有依赖关系。 根据查询的数量可能是一个巨大的任务,但对于每个方面(可维护性,性能,依赖性等)是最好的解决scheme。

我迟到了,但是如果你想把相关的查询存储在一个单独的文件中,YAML是一个很好的select,因为它比任何其他数据序列化格式更好地处理任意的空格,并且还有其他一些很好的function,比如注释:

 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')); 

下面是它的一个片段(使用模板string而不是文件):

 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/js-yaml@3.8.1/dist/js-yaml.min.js"></script> 

另一种使用ES6string模板的独立文件的方法。

当然,这并不能回答原来的问题,因为它需要ES6,但已经有一个我不想取代的公认的答案。 我只是认为,从讨论查询存储和pipe理方法的angular度来看,这很有趣。

 // myQuery.sql.js "use strict"; var p = module.parent; var someVar = p ? '$1' : ':someVar'; // Comments if needed... var someOtherVar = p ? '$2' : ':someOtherVar'; module.exports = ` --@@sql@@ select foo from bar where x = ${someVar} and y = ${someOtherVar} --@@/sql@@ `; module.parent || console.log(module.exports); // (or simply "p || console.log(module.exports);") 

这种方法的优点是:

  • 非常可读,即使是JavaScript的小开销。

  • 参数被放置为可读的variables名称,而不是愚蠢的“$ 1,$ 2”等等,并且在文件顶部显式声明,因此检查它们必须提供的顺序很简单。

  • 可以被要求为myQuery = require("path/to/myQuery.sql.js")以指定的顺序获取有效的查询string$ 1,$ 2等等。

  • 但是,也可以直接用node path/to/myQuery.sql.js执行得到有效的SQL来在sql解释器中执行

    • 通过这种方式,您可以避免从查询testing环境到应用程序代码的前后复制查询和replace参数说明(或值)的混乱:只需使用相同的文件即可。

    • 注意:我使用PostgreSQL语法来表示variables名称。 但是对于其他数据库来说,如果不同,则很容易适应。

例:

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

注意: '@@ sql @@'和'@@ / sql @@'(或类似的)标签是完全可选的,但是对于正确的语法高亮非常有用, 至less在Vim

其实我其实并没有在下面写(...) | psql... (...) | psql...直接编码到控制台,但只是(在vim缓冲区中):

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

…尽可能多的testing条件我想testing和执行他们通过目视select所需的块和input:!bash | psql ... :!bash | psql ...

将查询放入db过程后,调用代码中的过程。 @paval也已经回答你也可以在这里查阅 。

创build过程sp_query()
select * from table1;