检查数据是否遵循计划订单

时间:2016-08-03 12:02:43

标签: sql sql-server sql-server-2012

所以我很难尝试解决这个问题。

我目前有这样的材料生产计划(每行都是一批):

SELECT
    Material, 
    Quantity,
    Range,
    OrderBy
FROM
    ProductionPlan
ORDER BY
    OrderBy

+----------+----------+-------+---------+
| Material | Quantity | Range | OrderBy |
+----------+----------+-------+---------+
| A        |      120 |     5 |       1 |
| B        |      120 |     5 |       2 |
| A        |      120 |     5 |       3 |
| C        |      120 |    10 |       4 |
| A        |      120 |     5 |       5 |
| A        |      120 |     5 |       6 |
+----------+----------+-------+---------+

我们的实际生产数据如下所示:

SELECT
    Material, 
    Quantity,
    BatchNm 
FROM
    ProducedMaterials
ORDER BY
    BatchNm 
+----------+----------+---------+
| Material | Quantity | BatchNm |
+----------+----------+---------+
| A        |      120 |     101 |
| B        |      113 |     102 |
| C        |      111 |     103 |
| A        |      353 |     104 |
+----------+----------+---------+

我需要知道的是,每个计划的材料是通过检查几件事来实现的,如果它应该显示好,否则显示不好:

  • 如果生产数量等于计划数量(+ -Range)

  • 按照计划生成了相同的订单。

当订单被破坏时:它一直在寻找生产的材料。 在示例中,他正在寻找C,但正确的顺序是A.它显示该行的BAD,如果材料匹配则查看下一行。在生产的材料匹配之前,它将继续显示计划数量的BAD;

所以我应该这样结束:

+----------+----------+-------+---------+--------+
| Material | Quantity | Range | OrderBy | Status |
+----------+----------+-------+---------+--------+
| A        |      120 |     5 |       1 |   GOOD | <-- Was produced first and quantity is within range
| B        |      120 |     5 |       2 |    BAD | <-- The Produced quantity(113) is not withing planned range
| A        |      120 |     5 |       4 |    BAD | <-- Bad because it didn't follow the plan (Suposed to be Material C)
| C        |      120 |    10 |       3 |   GOOD | <-- Good because it IS the next produced material AND it's quantity matched the planned quantity
| A        |      120 |     5 |       5 |   GOOD | <-- Good because it matches the next planned material AND the quantity is withing range(because the next row is the same material)
| A        |      120 |     5 |       6 |    BAD | <-- Bad because even thought the planned order is ok (Same material as the above row), the remaining quantity is above the quantity range (353 - 120(from the above row) = 233 "remaining" material)
+----------+----------+-------+---------+--------+

我很抱歉英语不好,但我很乐意帮助你。

P.S。我现在不在我的开发机器上。一旦我做到了,我将发布我正在处理的功能;

5 个答案:

答案 0 :(得分:1)

这就是你要追求的吗? 如果是这样,我会在答案中提供更多信息;只是想确保这个答案首先满足你的问题,但是如果你需要更多信息,可以在评论中同时提出问题。

declare @Orders table (
    OrderId bigint not null identity(1,1) primary key clustered
    , Material char(1) not null
    , Quantity int not null
    , TolerenceRange int not null
)
declare @MaterialsProduced table (
    Material char(1) not null 
    , QuantityProduced int not null
)

insert @Orders (Material, Quantity, TolerenceRange) 
values ('A',120,5)
, ('B',120,5)
, ('A',120,5)
, ('C',120,10)
, ('A',120,5)
, ('A',120,5)
/*
select * from @Orders

 Material | Quantity | TolerenceRange | OrderId |
+----------+----------+-------+-------+
| A        |      120 |     5 |     1 |
| B        |      120 |     5 |     2 |
| A        |      120 |     5 |     4 |
| C        |      120 |    10 |     3 |
| A        |      120 |     5 |     5 |
| A        |      120 |     5 |     6 |
+----------+----------+-------+-------+
*/
insert @MaterialsProduced (Material,QuantityProduced)
values ('A',150)
,('B',113)
,('C',111)
,('A',353)
/*
+----------+------------------+
| Material | QuantityProduced |
+----------+----------+-------+
| A        |      150 |
| B        |      113 |
| C        |      111 |
| A        |      353 |
*/

