父母数基于多个孩子的配对

时间:2018-06-28 13:00:43

标签: sql sql-server parent-child cross-apply outer-apply

在下面的示例中,我试图根据我在每个酒吧位置提供的食材可用性来计算可以饮用的饮料数量。

进一步澄清,如以下示例所示:根据下表中突出显示的数字;我知道我只能在2018年6月30日制作1玛格丽塔酒(如果我将补给品运送到该地点,则可以在DC或FL中制造)。

数据表样本

Sample chart of inventory by location group by drink

请使用以下代码在上方输入相关数据:

    CREATE TABLE #drinks 
    (
        a_date      DATE,
        loc         NVARCHAR(2),
        parent      NVARCHAR(20),
        line_num    INT,
        child       NVARCHAR(20),
        avail_amt   INT
    );

INSERT INTO #drinks VALUES ('6/26/2018','CA','Long Island','1','Vodka','7');
INSERT INTO #drinks VALUES ('6/27/2018','CA','Long Island','2','Gin','5');
INSERT INTO #drinks VALUES ('6/28/2018','CA','Long Island','3','Rum','26');
INSERT INTO #drinks VALUES ('6/26/2018','DC','Long Island','1','Vodka','15');
INSERT INTO #drinks VALUES ('6/27/2018','DC','Long Island','2','Gin','18');
INSERT INTO #drinks VALUES ('6/28/2018','DC','Long Island','3','Rum','5');
INSERT INTO #drinks VALUES ('6/26/2018','FL','Long Island','1','Vodka','34');
INSERT INTO #drinks VALUES ('6/27/2018','FL','Long Island','2','Gin','14');
INSERT INTO #drinks VALUES ('6/28/2018','FL','Long Island','3','Rum','4');
INSERT INTO #drinks VALUES ('6/30/2018','DC','Margarita','1','Tequila','6');
INSERT INTO #drinks VALUES ('7/1/2018','DC','Margarita','2','Triple Sec','3');
INSERT INTO #drinks VALUES ('6/29/2018','FL','Margarita','1','Tequila','1');
INSERT INTO #drinks VALUES ('6/30/2018','FL','Margarita','2','Triple Sec','0');
INSERT INTO #drinks VALUES ('7/2/2018','CA','Cuba Libre','1','Rum','1');
INSERT INTO #drinks VALUES ('7/8/2018','CA','Cuba Libre','2','Coke','5');
INSERT INTO #drinks VALUES ('7/13/2018','CA','Cuba Libre','3','Lime','14');
INSERT INTO #drinks VALUES ('7/5/2018','DC','Cuba Libre','1','Rum','0');
INSERT INTO #drinks VALUES ('7/19/2018','DC','Cuba Libre','2','Coke','12');
INSERT INTO #drinks VALUES ('7/31/2018','DC','Cuba Libre','3','Lime','9');
INSERT INTO #drinks VALUES ('7/2/2018','FL','Cuba Libre','1','Rum','1');
INSERT INTO #drinks VALUES ('7/19/2018','FL','Cuba Libre','2','Coke','3');
INSERT INTO #drinks VALUES ('7/17/2018','FL','Cuba Libre','3','Lime','2');
INSERT INTO #drinks VALUES ('6/30/2018','DC','Long Island','3','Rum','4');
INSERT INTO #drinks VALUES ('7/7/2018','FL','Cosmopolitan','5','Triple Sec','7');

预期结果如下:

