Рекурсивная проблема sql

У меня есть проблема, которую я хотел бы решить с помощью SQL-запроса. Это будет использоваться как PoC (доказательство концепции).

Проблема:

Предложения продуктов состоят из одного или нескольких экземпляров продукта, продукта экземпляр может принадлежать ко многим товарным предложениям. Это можно реализовать в виде таблицы:

PO | PI

-----

A | 10

A | 11

A | 12

B | 10

B | 11

C | 13

Теперь я хотел бы получить обратно предложение продукта из набора экземпляров продукта. Например. если мы отправим 10,11,13, ожидаемый результат будет B & C, и если мы отправим только 10, тогда результат должен быть ПУСТО (NULL), поскольку ни одно предложение продукта не состоит из только 10. Отправка 10,11,12 приведет к получению A (не A и B, поскольку 12 сам по себе не является действительным товарным предложением).

Предпосылки: Комбинация отправленных экземпляров продукта может привести только к одному конкретному сочетание продуктовых предложений, поэтому для каждого запроса есть только одно решение.

Какая платформа базы данных? Оракул? SQL Server? MySQL? SQLite? ...

Joel Coehoorn 18.12.2008 17:45

Таким образом, запрос 10,11,12 возвращает только A, а не B. Но если мы добавим D | 12 в ваш список продуктов, то получим ли мы A, D, а также B?

Adam Bellaire 18.12.2008 18:00

Кроме того, что, если мы добавим C | 11, а затем снова попросим 10,11,13? Получим ли мы снова B & C или не получим ни одного, поскольку ни 10, ни 13 не являются ЗП сами по себе?

Adam Bellaire 18.12.2008 18:03

Первый вопрос о платформе, мы можем ограничиться Oracle для этой проблемы.

Patrik 18.12.2008 18:34

Что касается вопросов Адамса, это было то, что я пытался объяснить в предварительных условиях, что это не входит в объем проблемы, как указано, поэтому не будет D 12, извините, если я не настолько хорош в объяснении. Это что-нибудь уточняет?

Patrik 18.12.2008 18:34

@ Патрик: Может быть? Сообщите мне, правильно ли это: либо (1) будет один PO, который точно содержит все запрошенные PI, либо (2) все PO будут непересекающимися наборами, объединение которых равно запрошенным PI. Это то, что вы собираетесь делать?

Adam Bellaire 18.12.2008 18:59

Проблема: как «10,11,13» может привести к «B, C», а «10,11,12» каким-то образом дисквалифицирует «B»? Если «12» дисквалифицирует элемент «B», потому что «B» не соответствует «12», то «13» в первом запросе ТАКЖЕ должно быть выполнено по той же причине. --- ПОЖАЛУЙСТА, объясните, что вы имели в виду под словами «12 не является действительным товарным предложением само по себе».

Timothy Khouri 18.12.2008 19:45

@Timothy - Похоже, вы можете использовать только один экземпляр продукта один раз. Итак, комбинация (10,11,12) (для A) и (10,11) (для B) недействительна, поскольку вы использовали (10,11) дважды. Это то, что делает его плохо подходящим для SQL.

Mark Brackett 18.12.2008 20:29

@Mark: Да, я тоже об этом догадываюсь. На самом деле в моем последнем комментарии (1) - это просто частный случай (2), поскольку, если у вас есть только один набор, вам не о чем больше беспокоиться о том, что вы не пересекаетесь.

Adam Bellaire 18.12.2008 21:29

Я думаю, что у меня есть несколько лучшая формулировка вопроса: представьте, что есть этот магазин, который продает определенные товары. К сожалению, магазин не продает каждый продукт по отдельности, а группирует их в «предложения». Один продукт может быть в нескольких разных предложениях, а предложения могут содержать ...

Vilx- 19.12.2008 01:22

... более одного продукта. Теперь, когда приходит заказчик, его не волнует эта система. Ему просто нужны продукты A, B и C. Работа магазина состоит в том, чтобы выбрать некоторые предложения для покупателя таким образом, чтобы покупатель получил ТОЧНО то, что он хочет. Не больше и не меньше.

Vilx- 19.12.2008 01:23

Предположим, что клиенту не нужны несколько экземпляров одного и того же продукта.

Vilx- 19.12.2008 01:24

Вопрос: Это классическая рекурсивная таблица, в которой предложение продукта имеет ключ, а предложение продукта может включать вложенные предложения продуктов, тогда как нижний уровень дерева (конечные узлы, если хотите) являются экземплярами продукта?

BenAlabaster 19.12.2008 02:30

Например: Key, ParentKey, OfferingName.

