【问题标题】:i have a problem with selecting some data from mysql database both side CSV我在从 mysql 数据库双方 CSV 中选择一些数据时遇到问题
【发布时间】:2018-11-25 13:29:42
【问题描述】:

我有两边逗号分隔的值,比如

 $ing=1,2,3,4,5,6

并且数据库有一个带有值的表

 IDS    5,2,1,6,2,3,45 // in database

我知道这不是一个好习惯,但我必须这样做才能在一个查询中获取值。我还有一个单独的表,其中 IDS 是单独的,并且对应于单独的用户,例如

  user 1    IDS 2 
  user 3    IDS 65 etc 

目前我正在使用

 $conditions[] = "CONCAT(',', `rcring`, ',') REGEXP ',($ing),'";

它给了我很好的结果,但它给了我存在任何 $ing 的值我只想要至少具有所有 $ing 的表

您能帮帮我吗?我尝试了我的工作,但在任何地方都找不到合适的解决方案。谢谢。

      //EDIT

我已经有条件作为一个数组

   if($ser !=''){
                   $conditions[] = "rc_ser='$ser'";
                }if($fridge == 1){
                    $ing="0|";
                    $ctch2fr='checked';
                    foreach($_SESSION['fridge'] as $item){
                    $ing=$ing.$item."|";}
                   $conditions[] = "CONCAT(',', `rcring`, ',') REGEXP ',($ing),'";
                }
                 if (count($conditions) > 0) {
                $sql= implode(' AND ', $conditions);
                }

像这样作为数组中的条件,我在查询中这样称呼它

select * ,(select count(rcid) from recipe where ($sql)) as total from recipe where ($sql)

我尝试了下面的答案,但它总是给我 0 结果 当我打印查询时,它会像这样显示我

 select *,(select count(rcid) from recipe where (CONCAT(',', `rcring`, ',') REGEXP ',(0),') ) as total from recipe where (CONCAT(',', `rcring`, ',') REGEXP ',(0),')

【问题讨论】:

  • 1.删除$conditions 附近的[] 2. 你能用var_dump($conditions); 告诉我你的结果吗?
  • 您能否展示一个表格示例和您想要作为输出的数据?
  • 在数据库列中存储分隔列表真的那么糟糕吗? 是的,就是这么糟糕 stackoverflow.com/a/3653574/2739274
  • 我怀疑这句话的真实性“我知道这不是一个好的做法,但我必须这样做才能在一个查询中获取值”,你能扩展一下吗?

标签: php mysql mysqli


【解决方案1】:

您似乎有一个包含逗号分隔的成分列表的食谱表:

5,2,1,6,2,3,45

以及给定成分的列表:

1,2,3,4,5,6

并且您想找到可以使用给定成分制备的食谱(食谱成分是给定成分的子集)。您需要编写构建以下查询的 PHP 代码:

SELECT *
FROM recipe
WHERE (
    FIND_IN_SET('1', rcring) > 0 AND
    FIND_IN_SET('2', rcring) > 0 AND
    FIND_IN_SET('3', rcring) > 0 AND
    FIND_IN_SET('4', rcring) > 0 AND
    FIND_IN_SET('5', rcring) > 0 AND
    FIND_IN_SET('6', rcring) > 0
)

基于您的尝试的 PHP 代码粗略大纲(您必须将其转换为准备好的语句):