![Expected Results Desired

请注意,如预期结果所示,孩子可以互换。例如,2018年7月7日,Triple Sec来到国际大都会;但是,由于孩子也是朗姆酒,因此改变了玛格丽塔酒在佛罗里达州的供应。

也不会在06/30和06/31上对Cuba Libre的DC地区进行更新。

请注意,零件是可互换的,而且每次有新物品到达时,它现在都可以提供以前的任何物品。

最后-如果我可以添加另一列显示套件可用性的栏真是太棒了,而无论其位置在哪里,仅根据孩子的可用性而定。对于前如果DC中有一个3号孩子,而FL中没有一个孩子,那么FL可以根据其他位置的库存来假设他们有足够的库存来制作饮料!

4 个答案:

答案 0 :(得分:1)

我创建了几个额外的表来帮助编写查询,但是如果需要,可以从#drinks表中生成这些表:

CREATE TABLE #recipes 
(
    parent      NVARCHAR(20),
    child       NVARCHAR(20)
);

INSERT INTO #recipes VALUES ('Long Island', 'Vodka');
INSERT INTO #recipes VALUES ('Long Island', 'Gin');
INSERT INTO #recipes VALUES ('Long Island', 'Rum');
INSERT INTO #recipes VALUES ('Maragrita', 'Tequila');
INSERT INTO #recipes VALUES ('Maragrita', 'Triple Sec');
INSERT INTO #recipes VALUES ('Cuba Libre', 'Coke');
INSERT INTO #recipes VALUES ('Cuba Libre', 'Rum');
INSERT INTO #recipes VALUES ('Cuba Libre', 'Lime');
INSERT INTO #recipes VALUES ('Cosmopolitan', 'Cranberry Juice');
INSERT INTO #recipes VALUES ('Cosmopolitan', 'Triple Sec');

CREATE TABLE #locations 
(
    loc      NVARCHAR(20)
);

INSERT INTO #locations VALUES ('CA');
INSERT INTO #locations VALUES ('FL');
INSERT INTO #locations VALUES ('DC');

查询将变为:

DECLARE @StartDateTime DATETIME
DECLARE @EndDateTime DATETIME

SET @StartDateTime = '2018-06-26'
SET @EndDateTime = '2018-07-31';

--First, build a range of dates that the report has to run for
WITH DateRange(a_date) AS 
(
    SELECT @StartDateTime AS DATE
    UNION ALL
    SELECT DATEADD(d, 1, a_date)
    FROM   DateRange 
    WHERE  a_date < @EndDateTime
)
SELECT a_date, parent, loc, avail_amt
FROM   (--available_recipes_inventory
        SELECT a_date, parent, loc, avail_amt,
               LAG(avail_amt, 1, 0) OVER (PARTITION BY loc, parent ORDER BY a_date) AS previous_avail_amt
        FROM   (--recipes_inventory
                SELECT a_date, parent, loc, 
                       --The least amount of the ingredients for a recipe is the most 
                       --amount of drinks we can make for it
                       MIN(avail_amt) as avail_amt
                FROM   (--ingredients_inventory
                        SELECT dr.a_date, r.parent, r.child, l.loc, 
                               --Default ingredients we don't have with a zero amount
                               ISNULL(d.avail_amt, 0) as avail_amt
                        FROM   DateRange dr CROSS JOIN
                               #recipes r CROSS JOIN
                               #locations l OUTER APPLY
                               (
                                --Find the total amount available for each 
                                --ingredient at each location for each date
                                SELECT SUM(d1.avail_amt) as avail_amt
                                FROM   #drinks d1
                                WHERE  d1.a_date <= dr.a_date
                                AND    d1.loc = l.loc
                                AND    d1.child = r.child
                               ) d
                        ) AS ingredients_inventory
                GROUP BY a_date, parent, loc
               ) AS recipes_inventory
        --Remove all recipes that we don't have enough ingredients for
        WHERE  avail_amt > 0 
       ) AS available_recipes_inventory
--Selects the first time a recipe has enough ingredients to be made
WHERE  previous_avail_amt = 0 
--Selects when the amount of ingredients has changed
OR     previous_avail_amt != avail_amt 
ORDER BY a_date
--MAXRECURSION needed to generate the date range
OPTION (MAXRECURSION 0)
GO

最里面的SELECT创建一个伪库存表(ingredients_inventory),该表由位置,成分,日期和可用数量组成。如果某个地点在特定日期没有某种成分,那么将使用零。

下一个SELECT查询将发现每个位置/日期可以制作多少个配方(同样可能为零)。

下一个SELECT查询是一个中间表,该表用于收集前一天每个位置可以制作多少个每种食谱(同时还删除了所有不能制作的饮料)。

最后,最外层的SELECT查询使用前一天的数据来查找每种特定配方的制作量已更改。

此查询与您的表产生的数字略有不同,但是我认为这是因为您的错误吗?以佛罗里达州为例,额外的朗姆酒会在7月2日进驻,因此可以制造的长岛数量达到5个。到19日,便可以制造2个古巴图书馆。

结果:

+------------+-------------+-----+-----------+
| a_date     | parent      | loc | avail_amt |
+------------+-------------+-----+-----------+
| 2018-06-28 | Long Island | DC  | 5         |
| 2018-06-28 | Long Island | CA  | 5         |
| 2018-06-28 | Long Island | FL  | 4         |
| 2018-06-30 | Long Island | DC  | 9         |
| 2018-07-01 | Maragrita   | DC  | 3         |
| 2018-07-02 | Long Island | FL  | 5         |
| 2018-07-07 | Maragrita   | FL  | 1         |
| 2018-07-13 | Cuba Libre  | CA  | 5         |
| 2018-07-19 | Cuba Libre  | FL  | 2         |
| 2018-07-31 | Cuba Libre  | DC  | 9         |
+------------+-------------+-----+-----------+

答案 1 :(得分:0)

我认为这将提供所需的结果。

创建了一个将获取清单的功能。

Create function GetInventoryByDateAndLocation
(@date DATE, @Loc NVARCHAR(2))
RETURNS TABLE
AS
RETURN
(
Select child,avail_amt from 
    (Select a_date, child,avail_amt, 
     ROW_NUMBER() over (partition by child order by a_date desc) as ranking 
     from drinks where loc = @Loc and a_date<=@date)c 
where ranking = 1
)

然后查询:

with parentChild as 
(Select distinct parent, line_num, child from drinks),
ParentChildNo as
(Select parent, max(line_num) as ChildNo from parentChild group by parent)
,Inventory as
(Select a_date,loc,s.* from drinks d cross apply
GetInventoryByDateAndLocation(d.a_date, d.loc)s)
, Available as
(Select a_date,parent,loc,count(*) as childAvailable,min(avail_amt) as quantity 
from Inventory i 
join parentChild c 
on i.child = c.child 
group by parent,loc,a_date)
Select a_date,a.parent,loc,quantity from available a 
join ParentChildNo pc 
on a.parent = pc.parent and a.childAvailable = pc.ChildNo 
where quantity > 0 order by 1

这将提供所有可以从库存中制成的饮料。希望它能解决您的问题。

这些只是我的2美分。有更好的方法可以做到这一点,我希望更多的人会阅读并提出更好的建议。

答案 2 :(得分:0)

不要以为这正是您要找的东西……也许会有所帮助。

WITH cteFiles AS (SELECT 'File_20170902_Name.txt' AS FILENAME FROM DUAL UNION ALL
                  SELECT 'File200_Name_20170902_1.txt' AS FILENAME FROM DUAL UNION ALL
                  SELECT 'File400_20170902_Name_1.txt' AS FILENAME FROM DUAL UNION ALL
                  SELECT 'File1_name_20170902.txt' AS FILENAME FROM DUAL)
SELECT FILENAME, REPLACE(REPLACE(REGEXP_SUBSTR(FILENAME, '_[0-9]+[_.]'), '_', NULL), '.', NULL) AS FINAL_NUMS
  FROM cteFiles;

答案 3 :(得分:0)

SELECT ( SELECT MAX(d2.a_date) FROM #drinks AS d2 WHERE d2.parent = d.parent AND d2.loc = d.loc) AS a_date ,d.loc ,d.parent ,SUM(d.avail_amt) AS [avail_amt(SUM)] ,COUNT(d.avail_amt) AS [avail_amt(COUNT)] FROM #drinks AS d GROUP BY d.loc ,d.parent ORDER BY a_date