select o.Material, o.Quantity, o.TolerenceRange, o.OrderId
, case when o.Quantity - o.TolerenceRange <= p.TotalQuantityProduced  - sum(po.Quantity) then 'GOOD' else 'BAD' end [Status] --If we assume all previous orders are fully satisfied, can we satisfy this order based on minimum permitted amount
, case when o.Quantity - o.TolerenceRange <= p.TotalQuantityProduced  - sum(po.Quantity - po.TolerenceRange) then 'GOOD' else 'BAD' end [BestCaseStatus] --If we assume all previous orders take the minimum allowed amount, can we satisfy this order based on minimum permitted amount
, case when o.Quantity - o.TolerenceRange <= p.TotalQuantityProduced  - sum(po.Quantity + po.TolerenceRange) then 'GOOD' else 'BAD' end [WorstCaseStatus] --If we assume all previous orders take the maximum allowed amount, can we satisfy this order based on minimum permitted amount
from @Orders o
left outer join (select Material, sum(QuantityProduced) TotalQuantityProduced from @MaterialsProduced group by Material) p
    on p.Material = o.Material
left outer join @Orders po --previous orders
    on po.Material = o.Material
    and po.OrderId < o.OrderId
group by o.OrderId, o.Material, o.Quantity, o.TolerenceRange, p.TotalQuantityProduced 
order by o.OrderId

答案 1 :(得分:1)

根据您在第二个表格中包含OrderId的更新,请在下方找到更新的解决方案:

declare @Orders table (
    OrderId bigint not null identity(1,1) primary key clustered
    , Material char(1) not null
    , Quantity int not null
    , TolerenceRange int not null
)
declare @SatisfiedOrders  table (
    Material char(1) not null 
    , QuantityProduced int not null
    , RelatedOrderId bigint not null --foreign key references @Orders(OrderId) --if we weren't using table variables we could define a foreign key here
)

insert @Orders (Material, Quantity, TolerenceRange) 
values ('A',120,5)
, ('B',120,5)
, ('A',120,5)
, ('C',120,10)
, ('A',120,5)
, ('A',120,5)
insert @SatisfiedOrders (Material,QuantityProduced, RelatedOrderId)
values ('A',150, 1)
,('B',113, 2)
,('C',111, 3)
,('A',353, 4)

select o.Material, o.Quantity, o.TolerenceRange, o.OrderId
, case 
    when 
        o.Material = so.Material
        and so.QuantityProduced between o.Quantity - o.TolerenceRange and o.Quantity + o.TolerenceRange  
    then 'GOOD' 
    else 'BAD' 
  end [Status] 
, case 
    when so.RelatedOrderId is null then 'No Matching Order Found (Order #:' + cast(o.OrderId as nvarchar) + ')' 
    else '' 
        + case 
            when o.Material = so.Material then '' 
            else 'Different Materials (Ordered: ' + o.Material + '; Received: ' + so.Material + ').  ' 
          end
        + case 
            when so.QuantityProduced >= o.Quantity - o.TolerenceRange then '' 
            else 'Quantity below minimum (Ordered Min: ' + cast(o.Quantity - o.TolerenceRange as nvarchar) + '; Received: ' + cast(so.QuantityProduced as nvarchar) + ')' 
          end
        + case 
            when so.QuantityProduced <= o.Quantity + o.TolerenceRange then '' 
            else 'Quantity above maximum (Ordered Max: ' + cast(o.Quantity + o.TolerenceRange as nvarchar) + '; Received: ' + cast(so.QuantityProduced as nvarchar) + ')' 
          end
  end
  [Explanation] 
from @Orders o
left outer join @SatisfiedOrders so
on so.RelatedOrderId = o.OrderId
order by o.OrderId

答案 2 :(得分:1)

以下解决方案有点长,但希望它能解决问题。我没有在所有可能的场景中测试这种方法,因此代码中出错的可能性仍然很大。 查询将无法正确处理计划中从未在计划中生成的材料。例如,实际生产包括“D”但“D”不在计划中。

