MySQL存在多个案例声明

时间:2018-02-06 17:11:56

标签: mysql sql database

我有两张桌子。我们称之为:SEATS和SEAT_ALLOCATION_RULE表。

以下是表格架构:

CREATE TABLE IF NOT EXISTS `SEATS` (
  `SeatID` int(11) NOT NULL AUTO_INCREMENT,
  `SeatName` varchar(255) NOT NULL DEFAULT '',
   PRIMARY KEY (`SeatID`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=9 ;

INSERT INTO `SEATS` (`SeatID`, `SeatName`) VALUES
(1, 'Super VIP'),
(2, 'VIP'),
(3, 'Business'),
(4, 'Economy'),
(5, 'Standing');

CREATE TABLE IF NOT EXISTS `SEAT_ALLOCATION_RULE` (
 `SeatID` int(11) NOT NULL DEFAULT '0',
 `Origin` varchar(50) NOT NULL DEFAULT '0',
 `Destination` varchar(50) NOT NULL DEFAULT '',
 `Passenger_Type` varchar(25) NOT NULL DEFAULT '',
  PRIMARY KEY (`SeatID`,`Origin`,`Destination`,`Passenger_Type`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

INSERT INTO `SEAT_ALLOCATION_RULE` (`SeatID`, `Origin`, `Destination, `Passenger_Type`) VALUES
(1, 'Malaysia','',''),
(2, 'Malaysia','Singapore',''),
(3, 'Malaysia','Singapore','Senior_Citizen'),
(4, 'Bangkok','Japan','Student'),
(5, 'Cambodia','China','Senior_Citizen');

SEAT_ALLOCATION_RULE表根据以下优先顺序确定乘客应分配到哪个座位:

1. Origin, destination, and  passenger_type match
2. Origin and destination match
3. Origin match

这意味着如果所有字段(origin,destination和passenger_type)匹配,则它应该比仅仅两个字段匹配时具有更高的优先级,依此类推。如果列为空,则将其视为未指定,因此具有较低的优先级。所以,例如:

  • 如果Origin是Malaysia,Destination是Singapore,Passenger_Type是Senior_Citizen,则应该返回seatID 3
  • 如果Origin是Malaysia,Destination是Singapore,Passenger_Type是Student,则应返回seatID 2(因为它只匹配Origin和Destination)
  • 如果Origin为Malaysia,Destination为US,Passenger_Type为Student,则应返回seatID 1(因为它只与Origin匹配)。

现在,根据上述规则,如果来源是马来西亚,目的地是新加坡,而Passenger_Type是学生,则返回seatID的查询如下:

SELECT s.SeatID, s.SeatName
FROM SEATS s
WHERE
CASE WHEN EXISTS(
  select 1
  from SEAT_ALLOCATION_RULE r
  where s.SeatID = r.SeatID
  AND r.Origin = 'Malaysia'
  AND r.Destination = 'Singapore'
  AND r.Passenger_Type='Student') Then 1
WHEN EXISTS(
  select 1
  from SEAT_ALLOCATION_RULE r
  where s.SeatID = r.SeatID
  AND r.Origin = 'Malaysia'
  AND r.Destination = 'Singapore'
  AND r.Passenger_Type='') Then 1
WHEN EXISTS(
  select 1
  from SEAT_ALLOCATION_RULE r
  where s.SeatID = r.SeatID
  AND r.Origin = 'Malaysia'
  AND r.Destination = ''
  AND r.Passenger_Type='') Then 1 END

但是,上面的查询不起作用,因为它将返回seatID 1和2,但预期的输出只是seatID 2(因为起始和目标匹配,它需要更高的优先级)。有人可以帮助纠正我的SQL查询吗?

2 个答案:

答案 0 :(得分:0)

这修复了现有的SQL:

SELECT DISTINCT s.SeatID, s.SeatName
FROM SEATS s
LEFT JOIN SEAT_ALLOCATION_RULE r  ON r.SeatID = s.SeatID
    AND r.Origin = 'Malaysia' 
    AND ( 
       (r.Destination = 'Singapore' AND r.Passenger_Type IN ('Student', ''))
       OR 
       (r.Destination = '' AND r.Passenger_Type = '')
    )
WHERE r.SeatID IS NOT NULL

但它只是一个部分解决方案,它是你真正想要仅根据数据应用的手工编码逻辑。

完整的解决方案将使用假设输入来为您的乘客的机票信息生成所有符合条件的座位。这是横向连接/应用的一个很好的用途,MySql遗憾地缺少它们(它们的所有主要竞争对手都有至少两个发布周期,以及当前MySql版本中缺少的其他宝石,如窗口函数,ctes ,完全加入...我可以继续)。以下是我在Sql Server中的使用方法:

SELECT p.PassengerID, s.SeatID, s.SeatName
FROM Passenger p
CROSS APPLY (
    SELECT TOP 1 r.SeatID
    FROM SEAT_ALLOCATION_RULE r
    WHERE COALESCE(NULLIF(r.Origin, ''),p.Origin) = p.Origin
        AND COALESCE(NULLIF(r.Destination,''), p.Destination) = p.Destination
        AND COALESCE(NULLIF(r.Passenger_Type,''),p.Passenger_Type) = p.Passenger_Type
    ORDER BY 
        CASE WHEN r.Origin <> '' THEN 1 ELSE 0 END
      + CASE WHEN r.Destination <> '' THEN 1 ELSE 0 END
      + CASE WHEN r.Passenger_Type <> '' THEN 1 ELSE 0 END DESC
) r
INNER JOIN SEATS s ON s.SeatID = r.SeatID
WHERE p.PassengerID = /* passenger criteria here */

我知道Sql Server解决方案对您没有多大帮助,但也许它会建议一个更好的MySql解决方案。

如果没有APPLY,我知道这样做的唯一方法是首先计算乘客的MAX()匹配计数(规则的多少部分匹配):

SELECT p.PassengerID, 
   MAX(CASE WHEN r.Origin <> '' THEN 1 ELSE 0 END
      + CASE WHEN r.Destination <> '' THEN 1 ELSE 0 END
      + CASE WHEN r.Passenger_Type <> '' THEN 1 ELSE 0 END) AS MatchCount
FROM Passenger p
INNER JOIN SEAT_ALLOCATION_RULE r ON COALESCE(NULLIF(r.Origin, ''),p.Origin) = p.Origin
    AND COALESCE(NULLIF(r.Destination,''), p.Destination) = p.Destination
    AND COALESCE(NULLIF(r.Passenger_Type,''),p.Passenger_Type) = p.Passenger_Type
GROUP BY p.PassengerID

然后使用它过滤到具有相同匹配数的结果:

SELECT p
FROM Passenger p
INNER JOIN ( /* matchecounts */
    SELECT p.PassengerID, 
        MAX(CASE WHEN r.Origin <> '' THEN 1 ELSE 0 END
          + CASE WHEN r.Destination <> '' THEN 1 ELSE 0 END
          + CASE WHEN r.Passenger_Type <> '' THEN 1 ELSE 0 END) AS MatchCount
    FROM Passenger p
    INNER JOIN SEAT_ALLOCATION_RULE r ON COALESCE(NULLIF(r.Origin, ''),p.Origin) = p.Origin
        AND COALESCE(NULLIF(r.Destination,''), p.Destination) = p.Destination
        AND COALESCE(NULLIF(r.Passenger_Type,''),p.Passenger_Type) = p.Passenger_Type
    GROUP BY p.PassengerID
) m ON m.PassengerID = p.PassengerID
INNER JOIN SEAT_ALLOCATION_RULE r ON COALESCE(NULLIF(r.Origin, ''),p.Origin) = p.Origin
    AND COALESCE(NULLIF(r.Destination,''), p.Destination) = p.Destination
    AND COALESCE(NULLIF(r.Passenger_Type,''),p.Passenger_Type) = p.Passenger_Type
INNER JOIN SEATS s ON s.SeatID = r.SeatID
WHERE m.MatchCount =  
    (CASE WHEN r.Origin <> '' THEN 1 ELSE 0 END
  + CASE WHEN r.Destination <> '' THEN 1 ELSE 0 END
  + CASE WHEN r.Passenger_Type <> '' THEN 1 ELSE 0 END)
  AND p.PassengerID = /* Passenger criteria here */

在数据库中重复了很多代码和工作,并且效率不高。您可以在嵌套查询中重复乘客条件,但这只会有所帮助。如果乘客同样匹配两个规则,此选项也可能会返回多个记录,但您可以使用GROUP BY表达式轻松解决此问题。

在任何一种情况下,请注意,您可以使用实际的NULL值代替空字符串来提高SEAT_ALLOCATION_RULE表的缺失部分,从而提高性能并简化代码。

答案 1 :(得分:0)

这应该可以解决问题:

select seatid
  from seat_allocation_rule sar
order by ((sar.origin = :origin) << 2) + ((sar.destination = :destination) << 1) + (sar.passenger_type = :passenger_type) desc,
         ((sar.origin <> '')     << 2) + ((sar.destination <> '')          << 1) + (sar.passenger_type <> '')             asc
 limit 1

了解如何:

create table testcase (
    origin         varchar(255),
    destination    varchar(255),
    passenger_type varchar(255),
    expected_seat  int(11)
);

insert into testcase values ('Malaysia','Singapore','Senior_Citizen',3),
                            ('Malaysia','Singapore','Student',2),
                            ('Malaysia','US','Student',1);

select * from (
select tc.*,
       sar.seatid,
       case when sar.seatid = tc.expected_seat then 'Y' else '-' end as pass,
         ((sar.origin = tc.origin) << 2)
       + ((sar.destination = tc.destination) << 1)
       + ((sar.passenger_type = tc.passenger_type) << 0) as score,
         ((sar.origin <> '') << 2)
       + ((sar.destination <> '') << 1)
       + ((sar.passenger_type <> '') << 0) as priority
  from seat_allocation_rule sar
       cross join testcase tc
) x order by expected_seat desc, score desc, priority asc;