MySQL IN 子句:当我多次使用相同的值时,如何获取多行?

MySQL IN clause: How can I get multiple rows when I use a same value multiple times?

我正在 Visual Studio 中编写 C# Windows Forms 应用程序,我正在尝试获取有关产品价格的数据以及用户将产品添加到其购物清单的数量我的本地 MySQL-数据库到列表(int)中。

我的做法如下:

如果用户已将产品添加到他们的购物清单中 4 次,我将产品的条形码添加到我的列表(int)中 4 次。

这是有效的,但是当我使用 String.Join() 方法将列表的所有项目读出到我的查询的 IN 子句中并执行它时,它只 return尽管 IN 运算符多次具有相同的条形码,但它是一行。

以下是我如何将条形码添加到我的 List(int)

int count = 0;
List<int> barcodes = new List<int>();
MySqlCommand cmd = new MySqlCommand("SELECT product_barcode, amount FROM shopping_list_items WHERE shopping_list_id = " + current_shoppingListID + ";", db.connection);
cmd.ExecuteNonQuery();
var reader = cmd.ExecuteReader();

while (reader.Read())
{
    do
    {
        barcodes.Add(Int32.Parse(reader["product_barcode"].ToString()));
        count++;
    } while (count < Int32.Parse(reader["amount"].ToString()));
}

reader.Close();

这就是我执行查询并将值分配给变量的方式:

MySqlCommand cmdSum = new MySqlCommand("SELECT sum(price) AS 'total', supermarket_id FROM prices WHERE barcode IN (" + String.Join(", ", barcodes) + ") GROUP BY supermarket_id;", db.connection);
cmdSum.ExecuteNonQuery();
var readerSum = cmdSum.ExecuteReader();

while (readerSum.Read())
{
    switch (double.Parse(readerSum["supermarket_id"].ToString()))
    {
        case 1:
            sumSupermarket1 = double.Parse(readerSum["total"].ToString());
            break;

        case 2:
            sumSupermarket2 = double.Parse(readerSum["total"].ToString());
            break;

        case 3:
            sumSupermarket3 = double.Parse(readerSum["total"].ToString());
            break;
     }
}

一个简单的查询可能看起来像这样:

SELECT name FROM products WHERE barcode IN (13495, 13495, 13495);

如果上面的查询是我的查询,那么我希望它 return 3 个相同的行。

所以我现在的问题是,尽管我在 MySQL-查询的 IN 子句中多次使用相同的值,但我如何才能获得多行?

问:虽然我在 MySQL-query 的 IN-clause 中多次使用相同的值,但如何获得多行?

答:我们没有。 IN () 不是这样的。

请注意

WHERE foo IN ('fee','fi','fi','fi')` 

对于

是shorthand
WHERE ( foo = 'fee'
     OR foo = 'fi'
     OR foo = 'fi'
     OR foo = 'fi'
      )

了解这里发生的事情。 MySQL 将检查每一行,并针对每一行检查此条件 return 是否为真。如果该行满足条件,则该行得到 returned。否则该行不是 returned.

foo值为'fi'的一行满足多个条件也没关系。 MySQL 关心的是括号内的条件最终计算为 TRUE。

作为示例,请考虑:

  WHERE ( t.picked_by     = 'peter piper'
       OR t.picked_amount = 'peck'
       OR t.name          LIKE '%pickled%'
       OR t.name          LIKE '%pepper%'
        )

可能有一行满足所有这些条件。但是 WHERE 子句只询问整个条件是否为 TRUE。如果是,return 该行。如果没有,则排除该行。我们没有得到一行的四个副本,因为满足了不止一个条件。


那么我们如何得到一个包含一行的多个副本的集合呢?

作为一种可能的选择,我们可以使用单独的 SELECT 语句并将结果与​​ UNION ALL 集合运算符组合。像这样:

SELECT p1.name FROM product p1 WHERE p1.barcode IN (13495)
UNION ALL
SELECT p2.name FROM product p2 WHERE p2.barcode IN (13495)
UNION ALL
SELECT p3.name FROM product p3 WHERE p3.barcode IN (13495)

请注意,此查询的结果与原始查询的结果有很大不同。

还有其他查询模式可以 return 一个等效的集合。


跟进

在不了解用例和规范的情况下,我只是在猜测我们试图实现的目标。基于代码中显示的两个查询(遵循我们在易受 SQL 注入攻击的代码中看到的常见模式),

购物清单:

SELECT i.product_barcode
     , i.amount
  FROM shopping_list_item i
 WHERE i.shopping_list_id = :id

什么是amount?那是订购的数量吗?我们要 罐这个,还是 磅那个?似乎我们想要将单价乘以订购数量以获得成本。 (两罐的价格是一罐的两倍。)

如果我们想要的是来自多家商店的购物清单上的商品的总成本,我们可以这样做:

SELECT SUM(p.price * s.amount)  AS `total`
     , p.supermarket_id 
  FROM ( SELECT i.product_barcode
              , i.amount
           FROM shopping_list_item i 
          WHERE i.shopping_list_id = :id
       ) s
  JOIN price p
    ON p.barcode = s.product_barcode
 GROUP
    BY p.supermarket_id

请注意,如果特定的 product_barcode 不适用于特定的 supermarket_id,则列表中的该项目将被排除在总数之外,即对于没有超市的超市,我们可以获得较低的总数我们的清单上应有尽有。

为了性能,我们可以去掉内联视图,这样写查询:

SELECT SUM(p.price * i.amount)  AS `total`
     , p.supermarket_id
  FROM shopping_list_item i
  JOIN price p
    ON p.barcode = i.product_barcode
 WHERE i.shopping_list_id = :id
 GROUP
    BY p.supermarket_id

如果我们绝对必须通过购物清单查询,然后使用其中的行创建第二个查询,我们可以形成如下所示的查询:

SELECT SUM(p.price * i.amount) AS `total`
     , p.supermarket_id 
  FROM ( -- shopping_list here
         SELECT '13495' AS product_barcode, '1'+0 AS amount 
         UNION ALL SELECT '13495', '1'+0
         UNION ALL SELECT '13495', '1'+0
         UNION ALL SELECT '12222', '2'+0
         UNION ALL SELECT '15555', '5'+0
         -- end shopping_list
       ) i
   JOIN price p
     ON p.barcode = i.product_barcode
  WHERE i.shopping_list_id = :id
  GROUP
     BY p.supermarket_id

您可能最好研究 LINQ to SQL 而不是使用直接 SQL 和注入。

您可以使用内联 table 连接来完成您想要的:

"SELECT sum(price) AS 'total', supermarket_id
 FROM (select "+barcodes[0]+"as bc union all select "+String.Join(" union all select ", barcodes.Skip(1).ToArray())+") w
 JOIN prices p ON p.barcode = w.bc
 GROUP BY supermarket_id;"

注意:如果您可以使用内联 table 别名命名列(我无法测试),您可以简化内联 table 生成。