BenAlabaster 19.12.2008 02:30
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
7
14
761
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

У меня нет базы данных передо мной, но, в первую очередь, вы хотите, чтобы список заказов на поставку, у которых нет никаких PI, нет в вашем списке ввода, т.е.

select distinct po 
from tbl 
where po not in ( select po from tbl where pi not in (10,11,13) )

Обновлено: вот пример других случаев:
Когда вход PI = 10,11,13, внутренний выбор возвращает A, поэтому внешний выбор возвращает B, C
Когда входное значение PI = 10, внутренний выбор возвращает A, B, C, поэтому внешний выбор не возвращает строк. Когда вход PI = 10,11,12, внутренний выбор возвращает C, поэтому внешний выбор возвращает A, B

Обновлено: Адам указал, что этот последний случай не соответствует требованию возврата только A (который научит меня спешить), поэтому это еще не рабочий код.

левое соединение, в котором столбец в объединенной таблице имеет значение NULL, обычно выполняется быстрее (но я не тот, кто проголосовал против вас).

Joel Coehoorn 18.12.2008 17:54

Это также найдет предложения продуктов, которые являются подмножествами поиска. Например, добавьте D | 13 к таблице, и D появится в результатах. Я думаю. У меня также нет передо мной db.

recursive 18.12.2008 17:56

Это действительно кажется правильным. Из заявленной проблемы, если вы добавляете D | 13, а 13 находится в списке PI, тогда D должен быть возвращен.

Adam Bellaire 18.12.2008 17:57

Хорошо, на самом деле это неверно для его вопроса, он утверждает, что 10,11,12 должны приводить к A, а не A, B. Но опять же, этот вопрос сформулирован неоднозначно, я прокомментировал выше, чтобы запросить более подробную информацию.

Adam Bellaire 18.12.2008 18:09

ну какой-то псевдокод из моей головы здесь:

выберите из таблицы, где PI = 10 или pi = 11 и т. д.

сохранить результат во временной таблице

выберите отдельный заказ на поставку и количество (PI) из временной таблицы.

теперь для каждого заказа на покупку вы можете получить все доступные предложения PI. если количество доступных PI совпадает с количеством в таблице temp, это означает, что у вас есть все PI для этого заказа на поставку. добавьте все заказы на покупку, и вы получите результат.

ИМХО невозможно через чистый SQL без кода хранимой процедуры. Но я не уверен.

Добавлен: С другой стороны, я получаю представление о рекурсивном запросе (в MSSQL 2005 есть такая вещь, которая позволяет объединять запрос с его собственными результатами до тех пор, пока не будут возвращены строки), который может «собираться» правильные ответы путем объединения результатов предыдущего шага со всеми продуктами и последующей фильтрации недопустимых комбинаций. Однако вы получите все перестановки допустимых комбинаций, и это вряд ли будет эффективно. Идея довольно расплывчата, поэтому я не могу гарантировать, что она действительно может быть реализована.

То, что он на самом деле имеет в виду, называется общим табличным выражением или CTE.

Brian Rudolph 18.12.2008 21:04

Да, CTE. Это рекурсивный запрос, не так ли? :)

