对于我正在进行的个人项目,我想在Steam,Impulse,EA Origins和其他几个网站上制作游戏价格线图。目前,我已经修改了SteamCalculator.com使用的脚本,以记录每个国家/地区代码或每个网站中每个游戏的当前价格(如果适用的话,销售价格)。我还有一个列存储价格的日期。我目前的表格看起来像这样:
THIS STRUCTURE IS NO LONGER VALID. SEE BELOW
+----------+------+------+------+------+------+------+------------+
| steam_id | us | at | au | de | no | uk | date |
+----------+------+------+------+------+------+------+------------+
| 112233 | 999 | 899 | 999 | NULL | 899 | 699 | 2011-8-21 |
| 123456 | 1999 | 999 | 1999 | 999 | 999 | 999 | 2011-8-20 |
| ... | ... | ... | ... | ... | ... | ... | ... |
+----------+------+------+------+------+------+------+------------+
目前每个国家/地区都是单独更新的(有一个for循环通过这些国家/地区),但如果它会简化它,那么可以修改它以暂时将新价格存储到数组中,然后一次更新整行。无论如何,出于性能原因,我最终可能会这样做。
现在我的问题是确定如果其中一个价格发生变化,如何最好地更新此表。例如,我们假设2011年8月22日游戏112233
在美国以4.99美元的价格开售,奥地利以3.99欧元的价格开售,其他价格保持不变。我需要表格看起来像这样:
THIS STRUCTURE IS NO LONGER VALID. SEE BELOW
+----------+------+------+------+------+------+------+------------+
| steam_id | us | at | au | de | no | uk | date |
+----------+------+------+------+------+------+------+------------+
| 112233 | 999 | 899 | 999 | NULL | 899 | 699 | 2011-8-21 |
| 123456 | 1999 | 999 | 1999 | 999 | 999 | 999 | 2011-8-20 |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 112233 | 499 | 399 | 999 | NULL | 899 | 699 | 2011-8-22 |
+----------+------+------+------+------+------+------+------------+
我不想创建新行每次时间检查价格,否则我最终会在日复一日地拥有数百万行重复价格。我也不想像每次更改价格那样创建一个新行:
THIS STRUCTURE IS NO LONGER VALID. SEE BELOW
+----------+------+------+------+------+------+------+------------+
| steam_id | us | at | au | de | no | uk | date |
+----------+------+------+------+------+------+------+------------+
| 112233 | 999 | 899 | 999 | NULL | 899 | 699 | 2011-8-21 |
| 123456 | 1999 | 999 | 1999 | 999 | 999 | 999 | 2011-8-20 |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 112233 | 499 | 899 | 999 | NULL | 899 | 699 | 2011-8-22 |
| 112233 | 499 | 399 | 999 | NULL | 899 | 699 | 2011-8-22 |
+----------+------+------+------+------+------+------+------------+
我可以通过使每个(steam_id, <country>)
成为唯一索引然后将ON DUPLICATE KEY UPDATE
添加到每个数据库查询来防止第一个问题而不是第二个问题。如果价格不同,这只会添加一行,但是会为每个更改的国家/地区添加新行。它也不允许两个不同日期的单个游戏的价格相同(例如,假设游戏112233
稍后销售并返回9.99美元),所以这显然是一个糟糕的选择。
我可以通过将(steam_id, date)
作为唯一索引然后将ON DUPLICATE KEY UPDATE
添加到每个查询来防止第二个问题而不是第一个问题。运行脚本的每一天日期都已更改,因此它将创建一个新行。这种方法最终会产生数百条相同价格的日常生产线。
如果(并且仅当)自最近日期以来任何价格发生变化,我如何告诉MySQL创建新行?
更新 -
根据此线程中人员的建议,我已更改了数据库的架构,以便将来添加新的国家/地区代码,并避免需要一次更新整个行的问题。新架构看起来像:
+----------+------+---------+------------+
| steam_id | cc | price | date |
+----------+------+---------+------------+
| 112233 | us | 999 | 2011-8-21 |
| 123456 | uk | 699 | 2011-8-20 |
| ... | ... | ... | ... |
+----------+------+---------+------------+
除了这个新架构之外,我发现我可以使用以下SQL查询来获取最新更新的价格:
SELECT `price` FROM `steam_prices` WHERE `steam_id` = 112233 AND `cc`='us' ORDER BY `date` ASC LIMIT 1
此时我的问题归结为:
是否可以(仅使用SQL而不是应用程序逻辑)仅在条件为真时插入行?例如:
INSERT INTO `steam_prices` (...) VALUES (...) IF price<>(SELECT `price` FROM `steam_prices` WHERE `steam_id` = 112233 AND `cc`='us' ORDER BY `date` ASC LIMIT 1)
从the MySQL manual我找不到任何办法。我发现如果唯一索引相同,您可以忽略或更新。但是,如果我将价格作为一个独特的索引(允许我更新日期,如果它是相同的),那么我将无法识别游戏何时开始销售,然后返回其原始价格。例如:
+----------+------+---------+------------+
| steam_id | cc | price | date |
+----------+------+---------+------------+
| 112233 | us | 999 | 2011-8-20 |
| 112233 | us | 499 | 2011-8-21 |
| 112233 | us | 999 | 2011-8-22 |
| ... | ... | ... | ... |
+----------+------+---------+------------+
此外,在找到并阅读MySQL Conditional INSERT之后,我创建并尝试了以下查询:
INSERT INTO `steam_prices`(
`steam_id`,
`cc`,
`update`,
`price`
)
SELECT '7870', 'us', NOW(), 999
FROM `steam_prices`
WHERE
`price`<>999
AND `update` IN (
SELECT `update`
FROM `steam_prices`
ORDER BY `update`
ASC LIMIT 1
)
想法是插入行'7870', 'us', NOW(), 999
if(且仅当)最新price
的{{1}}不是999.当我运行此时出现以下错误:
1235 - 此版本的MySQL尚不支持'LIMIT&amp; IN / ALL / ANY / SOME子查询'
有什么想法吗?
答案 0 :(得分:6)
如果您只是将架构更改为以下内容,您可能会发现这更容易:
steam_id integer
country varchar(2)
date date
price float
primary key (steam_id,country,date)
(与其他适当的指标一起),然后只是依次担心每个国家。
换句话说,您的for
循环具有唯一的ID /国家/地区组合,因此它可以简单地查询该组合的最新日期记录,如果不同则添加新行。
这将使您的选择更加复杂,但我相信这是一个更好的解决方案,尤其是如果有任何机会可以在未来添加更多国家(它不会破坏架构在那种情况下)。
答案 1 :(得分:2)
首先,我建议您将数据存储在每个国家/地区硬编码较少的表单中:
+----------+--------------+------------+-------+
| steam_id | country_code | date | price |
+----------+--------------+------------+-------+
| 112233 | us | 2011-08-20 | 12.45 |
| 112233 | uk | 2011-08-20 | 12.46 |
| 112233 | de | 2011-08-20 | 12.47 |
| 112233 | at | 2011-08-20 | 12.48 |
| 112233 | us | 2011-08-21 | 12.49 |
| ...... | .. | .......... | ..... |
+----------+--------------+------------+-------+
从此处,您可以在前三列上放置一个主键...
现在提出关于不创建额外行的问题......这就是简单的事务+应用程序逻辑的优点。
这种方法有问题吗?
希望这有帮助。
答案 2 :(得分:1)
经过实验,并在MySQL Conditional INSERT和http://www.artfulsoftware.com/infotree/queries.php#101的帮助下,我发现了一个有效的查询:
INSERT INTO `steam_prices`(
`steam_id`,
`cc`,
`price`,
`update`
)
SELECT 7870, 'us', 999, NOW()
FROM `steam_prices` AS p1
LEFT JOIN `steam_prices` AS p2 ON p1.`steam_id`=p2.`steam_id` AND p1.`update` < p2.`update`
WHERE
p2.`steam_id` IS NULL
AND p1.`steam_id`=7870
AND p1.`cc`='us'
AND (
p1.`price`<>999
)
答案是首先返回没有早期时间戳的所有行。这是通过组内聚合完成的。只在时间戳较早的行上加入一个表。如果它无法加入(时间戳不早),那么您知道该行包含最新的时间戳。这些行在连接表中将具有 NULL id(无法加入)。
选择具有最新时间戳的所有行后,只抓取steam_id为您正在寻找的steam_id的行以及价格与您输入的新价格不同的行。如果此时该游戏的行没有不同的行,则自上次更新后价格没有变化,因此返回空集。返回空集时,SELECT语句将失败,并且不会插入任何内容。如果SELECT语句成功(找到了不同的价格),那么它将返回插入到表中的行7870, 'us', 999, NOW()
。
编辑 - 我实际上在一段时间后发现了上述查询的错误,之后我修改了它。如果自上次更新后价格发生了变化,上面的查询将插入一个新行,但如果该项目的数据库中当前没有价格,则不会插入一行。
要解决这个问题,我必须利用DUAL
表(总是包含一行),然后在where子句中使用OR来测试不同的价格 OR 空集
INSERT INTO `steam_prices`(
`steam_id`,
`cc`,
`price`,
`update`
)
SELECT 12345, 'us', 999, NOW()
FROM DUAL
WHERE
NOT EXISTS (
SELECT `steam_id`
FROM `steam_prices`
WHERE `steam_id`=12345
)
OR
EXISTS (
SELECT p1.`steam_id`
FROM `steam_prices` AS p1
LEFT JOIN `steam_prices` AS p2 ON p1.`steam_id`=p2.`steam_id` AND p1.`update` < p2.`update`
WHERE
p2.`steam_id` IS NULL
AND p1.`steam_id`=12345
AND p1.`cc`='us'
AND (
p1.`price`<>999
)
)
它很长,非常难看,而且非常复杂。但它完全像宣传的那样工作。如果某个steam_id数据库中没有价格,那么它会插入一个新行。如果已有价格,那么它会使用最新更新检查价格,如果不同,则会插入新行。