Предложение MySQL IN: как я могу получить несколько строк, когда я использую одно и то же значение несколько раз?

Я программирую приложение С# Windows Forms в Visual Studio и пытаюсь получить данные о ценах на продукты и о сумме, которую пользователь добавил в свой список покупок, из моей локальной базы данных MySQL в список (int).

Я делаю следующее:

Если пользователь добавил продукт 4 раза в свой список покупок, я добавляю штрих-код продукта 4 раза в свой список (int).

Это работает, но когда я считываю все элементы списка с помощью метода String.Join() в предложение IN моего запроса и выполняю его, он возвращает строку только один раз, хотя оператор IN имеет один и тот же штрих-код несколько раз.

Ниже показано, как я добавляю штрих-коды в свой список (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);

Если приведенный выше - мой запрос, я хочу, чтобы он возвращал 3 одинаковых строки.

Итак, теперь мой вопрос: как я могу получить несколько строк, хотя я использую одно и то же значение несколько раз в предложении IN MySQL-запроса?


person David P.    schedule 21.06.2019    source источник


Ответы (2)


В: как я могу получить несколько строк, если я использую одно и то же значение несколько раз в предложении IN запроса MySQL?

A: Мы не делаем. IN () работает не так.

Обратите внимание, что

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

Является сокращением для

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

Понять, что здесь происходит. MySQL проверит каждую строку и для каждой строки проверит, возвращает ли это условие значение TRUE или нет. Если строка удовлетворяет условию, строка возвращается. В противном случае строка не возвращается.

Неважно, что строка со значением 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. Если это так, верните строку. Если это не так, исключите строку. Мы не получаем четыре копии строки, потому что выполняется более одного условия.


Как же нам получить набор из нескольких копий строки?

Как один из возможных вариантов, мы могли бы использовать отдельные операторы SELECT и объединить результаты с оператором UNION ALL set. Что-то вроде этого:

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)

Обратите внимание, что результат этого запроса значительно отличается от результата исходного запроса.

Существуют и другие шаблоны запросов, которые могут возвращать эквивалентный набор.


ПОСЛЕДУЮЩИЕ

Без понимания варианта использования, спецификации, я просто догадываюсь о том, чего мы пытаемся достичь. Основываясь на двух запросах, показанных в коде (которые следуют общему шаблону, который мы видим в коде, уязвимом для 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
person spencer7593    schedule 21.06.2019
comment
Большое спасибо Спенсер, это сработало. Я застрял на пару часов, но вы только что решили эту проблему :) кстати: под количеством я подразумеваю пару раз, когда пользователь добавил продукт в свой список покупок. - person David P.; 22.06.2019

Вероятно, вам было бы лучше изучить LINQ to SQL, а не использовать прямой SQL и внедрение.

Вы можете использовать встроенное соединение таблиц, чтобы выполнить то, что вы хотите:

"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;"

Примечание. Если вы можете назвать столбец псевдонимом встроенной таблицы (я не смог это проверить), вы можете упростить создание встроенной таблицы.

person NetMage    schedule 21.06.2019
comment
Работает ли LINQ to SQL с MySQL? Я знал, что это сделал EF, но не был уверен в LINQ to SQL. - person mjwills; 22.06.2019
comment
@mjwills Есть несколько вариантов — см. Nuget для LINQ2Db.MySQL, MySQL Connector/Net поддерживает EF 6 и LINQ to SQL, а также есть коммерческие продукты, такие как DevArt. Обычно я использую драйвер IQ, включенный в LINQPad. - person NetMage; 24.06.2019