【问题标题】:IN clause and placeholdersIN 子句和占位符
【发布时间】:2011-11-17 03:28:58
【问题描述】:

我正在尝试在 Android 中执行以下 SQL 查询:

    String names = "'name1', 'name2";   // in the code this is dynamically generated

    String query = "SELECT * FROM table WHERE name IN (?)";
    Cursor cursor = mDb.rawQuery(query, new String[]{names});

但是,Android 不会将问号替换为正确的值。我可以执行以下操作,但这并不能防止 SQL 注入:

    String query = "SELECT * FROM table WHERE name IN (" + names + ")";
    Cursor cursor = mDb.rawQuery(query, null);

我怎样才能绕过这个问题并能够使用 IN 子句?

【问题讨论】:

    标签: android sqlite


    【解决方案1】:

    "?, ?, ..., ?" 形式的字符串可以是动态创建的字符串,并安全地放入原始 SQL 查询中(因为它是不包含外部数据的受限形式),然后可以正常使用占位符。

    考虑一个函数String makePlaceholders(int len),它返回用逗号分隔的len问号,然后:

    String[] names = { "name1", "name2" }; // do whatever is needed first
    String query = "SELECT * FROM table"
        + " WHERE name IN (" + makePlaceholders(names.length) + ")";
    Cursor cursor = mDb.rawQuery(query, names);
    

    只需确保传递与位置一样多的值。 SQLite 中的默认最大值 limit of host parameters 是 999 - 至少在正常构建中,不确定 Android :)


    这是一种实现方式:

    String makePlaceholders(int len) {
        if (len < 1) {
            // It will lead to an invalid query anyway ..
            throw new RuntimeException("No placeholders");
        } else {
            StringBuilder sb = new StringBuilder(len * 2 - 1);
            sb.append("?");
            for (int i = 1; i < len; i++) {
                sb.append(",?");
            }
            return sb.toString();
        }
    }
    

    【讨论】:

    • 是的,这是在 SQLite 和几乎任何其他 SQL 数据库中使用参数化 IN () 查询的(唯一)方法。
    • 使用这个方法,我扩充了我使用的 ContentProvider,并在 query() 方法中添加了逻辑来测试是否存在:“IN?”如果找到,是否计算“?”的出现次数?在原始选择中,与传递的参数长度相比,组装了一个“?,?,...?”差异并替换原来的“IN?”与生成的问号集合。这使得逻辑几乎可以全局使用,并且对于我的使用来说它似乎运行良好。我确实必须添加一些特殊配置来过滤空的 IN 列表,在这些情况下,“IN?”暂时替换为“1”。
    • 这很傻,当然,如果你要自己制作带有 N 个问号的字符串,你还不如直接对数据进行编码。假设它已经过消毒。
    • 整个makePlaceholders 可以替换为TextUtils.join(",", Collections.nCopies(len, "?"))。不那么冗长。
    • Caused by: android.database.sqlite.SQLiteException: near ",": syntax error (code 1): , while compiling: SELECT url FROM tasks WHERE url=?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?
    【解决方案2】:

    简短示例,基于 user166390 的回答:

    public Cursor selectRowsByCodes(String[] codes) {
        try {
            SQLiteDatabase db = getReadableDatabase();
            SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
    
            String[] sqlSelect = {COLUMN_NAME_ID, COLUMN_NAME_CODE, COLUMN_NAME_NAME, COLUMN_NAME_PURPOSE, COLUMN_NAME_STATUS};
            String sqlTables = "Enumbers";
    
            qb.setTables(sqlTables);
    
            Cursor c = qb.query(db, sqlSelect, COLUMN_NAME_CODE+" IN (" +
                            TextUtils.join(",", Collections.nCopies(codes.length, "?")) +
                            ")", codes,
                    null, null, null); 
            c.moveToFirst();
            return c;
        } catch (Exception e) {
            Log.e(this.getClass().getCanonicalName(), e.getMessage() + e.getStackTrace().toString());
        }
        return null;
    }
    

    【讨论】:

      【解决方案3】:

      遗憾的是没有办法做到这一点(显然'name1', 'name2' 不是单个值,因此不能在准备好的语句中使用)。

      因此,您将不得不降低您的视线(例如,通过创建非常具体的、不可重复使用的查询,如 WHERE name IN (?, ?, ?))或不使用存储过程并尝试使用其他一些技术来防止 SQL 注入...

      【讨论】:

        【解决方案4】:

        按照接受的答案中的建议,但不使用自定义函数生成逗号分隔的“?”。请检查下面的代码。

        String[] names = { "name1", "name2" }; // do whatever is needed first
        String query = "SELECT * FROM table"
            + " WHERE name IN (" + TextUtils.join(",", Collections.nCopies(names.length, "?"))  + ")";
        Cursor cursor = mDb.rawQuery(query, names);
        

        【讨论】:

          【解决方案5】:

          您可以使用TextUtils.join(",", parameters) 来利用sqlite 绑定参数,其中parameters 是一个带有"?" 占位符的列表,结果字符串类似于"?,?,..,?"

          这是一个小例子:

          Set<Integer> positionsSet = membersListCursorAdapter.getCurrentCheckedPosition();
          List<String> ids = new ArrayList<>();
          List<String> parameters = new ArrayList<>();
          for (Integer position : positionsSet) {
              ids.add(String.valueOf(membersListCursorAdapter.getItemId(position)));
              parameters.add("?");
          }
          getActivity().getContentResolver().delete(
              SharedUserTable.CONTENT_URI,
              SharedUserTable._ID + " in (" + TextUtils.join(",", parameters) + ")",
              ids.toArray(new String[ids.size()])
          );
          

          【讨论】:

          • 如果“参数”之一需要为空怎么办?
          【解决方案6】:

          其实你可以使用android的原生查询方式来代替rawQuery:

          public int updateContactsByServerIds(ArrayList<Integer> serverIds, final long groupId) {
              final int serverIdsCount = serverIds.size()-1; // 0 for one and only id, -1 if empty list
              final StringBuilder ids = new StringBuilder("");
              if (serverIdsCount>0) // ambiguous "if" but -1 leads to endless cycle
                  for (int i = 0; i < serverIdsCount; i++)
                      ids.append(String.valueOf(serverIds.get(i))).append(",");
              // add last (or one and only) id without comma
              ids.append(String.valueOf(serverIds.get(serverIdsCount))); //-1 throws exception
              // remove last comma
              Log.i(this,"whereIdsList: "+ids);
              final String whereClause = Tables.Contacts.USER_ID + " IN ("+ids+")";
          
              final ContentValues args = new ContentValues();
              args.put(Tables.Contacts.GROUP_ID, groupId);
          
              int numberOfRowsAffected = 0;
              SQLiteDatabase db = dbAdapter.getWritableDatabase());
                  try {
                      numberOfRowsAffected = db.update(Tables.Contacts.TABLE_NAME, args, whereClause, null);
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
                  dbAdapter.closeWritableDB();
          
          
              Log.d(TAG, "updateContactsByServerIds() numberOfRowsAffected: " + numberOfRowsAffected);
          
              return numberOfRowsAffected;
          }
          

          【讨论】:

            【解决方案7】:

            这是无效的

            String subQuery = "SELECT _id FROM tnl_partofspeech where part_of_speech = 'noun'";
            Cursor cursor = SQLDataBase.rawQuery(
                            "SELECT * FROM table_main where part_of_speech_id IN (" +
                                    "?" +
                                    ")",
                            new String[]{subQuery}););
            

            这是有效的

            String subQuery = "SELECT _id FROM tbl_partofspeech where part_of_speech = 'noun'";
            Cursor cursor = SQLDataBase.rawQuery(
                            "SELECT * FROM table_main where part_of_speech_id IN (" +
                                    subQuery +
                                    ")",
                            null);
            

            使用 ContentResolver

            String subQuery = "SELECT _id FROM tbl_partofspeech where part_of_speech = 'noun' ";
            
            final String[] selectionArgs = new String[]{"1","2"};
            final String selection = "_id IN ( ?,? )) AND part_of_speech_id IN (( " + subQuery + ") ";
            SQLiteDatabase SQLDataBase = DataBaseManage.getReadableDatabase(this);
            
            SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
            queryBuilder.setTables("tableName");
            
            Cursor cursor =  queryBuilder.query(SQLDataBase, null, selection, selectionArgs, null,
                    null, null);
            

            【讨论】:

              【解决方案8】:

              在 Kotlin 中,您可以使用 joinToString

              val query = "SELECT * FROM table WHERE name IN (${names.joinToString(separator = ",") { "?" }})"
              val cursor = mDb.rawQuery(query, names.toTypedArray())
              

              【讨论】:

                【解决方案9】:

                我为此使用Stream API:

                final String[] args = Stream.of("some","data","for","args").toArray(String[]::new);
                final String placeholders = Stream.generate(() -> "?").limit(args.length).collect(Collectors.joining(","));
                final String selection = String.format("SELECT * FROM table WHERE name IN(%s)", placeholders);
                
                db.rawQuery(selection, args);
                

                【讨论】:

                  猜你喜欢
                  • 2017-01-05
                  • 2017-05-09
                  • 1970-01-01
                  • 2012-10-02
                  • 2010-12-24
                  • 2010-10-07
                  • 2022-12-11
                  • 2023-01-22
                  相关资源
                  最近更新 更多