Vilx- 19.12.2008 01:17
  Select Distinct PO
   From Table T
   -- Next eliminates POs that contain other PIs
   Where Not Exists 
       (Select * From Table 
        Where PO = T.PO
            And PI Not In (10, 11, 12))
     -- And this eliminates POs that do not contain all the PIs
     And Not Exists 
        (Select Distinct PI From Table  
         Where PI In (10, 11, 12)
           Except 
         Select Distinct PI From Table  
         Where PO = T.PO

или, если ваша база данных не поддерживает EXCEPT ...

   Select Distinct PO
   From Table T
   -- Next predicate eliminates POs that contain other PIs
   Where Not Exists 
       (Select * From Table 
        Where PO = T.PO
            And PI Not In (10, 11, 12))
     -- And this eliminates POs that do not contain ALL the PIs
     And Not Exists 
         (Select Distinct PI From Table A
          Where PI In (10, 11, 12)
             And Not Exists
                 (Select Distinct PI From Table 
                  Where PO = T.PO 
                     And PdI = A.PI))                 

Я проверил это на примере данных и не получил строк. Я не понимаю, что вы намеревались сделать во втором пункте.

Dave Costa 18.12.2008 22:38

Вам понадобится количество элементов в вашем списке, то есть @list_count. Выясните, в каких предложениях есть экземпляры, которых нет в списке. Выберите все предложения, которых нет в списке что, а в списке делать есть экземпляры:

select P0,count(*) c from table where P0 not in (
select P0 from table where P1 not in (@list)
) and P1 in (@list) group by P0

Я бы сохранил это во временной таблице и выбрал * записи, где c = @list_count

Я думаю, это вернет случаи, когда в @list больше элементов, чем в заданном заказе. например, вернет (A, B) для (10, 11, 12), а не (A), как указано. Также вам понадобится DISTINCT, чтобы вы получали только одну строку для каждого заказа.

Rory 18.12.2008 18:50

Это близко ... но «10,11,12» по-прежнему возвращает «A, B», что недопустимо.

Timothy Khouri 18.12.2008 19:27

Редактировать: Хотя я думаю, что мой работает нормально, ответ Адама, без сомнения, элегантнее и эффективнее - я просто оставлю свой здесь для потомков!

Прошу прощения, так как я знаю, что это было помечено как проблема Oracle с тех пор, как я начал играть. Это некоторый код SQL2008, который, я думаю, работает для всех указанных случаев ....

declare @test table
(
    [PI] int
)
insert @test values (10), (11), (13)

declare @testCount int
select @testCount = COUNT(*) from @test

;with PO_WITH_COUNTS as 
(
        select  PO_FULL.PO, COUNT(PO_FULL.[PI]) PI_Count
        from    ProductOffering PO_FULL
        left
        join    (
                select  PO_QUALIFYING.PO, PO_QUALIFYING.[PI]
                from    ProductOffering PO_QUALIFYING
                where   PO_QUALIFYING.[PI] in (select [PI] from @test)
                ) AS QUALIFYING
                on      QUALIFYING.PO = PO_FULL.PO
                and     QUALIFYING.[PI] = PO_FULL.[PI]
        group by
                PO_FULL.PO
        having  COUNT(PO_FULL.[PI]) = COUNT(QUALIFYING.[PI])
)
select  PO_OUTER.PO
from    PO_WITH_COUNTS PO_OUTER 
cross 
join    PO_WITH_COUNTS PO_INNER
where   PO_OUTER.PI_Count = @testCount
or      PO_OUTER.PO <> PO_INNER.PO
group by
        PO_OUTER.PO, PO_OUTER.PI_Count
having  PO_OUTER.PI_Count = @testCount 
or      PO_OUTER.PI_Count + SUM(PO_INNER.PI_Count) = @testCount

Не уверен, есть ли у Oracle CTE, но может просто указать внутренний запрос как две производные таблицы. Перекрестное соединение во внешнем запросе позволяет нам находить комбинации предложений, в которых есть все допустимые элементы. Я знаю, что это будет работать только на основе утверждения в вопросе о том, что данные таковы, что существует только одна допустимая комбинация для каждого запрошенного набора, без этого это еще более сложно, поскольку подсчетов недостаточно для удаления комбинаций, в которых есть повторяющиеся продукты в их.

Если немного переопределить проблему:

У нас есть таблица клиентов с экземплярами продуктов:

crete table cust_pi (
pi varchar(5),
customer varchar(5));

И таблица product_catalogue:

CREATE TABLE PI_PO_TEST
   ("PO" VARCHAR2(5 CHAR),
   "PI" VARCHAR2(5 CHAR)
           );

Давайте заполним его некоторыми образцами данных:

insert into CUST_PI (PI, CUSTOMER)
values ('11', '1');
insert into CUST_PI (PI, CUSTOMER)
values ('10', '1');
insert into CUST_PI (PI, CUSTOMER)
values ('12', '1');
insert into CUST_PI (PI, CUSTOMER)
values ('13', '1');
insert into CUST_PI (PI, CUSTOMER)
values ('14', '1');
insert into PI_PO_TEST (PO, PI)
values ('A', '10');
insert into PI_PO_TEST (PO, PI)
values ('A', '11');
insert into PI_PO_TEST (PO, PI)
values ('A', '12');
insert into PI_PO_TEST (PO, PI)
values ('A', '13');
insert into PI_PO_TEST (PO, PI)
values ('B', '14');
insert into PI_PO_TEST (PO, PI)
values ('C', '11');
insert into PI_PO_TEST (PO, PI)
values ('C', '12');
insert into PI_PO_TEST (PO, PI)
values ('D', '15');
insert into PI_PO_TEST (PO, PI)
values ('D', '14');

Тогда мое первое решение для съемки выглядит так:

select po1 po /* select all product offerings that match the product definition 
                (i.e. have the same number of product instances per offering as 
                in product catalogue */
  from (select po po1, count(c.pi) k1
          from cust_pi c, pi_po_test t
         where c.pi = t.pi
           and customer = 1
         group by po) t1,
       (select po po2, count(*) k2 from pi_po_test group by po) t2
 where k1 = k2
   and po1 = po2
minus /* add those, that are contained within others */
select slave
  from (select po2 master, po1 slave
  /* this query returns, that if you have po "master" slave should be removed from result, 
     as it is contained within*/
          from (select t1.po po1, t2.po po2, count(t1.po) k1
                  from pi_po_test t1, pi_po_test t2
                 where t1.pi = t2.pi
                 group by t1.po, t2.po) t1,
               (select po, count(po) k2 from pi_po_test group by po) t2
         where t1.po2 = t2.po
           and k1 < k2)
 where master in
 /* repeated query from begining. This could be done better :-) */
       (select po1 po
          from (select po po1, count(c.pi) k1
                  from cust_pi c, pi_po_test t
                 where c.pi = t.pi
                   and customer = 1
                 group by po) t1,
               (select po po2, count(*) k2 from pi_po_test group by po) t2
         where k1 = k2
           and po1 = po2)

Все это было сделано на Oracle, поэтому ваш опыт может отличаться

Ответ принят как подходящий

Ладно, думаю, она у меня есть. Это соответствует указанным вами ограничениям. Возможно, есть способ еще больше упростить это, но он немного съел мой мозг:

select distinct PO 
from POPI x 
where 
  PO not in (
    select PO 
    from POPI 
    where PI not in (10,11,12)
  ) 
  and PI not in (
    select PI 
    from POPI 
    where PO != x.PO 
      and PO not in (
        select PO 
        from POPI 
        where PI not in (10,11,12)
      )
  );

Это дает только результаты, которые заполняют данный набор, которые не пересекаются со всеми другими результатами, которые я считать - это то, что вы просили. Для приведенных тестовых примеров:

  • Обеспечивая 10,11,12 выходов A
  • Обеспечение 10,11,13 выходов B, C

Этот работает как положено, отличная работа! И это подтверждает мою теорию о том, что это был SQL-код. Я тебе пиво, если ты когда-нибудь попадешь в Стокгольм, Швеция

Patrik 19.12.2008 11:49

Я проверил это с 4 наборами значений, и все они дали правильный результат. Здесь используется функция, которую я использую в SQL, для создания таблицы из строки параметров, разделенных точками с запятой.

DECLARE @tbl TABLE (
    po varchar(10),
    pii int)

INSERT INTO @tbl
SELECT 'A', 10
UNION ALL
SELECT 'A', 11
UNION ALL
SELECT 'A', 12
UNION ALL
SELECT 'B', 10
UNION ALL
SELECT 'B', 11
UNION ALL
SELECT 'C', 13

DECLARE @value varchar(100)
SET @value = '10;11;12;'
--SET @value = '11;10;'
--SET @value = '13;'
--SET @value = '10;'

SELECT DISTINCT po
FROM @tbl a
INNER JOIN fMultiValParam (@value) p ON
a.pii = p.paramid
WHERE a.po NOT IN (
    SELECT t.po
    FROM @tbl t
    LEFT OUTER JOIN (SELECT *
            FROM @tbl tt
            INNER JOIN fMultiValParam (@value) p ON
            tt.pii = p.paramid) tt ON
    t.pii = tt.pii
    AND t.po = tt.po
    WHERE tt.po IS NULL)

вот функция

CREATE    FUNCTION [dbo].[fMultiValParam]
(@Param varchar(5000))
RETURNS @tblParam TABLE (ParamID varchar(40))
AS
BEGIN

IF (@Param IS NULL OR LEN(@Param) < 2)
BEGIN
    RETURN
END

DECLARE @len INT
DECLARE @index INT
DECLARE @nextindex INT

SET @len = DATALENGTH(@Param)
SET @index = 0
SET @nextindex = 0

WHILE (@index < @len)
BEGIN
    SET @Nextindex = CHARINDEX(';', @Param, @index)

    INSERT INTO @tblParam
    SELECT SUBSTRING(@Param, @index, @nextindex - @index)

    SET @index = @nextindex + 1

END
RETURN
END

Попробуй это:

SELECT DISTINCT COALESCE ( offer, NULL )
FROM products
WHERE instance IN ( @instancelist )

Возможно ли, что покупатель запросит товар более одного раза?

Например: он / она просит пожертвовать 10,10,11,11,12?

Если это возможно, то такие решения, как

Выбрать ... из ... где пи в (10,10,11,11,12)

не будет работать.

Потому что «пи в (10,10,11,11,12)» то же самое, что «пи в (10,11,12)».

Решение для 10,10,11,11,12 - A&B.

Другие вопросы по теме