DECLARE @ProductionPlan TABLE 
(
     [Order] BIGINT NOT NULL IDENTITY(1,1) PRIMARY KEY CLUSTERED  -- Use identity column to define order of plan
    ,Material CHAR(1) not null
    ,Quantity INT not null
    ,TolerenceRange INT not null
)
DECLARE @ProducedMaterials TABLE 
(
     [Order] BIGINT NOT NULL IDENTITY(1,1) PRIMARY KEY CLUSTERED -- Use identity column to define order of actual production
    ,Material CHAR(1) not null 
    ,QuantityProduced INT not null
)

INSERT @ProductionPlan (Material, Quantity, TolerenceRange) 
VALUES    ('A',120,5)
    ,('B',120,5)
    ,('A',120,5)
    ,('C',120,10)
    ,('A',120,5)
    ,('A',120,5)
    ,('A',120,5);


INSERT @ProducedMaterials (Material,QuantityProduced)
VALUES     ('A',120)
    ,('B',113)
    ,('C',111)
    ,('A',353);


DECLARE @planEntriesCount INT = (SELECT COUNT([ORDER]) FROM @ProductionPlan);
DECLARE @productionEntriesCount INT = (SELECT COUNT([ORDER]) FROM @ProducedMaterials);
DECLARE @maximumDistance INT = ABS(@planEntriesCount - @productionEntriesCount);

WITH CTE_PlanAndActual
AS
(
    -- Join actual production with scheduled production on Material type only. For each possible 
    -- combination the query will calculate the “Distance” in order of execution between the actual 
    -- production step and the planned production steps. 

    SELECT   [Plan].[Order] AS [PlanedOrder]
            ,ISNULL([Actual].[Order], -1000) AS [ActualOrder]
            ,[Plan].[Material]
            ,ISNULL(ABS([Plan].[Order] - [Actual].[Order]), -10000000000) AS [Distance] -- Distance between actual production order and planned production order.  
            ,[Plan].[Quantity] AS [PlannedQuantity]
            ,ISNULL([Actual].[QuantityProduced], 0) AS [ActualQuantity]
            ,[Plan].[TolerenceRange]
    FROM    @ProductionPlan [Plan]
    LEFT OUTER JOIN @ProducedMaterials [Actual] ON [Actual].[Material] = [Plan].[Material]
)
,CTE_PlanAndActualBestMatch
AS
(
    --Next step we will use windowing function to determine the minimum distance between planned production step and actual production step.  
    --This will help us determine the best match with the information we have thus far.

    SELECT   [PlanedOrder]
            ,[ActualOrder]
            ,[Material]
            ,[Distance]
            ,MIN([Distance]) OVER (PARTITION BY [PlanedOrder]) AS [MinDistance]
            ,[PlannedQuantity]
            ,[ActualQuantity]
            ,[TolerenceRange]
    FROM    CTE_PlanAndActual
)
,CTE_PlanAndActualOrderValidated
AS
(
    -- Next eliminate records which does not meet the minimum distance criteria for each planned production step.
    -- Now that we have only the records that matches the minimum distance criteria we need to determine if any 
    -- of the actual production execution steps was out of order. We will use the LEAD windowing function to determine this.
    SELECT   [PlanedOrder]
            ,(
                -- If one or more step is out of order, then it means the production plan was not followed. Simply set the Actual order value for the record to null.
                CASE 
                    WHEN [ActualOrder] >  LEAD([ActualOrder], 1, [ActualOrder]) OVER (ORDER BY [PlanedOrder]) THEN NULL
                    WHEN ( ([PlanedOrder] = 1) AND ([ActualOrder] <> 1) ) THEN NULL
                    WHEN ( [Distance] > @maximumDistance) THEN NULL
                    ELSE [ActualOrder]
                END
             ) [ActualOrder]
            ,[Material]
            ,[Distance]
            ,[PlannedQuantity]
            ,[ActualQuantity]
            ,[TolerenceRange]
    FROM    CTE_PlanAndActualBestMatch
    WHERE   [MinDistance] = [Distance]  -- Eliminate records that is not the minimum distance between plan and actual.
)
,CTE_PlanAndActualWithRepeats
AS
(
    -- Next determine repeated planned orders this will be needed to correctly determine if the 
    -- production quantiles were within in planned tolerance range.
    -- Also calculate the Cumulative Planed Quantity for planned entries that repeat, this will 
    -- be needed to determine if repeated production entries are within tolerance range.

    SELECT   [PlanedOrder]
            ,[ActualOrder]
            ,[Material]
            ,[Distance]
            ,[PlannedQuantity]
            ,IIF([ActualOrder] IS NULL,  NULL, [ActualQuantity]) AS [ActualQuantity]
            ,[TolerenceRange]
            ,IIF([ActualOrder] IS NULL,  0, 1) AS PlanFollowed
            ,COUNT([PlanedOrder]) OVER (PARTITION BY [ActualOrder]) AS RepeatCount
            ,ROW_NUMBER() OVER (PARTITION BY [ActualOrder] ORDER BY [PlanedOrder]) AS RepeatIndex
            ,SUM([PlannedQuantity]) OVER (PARTITION BY [ActualOrder] ORDER BY [PlanedOrder]) AS [CumulativePlanedQuantity]
    FROM    CTE_PlanAndActualOrderValidated

)
,CTE_PlanAndEffectiveProduction
AS
(

    -- Calculate the effective production. In the event that production plan entry repeats the 
    -- effective production will use, the final effective production value will be calculated 
    -- from total actual production and cumulative planned production.
    SELECT   [PlanedOrder]
            ,[ActualOrder]
            ,[Material]
            ,[Distance]
            ,[PlannedQuantity]
            ,[ActualQuantity]
            ,[TolerenceRange]
            ,[PlanFollowed]
            ,[RepeatCount]
            ,[RepeatIndex]
            ,[CumulativePlanedQuantity]
            ,(
                CASE
                    WHEN ([RepeatIndex] < [RepeatCount]) AND ([CumulativePlanedQuantity] < [ActualQuantity]) THEN [PlannedQuantity]
                    WHEN ([RepeatIndex] > 1) AND ([RepeatIndex] = [RepeatCount]) THEN [ActualQuantity] - ([CumulativePlanedQuantity] - [PlannedQuantity])
                    ELSE [ActualQuantity]
                END
             ) AS EffectiveQuantity
    FROM    CTE_PlanAndActualWithRepeats
)
-- Finally determine status
SELECT   [PlanedOrder]
        ,[ActualOrder]
        ,[Material]
        ,[Distance]
        ,[PlannedQuantity]
        ,[ActualQuantity]
        ,[TolerenceRange]
        ,[PlanFollowed]
        ,[RepeatCount]
        ,[RepeatIndex]
        ,[CumulativePlanedQuantity]
        ,[EffectiveQuantity]
        ,(
            CASE 
                WHEN ([PlanFollowed] = 1) AND (ABS([EffectiveQuantity] - [PlannedQuantity]) <= [TolerenceRange]) THEN 'Good'
                WHEN ([PlanFollowed] = 1) AND (ABS([EffectiveQuantity] - [PlannedQuantity]) > [TolerenceRange]) THEN 'Bad - Out of Range' 
                WHEN ([PlanFollowed] = 0) THEN 'Bad - Plan Not Followed'
                ELSE 'Bad'
            END 
        ) [Status]
