MySQL从JSON对象动态构建查询

时间:2019-06-27 15:20:53

标签: javascript mysql json

有没有一种方法可以根据其值可能为空的JSON对象动态构建MySQL查询。

例如,从这样的一个对象开始:

{
  "a": 1
  "b": ""
  "c": "foo"
}

创建类似这样的查询(“ b”为空,不应考虑在内):

SELECT * FROM db.table
WHERE a = 1
AND c = "foo"

SELECT * FROM db.table
WHERE a = 1
AND b = ????
AND c = "foo"

编辑:可能确实是重复的。但我认为,例如使用变量和IF语句,这是一种更多的SQL方法。

编辑2:我找到了一种方法(使用node.js API,但在其他语言中应该类似):

const jsonObj = {
"a": 1,
"b": "",
"c": "foo"
}
const query = `
SELECT * FROM db.table
WHERE
IF('${jsonObj.a}' != '', a = '${jsonObj.a}', 1=1)
AND
IF('${jsonObj.b}' != '', b = '${jsonObj.b}', 1=1)
AND
IF('${jsonObj.c}' != '', c = '${jsonObj.c}', 1=1)
`

当然,此代码本身并不可用,必须加以修改以记住注入问题。

2 个答案:

答案 0 :(得分:1)

重要:此策略对SQL Injection Attacks开放。您必须转义这些值-最好使用准备好的查询。没有数据库客户端的更多知识,就无法指导您如何操作。

添加:我强烈建议您将允许列列入白名单,并且仅允许将白名单中的列键用于查询。下面的示例包含一个白名单来说明这一点。

这里是一个MVP,它将处理任意/动态对象,并根据您的请求构建SQL语句:

const obj = {
  "a": 1,
  "b": "",
  "c": "foo",
  "bad": "disallowed"
}

// example of how to use a whitelist
const whitelist = ['a', 'c'];

// set up an empty array to contain the WHERE conditions
let where = [];

// Iterate over each key / value in the object
Object.keys(obj).forEach(function (key) {
    // if the key is not whitelisted, do not use
    if ( ! whitelist.includes(key) ) {
        return;
    }

    // if the value is an empty string, do not use
	  if ( '' === obj[key] ) {
        return;
    }

    // if we've made it this far, add the clause to the array of conditions
    where.push(`\`${key}\` = "${obj[key]}"`);
});

// convert the where array into a string of AND clauses
where = where.join(' AND ');

// if there IS a WHERE string, prepend with WHERE keyword
if (where) {
    where = `WHERE ${where}`;
}

const sql = `SELECT * FROM table ${where}`;

console.log(sql);
// SELECT * FROM table WHERE `a` = "1" AND `c` = "foo"

注意:

  1. 为您提供任何形式的转义字符都超出此问题/答案的范围。如果值包含双引号字符(例如"),这肯定会失败
  2. 为您提供一种手段来“检测”该值是否为数字,并且不将其用引号引起来,这也不属于此问题/答案的范围。请注意,以我的经验,许多数据库都可以正确处理用引号引起来的数字值。

答案 1 :(得分:1)

让我们尝试创建一个可以处理许多复杂查询的函数

function prepareStmtFromObject(params) {
    const constraints = [];
    const data = [];
    Object.keys(params).forEach((item) => {
        if (!params[item] || params[item] == "") {
            return;
        }
        if (Array.isArray(params[item])) {
            constraints.push(`${item} in (?)`);
            data.push(params[item]);
        } else if (typeof params[item] === "string" && params[item].indexOf(",") > -1) {
            constraints.push(`${item} in (?)`);
            data.push(params[item].split(","));
        } else if (params[item] instanceof RegExp) {
            constraints.push(`${item} REGEXP ?`);
            data.push(params[item]);
        } else if (params[item] && typeof params[item] === "object") {
            Object.keys(params[item]).forEach((value) => {
                if (value === "$gte") {
                    constraints.push(`${item} >= ?`);
                    data.push(params[item][value]);
                } else if (value === "$lte") {
                    constraints.push(`${item} <= ?`);
                    data.push(params[item][value]);
                } else if (value === "$gt") {
                    constraints.push(`${item} > ?`);
                    data.push(params[item][value]);
                } else if (value === "$lt") {
                    constraints.push(`${item} < ?`);
                    data.push(params[item][value]);
                } else if (value === "$like") {
                    if (Array.isArray(params[item][value])) {
                        const localConstraints = [];
                        params[item][value].forEach((likeValues) => {
                            localConstraints.push(`${item} LIKE ?`);
                            data.push(`%${likeValues}%`);
                        });
                        constraints.push(`(${localConstraints.join(" OR ")})`);
                    } else if (typeof params[item][value] === "string" && params[item][value].indexOf(",") > -1) {
                        const localConstraints = [];
                        params[item][value] = params[item][value].split(",");
                        params[item][value].forEach((likeValues) => {
                            localConstraints.push(`${item} LIKE ?`);
                            data.push(`%${likeValues}%`);
                        });
                        constraints.push(`(${localConstraints.join(" OR ")})`);
                    } else {
                        constraints.push(`${item} LIKE ?`);
                        data.push(`%${params[item][value]}%`);
                    }
                }
            });
        } else {
            constraints.push(`${item} = ?`);
            data.push(params[item]);
        }
    });
    return { constraints, data };
}

const data = {
    userId: 1,
    company: ["google", "microsoft"],
    username: { $like: "Test" },
    name: { $like: [ "Test1", "Test2" ] },
    age: { $gt: 10 }
}
const stmt = prepareStmtFromObject(data);
console.log("SELECT * FROM user WHERE ", stmt.constraints.join(" and "));
console.log(stmt.data);

上面的函数返回一个约束和一个查询数组,可用于转义字符。这样,您还可以防止SQL注入。我假设您使用的是mysql软件包