$conditions = [];
foreach($fridge_ingredients as $ingredient) {
    $conditions[] = sprintf("FIND_IN_SET('%d', rcring) > 0", $ingredient);
}
$query = sprintf("SELECT *
FROM recipe
WHERE (%s)", implode(" AND ", $conditions));

话虽如此,正确的解决方案是规范化您的数据。这是结构的概要:

CREATE TABLE recipe (recipeid INT NOT NULL PRIMARY KEY, name VARCHAR(100));
CREATE TABLE ingredient (ingredientid INT NOT NULL PRIMARY KEY, name VARCHAR(100));
CREATE TABLE recipe_ingredient(recipeid INT NOT NULL,ingredientid INT NOT NULL, PRIMARY KEY(recipeid, ingredientid));

INSERT INTO recipe VALUES
(1, 'recipe 1'),
(2, 'recipe 2'),
(3, 'recipe 3'),
(4, 'recipe 4');
INSERT INTO ingredient VALUES
(1, 'ingredient 1'),
(2, 'ingredient 2'),
(3, 'ingredient 3'),
(4, 'ingredient 4');
INSERT INTO recipe_ingredient VALUES
(1, 1),
(2, 1), (2, 2),
(3, 1), (3, 2), (3, 3),
(4, 1), (4, 2), (4, 3), (4, 4);

查询:

SELECT *
FROM recipe
WHERE recipeid IN (
    SELECT recipeid
    FROM recipe_ingredient
    GROUP BY recipeid
    HAVING COUNT(CASE WHEN ingredientid IN (1, 2, 3) THEN 1 END) = COUNT(*)
)

结果:

recipeid  |  name
----------+----------
1         |  recipe 1
2         |  recipe 2
3         |  recipe 3

【讨论】:

    【解决方案2】:

    据我所知,您指定的条件仅在 rcring 包含与子字符串完全相同的 $ing 时匹配,例如rcring = "5,2,1,6,2,3,45" 与您的示例中的 $ing = "1,2,3,4,5,6" 不匹配。要在此处匹配,rcring 应存储为 "1,2,3,4,5,45"

    简单的排序在这里没有帮助,因为 $ing = "1,2,5,6" 不会匹配 rcring = "1,2,3,4,5,6,45"

    简单的方法

    使 $conditions[] 包含不同 ID 上的匹配连词,例如

    <?
    foreach (explode(",", $ing) as $key => $val) {
        $conditions[] = "CONCAT(',', `rcring`, ',') REGEXP ',($id),'"
    }
    ?>
    

    这将检查你想要什么——如果rcing 包含来自$ing 的所有ID

    但是,对于大型(> 10^8 行)表来说,这非常慢。此类查询将在 O(n) 时间内工作,检查表中的所有行。对于几个千兆字节的表,这可能需要一分钟以上的时间。

    优化

    使用Fulltext index searches。您需要存储由空格分隔的 ID,例如1 2 3 4 5 45,允许索引将其视为单词。 索引后,代码可以改写如下:

    <?
        $plus_ing = "+1 +2 +3 +4 +5"
        $conditions[] = "MATCh(`rcring`) AGAINST($plus_ing)"
    ?>
    

    现在它的运行速度会非常快,并且会跳过根本不匹配的行!

    小心!

    始终使用 PHP::PDO 并将 ID 作为参数传递!

    黑客可能会滥用您的系统,通过传递危险信息而不是 ID 来进行 SQL 注入,例如:123");DROP DATABASE TEST; COMMIT;// 这会将$ings 变成类似

    "+1 +2 +123\");DROP DATABASE TEST; COMMIT;//"

    最终查询看起来像

    SELECT * FROM USERS WHERE MATCh(`rcring`) AGAINST(+1 +2 +123\");DROP DATABASE TEST; COMMIT;//");
    

    正确的方法是PDO prepared statements howto 并始终以这种方式构造查询!

    【讨论】:

    • 它总是给我 0 个结果,从来不匹配任何东西,即使它应该。
    • 您的全文索引搜索建议相当不准确。默认情况下,单词的最小长度为 4 个字符,因此只有超过 1000 的 id 才会被索引/匹配,我相当确定较短的条件会被忽略。此默认值可以缩短,但会影响您的所有全文索引。逗号也算作非单词字符,因此无需将逗号分隔更改为空格分隔。
    • 全文索引不是这项工作的正确工具
    • 我更喜欢 Salman 的 FIND_IN_SET 方法,但是对于您的简单方法,您可以放弃 REGEXP ... 以支持更快的 LIKE CONCAT('%,', :i, ',%')
    • @Arth 这不是“相当不准确”。这是一个有效且快速的解决方案。检查文档。有ft_min_word_len 变量来控制字长。 LIKE... 方式被高估了。无论如何,它仍然会使 O(n) 读取
    【解决方案3】:

    您需要拆分 $ing 并在 where 子句中检查其所有部分:

    <?php
    $ings = explode(',',$ing);
    $conditions = array();
    foreach($ings as $i) {
        $conditions[] = "CONCAT(',', `rcring`, ',') REGEXP ',(".intval($i)."),'";
    }
    $where = implode(' AND ', $conditions);
    /* Use $where in where clause in your sql query */
    

    【讨论】:

    • 我更喜欢 Salman 的 FIND_IN_SET 方法,但你可以放弃你的 REGEXP ... 以支持更快的 LIKE CONCAT('%,', :i, ',%')
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-30
    • 1970-01-01
    • 2019-01-28
    • 1970-01-01
    • 1970-01-01
    • 2014-08-04
    • 1970-01-01
    相关资源
    最近更新 更多