FROM    CTE_PlanAndEffectiveProduction
ORDER BY [PlanedOrder]

答案 3 :(得分:1)

这是我的回答。

我假设每个新的生产批次都会将指向Orders的指针移动到某个新位置,这样所有超过此指针的订单都会变为非活动状态(而不是“OPENED”),而这只是决定它们是好还是坏的时间。 /> 然后生成的数量在OPENED订单之间分配。在这里,我可以想象应用不同的容差逻辑。下面的脚本为订单分配最小可能的数量,试图尽可能多地完成订单。

所以这是生产批次的循环。 另请注意,我稍微更改了源数据。

declare @Orders table (
      OrderId bigint not null identity(1,1) primary key clustered, 
      Material char(1) not null, 
      Quantity int not NULL, 
      ToleranceRange int not NULL,
      --
      QtyProduced int not NULL default 0,
      [Status] varchar(100) not NULL  default 'OPENED'
);
declare @MaterialsProduced table (
    Material char(1) not null ,
    QuantityProduced int not NULL,
    BatchNm int not NULL
);

insert @Orders (Material, Quantity, ToleranceRange) 
values 
  ('A',120,5)
, ('B',120,5)
, ('A',120,5)
, ('A',120,5) --added this row
, ('C',120,10)
, ('A',120,5)
, ('A',120,5)
, ('C',120,5) --added this row
insert @MaterialsProduced (Material,QuantityProduced, BatchNm)
values ('A',250,101 ) -- changed qty
,('B',113,102)
,('C',111,103)
,('A',353,104)

