在SQL中计算级联价格规则

时间:2014-08-15 16:33:53

标签: php mysql sql postgresql doctrine

我正在构建一个工具,允许人们将项目分类。在项目过帐之前,需要进行价格计算以确定向发布项目的用户收取的价格。

我提出了使用名为price_rule的表来定义规则的概念,它看起来像这样:

+------------ +
| Field       |
--------------+
| id          |
| user        |
| category    |
| price       |
+-------------+

作为一般规则,此数据库将始终具有后备行。此行用于在没有其他规则与特定上下文匹配的情况下确定价格。那一行是这样的:

+-----------+---------+-------------+-------+
| id        | user    | category    | price |
+-----------+---------+-------------+-------+
| 1         | NULL    | NULL        | 10.00 |
+-----------+---------+-------------+-------+

有了这一行而没有其他行,帖子总是花费10.00美元。

现在假设添加了一行。该表现在看起来像这样:

+-----------+---------+-------------+-------+
| id        | user    | category    | price |
+-----------+---------+-------------+-------+
| 1         | NULL    | NULL        | 10.00 |
+-----------+---------+-------------+-------+
| 2         | Bob     | NULL        | 8.00  |
+-----------+---------+-------------+-------+

通过添加此规则,Bob将在所有类别中为每个帖子支付8.00美元,其余用户将支付所有类别的10.00美元。

如果我们添加更多包含特定用户和特定类别的行:

+-----------+---------+-------------+-------+
| id        | user    | category    | price |
+-----------+---------+-------------+-------+
| 1         | NULL    | NULL        | 10.00 |
+-----------+---------+-------------+-------+
| 2         | Bob     | NULL        | 8.00  |
+-----------+---------+-------------+-------+
| 3         | Bob     | Bicycles    | 9.50  |
+-----------+---------+-------------+-------+
| 4         | Meghan  | Bicycles    | 5.00  |
+-----------+---------+-------------+-------+

当鲍勃去自行车类别发帖时,他的价格是9.50美元。任何其他类别,鲍勃将支付8.00美元。

Meghan现在支付5.00美元用于自行车类别的发布,每个其他类别支付10.00美元。

任何类别(包括自行车)中发布的每个其他用户都将支付10.00美元的默认价格。

在现实世界中,此表可能有几百行,这样可以精确控制发布的价格。

如果你想知道,这个概念背后有商业动机,因为在这个系统中发帖的成本并不总是确定的。它取决于与发布帖子的用户的业务关系。

在尝试设计一个返回适用于正在发布的帖子的最相关定价规则的查询时,问题就会显现出来。查询此表时,我可以访问以下信息:用户和类别。我尝试了一系列查询,并阅读了许多SQL概念,例如IFNULLCOALESCE,但我还没有能够确定正确的查询。

另一个问题是,在我们的实际应用程序中,price_rule表中有一个额外的列来关闭价格,但我已经将这个细节留下来以使这个问题中使用的示例更简单。我觉得无论是否有2列或3列用于计算,都可能适用相同的解决方案。

请注意,应用代码中强制执行的约束可防止添加重复规则。

我们也在使用Doctrine ORM和Doctrine DBAL,因此如果您的查询与Query Builder或DQL一起开箱即用,您的答案将被认为更有价值。可以在PostgreSQL或MySQL中使用的标准SQL解决方案也是可以接受的。

虽然我希望尽可能避免这种情况,但有效的解决方案还可能包括从price_rule表中获取每一行并使用应用程序代码确定适用的规则。如果您的解决方案基于此概念,请包含相关的伪代码。

2 个答案:

答案 0 :(得分:1)

一种简单的方法是按比例对每一行进行评分,并选择得分最高的行,以匹配Bob / Bicycles之类的内容;

SELECT price,
  CASE WHEN "user" = 'Bob' THEN 2 
       WHEN "user" IS NULL THEN 0
       ELSE -10 END +
  CASE WHEN "category" = 'Bicycles' THEN 1
       WHEN "category" IS NULL THEN 0
       ELSE -10 END score
  FROM field
ORDER BY score DESC LIMIT 1;

这为名称匹配提供2分,为类别匹配提供1分。任何不匹配都会给出-10,如果其他任何东西都不匹配,则允许默认获胜。

An SQLfiddle to test with

如果您有大量行,则需要添加一个WHERE子句,该子句只查找匹配的行(即匹配名称/类别或null),并在过滤的行上使用order by。

答案 1 :(得分:1)

Postgresql Fiddle

create or replace function price_rule(
    _user varchar(50), _category varchar(50)
) returns setof price_rule as $$

select id, "user", category, price
from (
    select *, 0 as priority
    from price_rule
    where category = _category and "user" = _user

    union

    select *, 1 as priority
    from price_rule
    where "user" = _user and category is null

    union

    select *, 2 as priority
    from price_rule
    where "user" is null and category is null
) s
order by priority
limit 1
;
$$ language sql;

我将上述查询转换为函数,以便于测试。但如果您愿意,可以打开它。

在MySQL中,我不记得如何创建一个函数,因为它是原始的:

MySQL Fiddle

select id, `user`, category, price
from (
    select *, 0 as priority
    from price_rule
    where category = 'Bicycles' and `user` = 'Bob'

    union

    select *, 1 as priority
    from price_rule
    where `user` = 'Bob' and category is null

    union

    select *, 2 as priority
    from price_rule
    where `user` is null and category is null
) s
order by priority
limit 1;