我有一个用户表,其中包含数十个列,如出生日期,车辆拥有年份,车辆品牌和型号,颜色以及与车辆无关的许多其他个人字段
还有一个名为优惠券的第二张桌子需要设计以支持“如果年龄小于30岁的用户有资格”,“如果车辆大于10岁的用户有资格”,“用户符合条件”等资格如果车辆颜色为绿色“。
当用户登录时,我需要提供用户符合条件的所有优惠券。我遇到的问题是优惠券资格可能很多,可能有等于,大于或小于等的限定符,可能有不同的组合。
此时我唯一的解决方案是将实际的sql字符串存储在其中一个优惠券表列中,如
select * from Users where UserId = SOME_PLACEHOLDER and VehicleYear < 10
然后我可以为每个优惠券行执行sql并返回true或false。由于我可能必须为每个优惠券代码执行1000个sql语句,因此效率非常低。
任何见解,帮助表示赞赏。我确实有服务器端代码,我可以在其中进行循环。
谢谢。
答案 0 :(得分:1)
非常困难的问题。似乎用户将以高音量添加,优惠券的频率相当规律。
将SQL添加到要动态使用的表中是可行的 - 至少你会得到一个新的执行计划 - 但你的计划缓存可能会膨胀。
我觉得为所有用户运行一张优惠券可能是您性能最高的查询,因为它是一组标准,对用户来说是相当有选择性的,优惠券总数很少,而全部运行单个用户的优惠券是该用户的每个优惠券的单独标准。为所有用户运行所有优惠券可能仍然表现良好,即使它实际上是首先交叉加入 - 我想这只是依赖。
无论如何,所有用户的所有优惠券的情况(或任何方式切片,真的)将是这样的:
SELECT user.id, coupon.id
FROM user
INNER JOIN coupon
ON (
CASE WHEN <coupon.criteria> THEN <coupon.id> -- code generated from the coupon rules table
CASE WHEN <coupon.criteria> THEN <coupon.id> -- etc.
ELSE NULL
) = coupon.id
要生成优惠券规则,您可以通过单次滑动相对轻松地进行字符串连接(并且您可以将优惠券的单独规则线设计与AND与另一个内部模板相结合):
DECLARE @outer_template AS varchar(max) = 'SELECT user.id, coupon.id
FROM user
INNER JOIN coupon
ON (
{template}
ELSE NULL
) = coupon.id
';
DECLARE @template AS varchar(max) = 'CASE WHEN {coupon.rule} THEN {coupon.id}{crlf}';
DECLARE @coupon AS TABLE (id INT, [rule] varchar(max));
INSERT INTO @coupon VALUES
(1, 'user.Age BETWEEN 20 AND 29')
,(2, 'user.Color = ''Yellow''');
DECLARE @sql AS varchar(MAX) = REPLACE(
@outer_template
,'{template}',
REPLACE((
SELECT REPLACE(REPLACE(
@template
,'{coupon.rule}', coupon.[rule])
, '{coupon.id}', coupon.id)
FROM @coupon AS coupon
FOR XML PATH('')
), '{crlf}', CHAR(13) + CHAR(10)));
PRINT @sql;
// EXEC (@sql);
有很多方法可以解决这个问题 - 在这里玩它:http://data.stackexchange.com/stackoverflow/q/115098/
我会考虑添加计算列(可能是持久的和索引的)来协助。例如,年龄 - 非持久计算列可能比标量函数表现更好。
我会考虑用一张表格对其进行批处理,该表格说明优惠券是否对用户有效以及最后一次验证的时间。
似乎年龄可能会发生变化,并且用户可能会因为生日快递而对优惠券有效或无效。
当用户登录时,您可以生成后台作业以更新其优惠券。在随后的登录中,不需要更新(因为它不可能在第二天或触发事件之前改变)。
只是一些想法。
我还要补充一点,您应该有一种方法在批准之前测试优惠券,以确保没有语法错误(因为SQL是临时的或任意的) - 这可以相对容易地完成 - 也许是测试用户table(test_user作为生成的代码模板中的用户)需要包含pass和fail行,优惠券规则指向那些。 EXEC不仅必须工作 - 它返回的行应该是预期的,只有该优惠券的预期行。
答案 1 :(得分:0)
这不是一个容易的问题。以下是一些可能有所帮助的快速提示,具体取决于您的域名要求:
限制要过滤的条件类型,以便您可以使用动态或非动态sql高效执行它们。例如,如果您只需要将最小值和最大值范围之间的整数作为标准,那么问题就会变得更加简单。 (您只需要知道字段名称,并使用最小值来描述标准,而不是完整的where语句。)
创建一些以有用的方式公开属性的视图。然后对这些视图执行查询 - 或者以某种方式预先选择这些视图。例如,年龄组视图的字段可以包含值< 21
,21-30
,30-45
,>45
。然后你的select只需要从这个视图中返回与这些字符串匹配的行。
创建一个表,用于存储运行条件匹配查询的结果(这可以通过后台进程离线运行)。然后,对于给定的用户,通过查看表中该用户的ID存在的位置来检查成员资格。
考虑到这一点,我意识到我的所有建议都基于一个想法。
如果您首先针对所有用户执行SQL查询并以某种方式缓存,则对单个用户的查询将更快地运行。如果每个用户都在针对整个数据集再现查询,那么您将失去效率。您需要一些方法来缓存结果并重用它们。
希望这有帮助 - 评论这些想法是否不明确。
答案 2 :(得分:0)
我对方法(类似于Hogan)的第一个想法是在创建优惠券时测试优惠券的适用性。将这些结果存储在表格中(例如User_Coupons
)。如果更改了任何用户数据,您的系统将重新测试适用于哪些优惠券的已更改用户。在优惠券创建(或更改)时间,它只会检查该优惠券。在使用创建(或更改)时,它只会检查该用户。
优惠券标准应该来自一组已知的可能标准,并且只要您想要添加新的类型标准,就可能需要更改代码。例如,假设您有一个类似于此的表设置:
CREATE TABLE Coupon_Criteria (
coupon_id INT NOT NULL,
age_minimum SMALLINT NULL,
age_maximum SMALLINT NULL,
vehicle_color VARCHAR(20) NULL,
...
CONSTRAINT PK_Coupon_Criteria PRIMARY KEY CLUSTERED (coupon_id)
)
如果您想添加基于车龄的优惠券的功能,那么您必须在表格中添加一列,同样您必须调整搜索代码。您可以使用NULL值来指示该优惠券未使用该条件。
上表的示例查询:
SELECT
CC.coupon_id
FROM
Users U
INNER JOIN Coupon_Criteria CC ON
(CC.age_maximum IS NULL OR dbo.f_GetAge(U.birthday) <= age_maximum) AND
(CC.age_minimum IS NULL OR dbo.f_GetAge(U.birthday) >= age_minimum) AND
(CC.vehicle_color IS NULL OR U.vehicle_color = CC.vehicle_color) AND
...
如果可能的标准数量非常大,这可能会变得难以处理。
另一种可能性是将优惠券标准保存在XML中,并为您的应用程序使用该业务对象来确定是否符合资格。它可以使用XML为User表(以及任何其他必要的表)生成正确的查询。
答案 3 :(得分:0)
这是另一种可能性。可以为每个条件提供一个查询模板,您可以将其附加到查询中。这只涉及数据的更新而不是DDL,并且可以具有良好的性能。它将涉及动态SQL。
CREATE TABLE Coupons (
coupon_id INT NOT NULL,
description VARCHAR(2000) NOT NULL,
...
CONSTRAINT PK_Coupons PRIMARY KEY CLUSTERED (coupon_id)
)
CREATE TABLE Coupon_Criteria (
coupon_id INT NOT NULL,
criteria_num SMALLINT NOT NULL,
description VARCHAR(50) NOT NULL,
code_template VARCHAR(500) NOT NULL,
CONSTRAINT PK_Coupon_Criteria PRIMARY KEY CLUSTERED (coupon_id, criteria_num),
CONSTRAINT FK_Coupon_Criteria_Coupon FOREIGN KEY (coupon_id) REFERENCES Coupons (coupon_id)
)
INSERT INTO Coupons (coupon_id, description)
VALUES (1, 'Young people save $200 on yellow vehicles!')
INSERT INTO Coupon_Criteria (coupon_id, criteria_num, description, code_template)
VALUES (1, 1, 'Young people', 'dbo.Get_Age(U.birthday) <= 20')
INSERT INTO Coupon_Criteria (coupon_id, criteria_num, description, code_template)
VALUES (1, 2, 'Yellow Vehicles', U.vehicle_color = ''Yellow''')
然后,您可以通过简单地连接任何给定优惠券的所有条件来构建查询。这一点的大的缺点是它只是单向的。给定优惠券,您可以轻松找到谁有资格获得优惠券,但是给予用户您无法找到符合条件的所有优惠券,除非通过所有优惠券。我的猜测是,第二个是你可能最感兴趣的东西。也许这会给你一些其他想法。
例如,您可以通过在表格中设置一定数量的条件以及优惠券/标准链接表指示该条件是否有效来使其以相反的方式工作。查询时,您可以在查询中包含该内容。换句话说,查询看起来像:
WHERE
(CC.is_active = 0 OR <code from the code column>) AND
查询变得非常复杂,因为您需要为每个可能的标准加入一次,或者您需要查询以比较优惠券的活动要求数与已满足的数量。这在SQL中是可能的,但它与使用EAV模型类似 - 这基本上就是这样:EAV模型的变化(yuck)