DECLARE @PMaterial CHAR(1);
DECLARE @PQuantity INT;

DECLARE prod CURSOR 
FORWARD_ONLY
FOR
    SELECT Material,QuantityProduced FROM @MaterialsProduced ORDER BY BatchNm;
OPEN prod;
FETCH NEXT FROM prod INTO @PMaterial,@PQuantity;
WHILE (@@FETCH_STATUS = 0)
BEGIN
    -- 1. Find first OPENED order for @PMaterial
    DECLARE @pointer int = 0;
    SELECT @pointer=min(OrderId)
    FROM @Orders 
    WHERE Material = @PMaterial AND [Status]='OPENED';
    -- SELECT @pointer;

    -- 2. Close all orders above @pointer
    UPDATE @Orders
    SET [Status]= CASE WHEN QtyProduced < Quantity - ToleranceRange THEN 'BAD - behind the pointer' ELSE 'GOOD' END
    WHERE OrderId < @pointer AND [Status]='OPENED';

    -- 3. Distribute @PQuantity among first OPENED orders of @PMaterial type.
    -- May need to adjust tolerance logic
    UPDATE o1
    SET QtyProduced = QtyProduced + CASE WHEN deficit <= @PQuantity 
        THEN Quantity - ToleranceRange - QtyProduced 
        ELSE CASE WHEN deficit - (Quantity - ToleranceRange - QtyProduced) <= @PQuantity
            THEN @PQuantity - (deficit  - (Quantity - ToleranceRange - QtyProduced))
            ELSE 0 END
        END
        ,[Status] = CASE WHEN deficit <= @PQuantity 
            THEN CASE WHEN
                  -- if this is the last order available check for superflow
                  OrderId = (SELECT Max(OrderId) FROM @orders WHERE Material= @PMaterial)
                  AND @PQuantity - deficit > 2*ToleranceRange
                THEN 'BAD - superflow' 
                ELSE 'GOOD' END
            ELSE 'OPENED' END
    FROM @Orders o1
    CROSS APPLY (
        SELECT deficit = sum(o2.Quantity - o2.ToleranceRange - o2.QtyProduced)
        FROM @Orders o2
        WHERE o2.OrderId BETWEEN @pointer AND o1.OrderId 
            AND o2.Material = @PMaterial AND o2.[Status]='OPENED'
        ) t
    WHERE OrderId >= @pointer AND Material = @PMaterial AND [Status]='OPENED'; 

    FETCH NEXT FROM prod INTO @PMaterial,@PQuantity;
END
CLOSE prod;
DEALLOCATE prod;
-- Still OPENED orders are really BAD
UPDATE @Orders 
SET [Status] ='BAD - finally'
WHERE [Status] ='OPENED';

SELECT * FROM @Orders
ORDER BY OrderId;

结果是

OrderId Material    Quantity    ToleranceRange  QtyProduced Status
1   A   120 5   115 GOOD
2   B   120 5   113 BAD - behind the pointer
3   A   120 5   115 GOOD
4   A   120 5   20  BAD - behind the pointer
5   C   120 10  110 GOOD
6   A   120 5   115 GOOD
7   A   120 5   115 BAD - superflow
8   C   120 5   1   BAD - finally

编辑

为了采取规则

  

“订单”在生产的材料发生变化之前是开放的,除非有   更多是相同的。因此,如果我在batchNm 101上制作了250个材料'A',   但计划120'A'并且在计划120'B'之后,'A'会   是一个不好的 - 超流

考虑使用

替换上述脚本中的3d步骤
-- 3. Distribute @PQuantity among first sequential orders of @PMaterial type
-- may need to adjust tolerance logic
-- 3.1. Only continous sequence of the same material orders are allowed
DECLARE @lastInSeq int = 0;
SELECT @lastInSeq = isnull(min(OrderId)-1, @pointer)
FROM @Orders 
WHERE Material <> @PMaterial AND OrderId > @pointer;

