У меня есть проблема, которую я хотел бы решить с помощью 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 сам по себе не является действительным товарным предложением).
Предпосылки: Комбинация отправленных экземпляров продукта может привести только к одному конкретному сочетание продуктовых предложений, поэтому для каждого запроса есть только одно решение.
Таким образом, запрос 10,11,12 возвращает только A, а не B. Но если мы добавим D | 12 в ваш список продуктов, то получим ли мы A, D, а также B?
Кроме того, что, если мы добавим C | 11, а затем снова попросим 10,11,13? Получим ли мы снова B & C или не получим ни одного, поскольку ни 10, ни 13 не являются ЗП сами по себе?
Первый вопрос о платформе, мы можем ограничиться Oracle для этой проблемы.
Что касается вопросов Адамса, это было то, что я пытался объяснить в предварительных условиях, что это не входит в объем проблемы, как указано, поэтому не будет D 12, извините, если я не настолько хорош в объяснении. Это что-нибудь уточняет?
@ Патрик: Может быть? Сообщите мне, правильно ли это: либо (1) будет один PO, который точно содержит все запрошенные PI, либо (2) все PO будут непересекающимися наборами, объединение которых равно запрошенным PI. Это то, что вы собираетесь делать?
Проблема: как «10,11,13» может привести к «B, C», а «10,11,12» каким-то образом дисквалифицирует «B»? Если «12» дисквалифицирует элемент «B», потому что «B» не соответствует «12», то «13» в первом запросе ТАКЖЕ должно быть выполнено по той же причине. --- ПОЖАЛУЙСТА, объясните, что вы имели в виду под словами «12 не является действительным товарным предложением само по себе».
@Timothy - Похоже, вы можете использовать только один экземпляр продукта один раз. Итак, комбинация (10,11,12) (для A) и (10,11) (для B) недействительна, поскольку вы использовали (10,11) дважды. Это то, что делает его плохо подходящим для SQL.
@Mark: Да, я тоже об этом догадываюсь. На самом деле в моем последнем комментарии (1) - это просто частный случай (2), поскольку, если у вас есть только один набор, вам не о чем больше беспокоиться о том, что вы не пересекаетесь.
Я думаю, что у меня есть несколько лучшая формулировка вопроса: представьте, что есть этот магазин, который продает определенные товары. К сожалению, магазин не продает каждый продукт по отдельности, а группирует их в «предложения». Один продукт может быть в нескольких разных предложениях, а предложения могут содержать ...
... более одного продукта. Теперь, когда приходит заказчик, его не волнует эта система. Ему просто нужны продукты A, B и C. Работа магазина состоит в том, чтобы выбрать некоторые предложения для покупателя таким образом, чтобы покупатель получил ТОЧНО то, что он хочет. Не больше и не меньше.
Предположим, что клиенту не нужны несколько экземпляров одного и того же продукта.
Вопрос: Это классическая рекурсивная таблица, в которой предложение продукта имеет ключ, а предложение продукта может включать вложенные предложения продуктов, тогда как нижний уровень дерева (конечные узлы, если хотите) являются экземплярами продукта?
Например: Key, ParentKey, OfferingName.


У меня нет базы данных передо мной, но, в первую очередь, вы хотите, чтобы список заказов на поставку, у которых нет никаких 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, обычно выполняется быстрее (но я не тот, кто проголосовал против вас).
Это также найдет предложения продуктов, которые являются подмножествами поиска. Например, добавьте D | 13 к таблице, и D появится в результатах. Я думаю. У меня также нет передо мной db.
Это действительно кажется правильным. Из заявленной проблемы, если вы добавляете D | 13, а 13 находится в списке PI, тогда D должен быть возвращен.
Хорошо, на самом деле это неверно для его вопроса, он утверждает, что 10,11,12 должны приводить к A, а не A, B. Но опять же, этот вопрос сформулирован неоднозначно, я прокомментировал выше, чтобы запросить более подробную информацию.
ну какой-то псевдокод из моей головы здесь:
выберите из таблицы, где PI = 10 или pi = 11 и т. д.
сохранить результат во временной таблице
выберите отдельный заказ на поставку и количество (PI) из временной таблицы.
теперь для каждого заказа на покупку вы можете получить все доступные предложения PI. если количество доступных PI совпадает с количеством в таблице temp, это означает, что у вас есть все PI для этого заказа на поставку. добавьте все заказы на покупку, и вы получите результат.
ИМХО невозможно через чистый SQL без кода хранимой процедуры. Но я не уверен.
Добавлен: С другой стороны, я получаю представление о рекурсивном запросе (в MSSQL 2005 есть такая вещь, которая позволяет объединять запрос с его собственными результатами до тех пор, пока не будут возвращены строки), который может «собираться» правильные ответы путем объединения результатов предыдущего шага со всеми продуктами и последующей фильтрации недопустимых комбинаций. Однако вы получите все перестановки допустимых комбинаций, и это вряд ли будет эффективно. Идея довольно расплывчата, поэтому я не могу гарантировать, что она действительно может быть реализована.
То, что он на самом деле имеет в виду, называется общим табличным выражением или CTE.
Да, CTE. Это рекурсивный запрос, не так ли? :)
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))
Я проверил это на примере данных и не получил строк. Я не понимаю, что вы намеревались сделать во втором пункте.
Вам понадобится количество элементов в вашем списке, то есть @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, чтобы вы получали только одну строку для каждого заказа.
Это близко ... но «10,11,12» по-прежнему возвращает «A, B», что недопустимо.
Редактировать: Хотя я думаю, что мой работает нормально, ответ Адама, без сомнения, элегантнее и эффективнее - я просто оставлю свой здесь для потомков!
Прошу прощения, так как я знаю, что это было помечено как проблема 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)
)
);
Это дает только результаты, которые заполняют данный набор, которые не пересекаются со всеми другими результатами, которые я считать - это то, что вы просили. Для приведенных тестовых примеров:
Этот работает как положено, отличная работа! И это подтверждает мою теорию о том, что это был SQL-код. Я тебе пиво, если ты когда-нибудь попадешь в Стокгольм, Швеция
Я проверил это с 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.
Какая платформа базы данных? Оракул? SQL Server? MySQL? SQLite? ...