Рекурсивная проблема SQL - PullRequest
       26

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

7 голосов
/ 18 декабря 2008

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

Проблема:

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

PO | PI

-----

A | 10

A | 11

A | 12

B | 10

B | 11

C | 13

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

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

Ответы [ 11 ]

7 голосов
/ 18 декабря 2008

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

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
2 голосов
/ 18 декабря 2008

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

Извиняюсь, так как я знаю, что это было помечено как проблема 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, но можно просто указать внутренний запрос как две производные таблицы. Перекрестное соединение во внешнем запросе позволяет нам находить комбинации предложений, которые имеют все допустимые позиции. Я знаю, что это будет работать только на основании утверждения в вопросе о том, что данные таковы, что для каждого запрошенного набора существует только 1 действительная комбинация, без чего это еще сложнее, поскольку подсчета недостаточно для удаления комбинаций, в которых есть дубликаты их.

1 голос
/ 19 декабря 2008

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

Например: он / она просит пожертвование на 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.

1 голос
/ 18 декабря 2008
  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))                 
1 голос
/ 18 декабря 2008

У меня нет БД передо мной, но в верхней части моей головы вы хотите список PO, у которых нет 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 (что научит меня торопиться), так что это еще не рабочий код.

0 голосов
/ 19 декабря 2008

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

SELECT DISTINCT COALESCE ( offer, NULL )
FROM products
WHERE instance IN ( @instancelist )
0 голосов
/ 18 декабря 2008

Я проверил это при 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
0 голосов
/ 18 декабря 2008

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

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

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, поэтому ваш пробег может отличаться

0 голосов
/ 18 декабря 2008

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

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

0 голосов
/ 18 декабря 2008

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...