UPDATE o1
SET QtyProduced = QtyProduced + CASE WHEN deficit <= @PQuantity 
    THEN Quantity - ToleranceRange - QtyProduced 
    ELSE CASE WHEN deficit - (Quantity - ToleranceRange - QtyProduced) <= @PQuantity
        THEN @PQuantity - (deficit  - (Quantity - ToleranceRange - QtyProduced))
        ELSE 0 END
    END
    ,[Status] = CASE WHEN deficit <= @PQuantity 
        THEN CASE WHEN
              -- if this is the last order available check for superflow
              OrderId = @lastInSeq
              AND @PQuantity - deficit > 2*ToleranceRange
            THEN 'BAD - superflow' -- superlow
            ELSE 'GOOD' END
        ELSE 'OPENED' END
FROM @Orders o1
CROSS APPLY (
    SELECT deficit = sum(o2.Quantity - o2.ToleranceRange - o2.QtyProduced)
    FROM @Orders o2
    WHERE o2.OrderId BETWEEN @pointer AND o1.OrderId 
        AND o2.Material = @PMaterial AND o2.[Status]='OPENED'
    ) t
WHERE OrderId BETWEEN @pointer AND @lastInSeq; 

此版本结果为

1   A   120 5   115 BAD - superflow
2   B   120 5   113 BAD - behind the pointer
3   A   120 5   0   BAD - behind the pointer
4   A   120 5   0   BAD - behind the pointer
5   C   120 10  110 GOOD
6   A   120 5   115 GOOD
7   A   120 5   115 BAD - superflow
8   C   120 5   0   BAD - finally

答案 4 :(得分:0)

LOOP是这个问题的最佳选择。

declare @Orders table (
      OrderId bigint not null identity(1,1) primary key clustered, 
      Material char(1) not null, 
      Quantity int not NULL, 
      TolerenceRange int not NULL
)
declare @MaterialsProduced table (
    Material char(1) not null 
    , QuantityProduced int not NULL,
    OrderId int 
)

insert @Orders (Material, Quantity, TolerenceRange) 
values 
  ('A',120,5)
, ('B',120,5)
, ('A',120,5)
, ('C',120,10)
, ('A',120,5)
, ('A',120,5)
insert @MaterialsProduced (Material,QuantityProduced, OrderId)
values ('A',120,1 )
,('B',113, 2)
,('C',111,3 )
,('A',353, 4)

declare @result table (
    OrderId bigint, 
    Material char(1) not null, 
    Quantity int not NULL, 
    TolerenceRange int not NULL,
    Status NVARCHAR(10)
)

DECLARE @TempMaterialsProduced TABLE (OrderId INT)
DECLARE @Counter INT = 1

WHILE (@Counter <= (SELECT COUNT(1) FROM @Orders))
BEGIN

    IF EXISTS 
    (       
        SELECT * FROM @Orders O
        WHERE 
            O.OrderId = @Counter AND
            O.Material = (
                            SELECT TOP 1 M.Material FROM @MaterialsProduced M
                            WHERE
                                M.OrderId NOT IN (SELECT T.OrderId FROM @TempMaterialsProduced T)
                            ORDER BY M.OrderId
                         )            
    )
        BEGIN
            INSERT INTO @TempMaterialsProduced
            SELECT TOP 1 M.OrderId FROM @MaterialsProduced M
            WHERE
                M.OrderId NOT IN (SELECT T.OrderId FROM @TempMaterialsProduced T)
            ORDER BY M.OrderId

            --

            INSERT INTO @result
            SELECT 
                O.OrderId ,
                O.Material ,
                O.Quantity ,
                O.TolerenceRange, 
                CASE
                    WHEN
                        M.QuantityProduced >= O.Quantity - O.TolerenceRange THEN 'GOOD'                     
                    ELSE 'BAD' END [Status]
            FROM 
                @Orders O INNER JOIN 
                @MaterialsProduced M ON M.OrderId = (SELECT MAX(T.OrderId) FROM @TempMaterialsProduced T)
            WHERE 
                O.OrderId = @Counter

        END
    ELSE
        BEGIN

            INSERT INTO @result
            SELECT *, 'BAD' FROM @Orders O
            WHERE 
                O.OrderId = @Counter
        END

    SET @Counter += 1;    
END

SELECT * FROM @result

输出:

OrderId Material    Quantity    TolerenceRange  Status
1       A           120         5               GOOD
2       B           120         5               BAD
3       A           120         5               BAD
4       C           120         10              GOOD
5       A           120         5               GOOD
6       A           120         5               BAD