【问题标题】:sql : finding how many objects were stolen by a player?sql : 找出一个玩家偷了多少物品?
【发布时间】:2013-06-01 18:22:35
【问题描述】:

数据库设计:

小提琴:http://sqlfiddle.com/#!2/4f23b3

我有一张桌子players

CREATE TABLE players (
    id MEDIUMINT(7) unsigned AUTO_INCREMENT PRIMARY KEY ,
    name VARCHAR(30)
) ENGINE = InnoDB

一张桌子objects

CREATE TABLE objects (
    id INT(9) unsigned AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(30),
    type VARCHAR(20)
) ENGINE = InnoDB

还有一个描述one-to-many对象和玩家之间关系的表格(一个玩家可以有多个对象),但严格来说它不是one-to-many,我们稍后会看到:

CREATE TABLE playerobjects (
    objectid INT(9) unsigned NOT NULL,
    playerid MEDIUMINT(7) unsigned NOT NULL,
    `date` DATE NOT NULL,
    PRIMARY KEY(objectid,`date`)
) ENGINE = InnoDB

上表可能难以理解,但这就是它的全部含义:

  • 玩家可以在任何给定日期拥有许多对象
  • 一个对象在任何给定日期只能属于一个玩家
  • 玩家可能另一个玩家的物品,这意味着桌 playerobjects 将有两个用于被盗物品的条目,具有不同的玩家 ID 和不同的日期
  • 一件物品每天只能被盗一次

因此,我们可以从上述准则推断出一个对象可能有很多所有者,但每天只有一个。也有可能player1从player2那里偷了一些东西,第二天player2从player 1那里偷了东西。

我想做什么:

现在,从上面的数据库中,我想找出一个玩家偷了多少物品,目前正在使用它们。另外,我想制作一个排行榜,显示排名靠前的顶级玩家。被盗物品。

也就是说,如果 Bob 先拿到了该物品,但 Emily 从 Bob 那里偷了它,而 Sheldon 从 Emily 那里偷了它,那么查询应该只显示该物品是 Sheldon 在某个日期偷的,而不是其他任何东西,因为 Emily 确实偷了该物品来自 Bob,但这是很久以前的事了,该对象的当前所有者是 Sheldon。

示例代码

INSERT INTO  players (name) VALUES ('Bob') , #id 1
        ('Emily') ,  #id  2
        ('Sheldon'); #id 3

INSERT INTO objects (name,type) VALUES ('Choco vanilla','ice cream'), #1 
        ('Butterscotch','ice cream'), #2
        ('Nexus 4','Mobile Phone'), #3
        ('Snoopy','pet'), #4
        ('minecraft','game'); #5

INSERT INTO playerobjects (playerid,objectid,date) VALUES (1,1,'2013-05-15'), 
        (2,2,'2013-05-15'), 
        (3,3,'2013-05-15'), 
        (1,4,'2013-05-15'), 
        (2,1,'2013-05-16'),
        (1,5,'2013-05-16'),
        (3,1,'2013-05-17'), 
        (1,3,'2013-05-18'), 
        (3,3,'2013-05-19'), 
        (3,5,'2013-05-19'),
        (2,5,'2013-05-20');

id 为 3,行的个人的预期结果:

        (3,1,'2013-05-17'), 
        (3,3,'2013-05-19')

id 为 2,行的个人的预期结果:

        (2,5,'2013-05-20')

玩家 1 的预期结果:没有,因为他偷的东西被重新从他身上偷走了,只剩下他没有从任何人那里偷的东西。

排行榜查询的预期结果:

 sheldon 2
 emily   1
 bob     0

我想出什么:

排行榜查询,提供错误信息,而且我认为效率低下:

SELECT tpo.playerid,COUNT(*) as steals
FROM `playerobjects` AS tpo

LEFT JOIN `playerobjects` AS tpo2 ON (tpo.objectid = tpo2.objectid AND tpo.date < tpo2.date)
LEFT JOIN `playerobjects` AS tpo3 ON (tpo.objectid = tpo3.objectid AND tpo.date > tpo3.date)

WHERE tpo2.objectid IS NULL
AND tpo3.objectid IS NOT NULL

GROUP BY (tpo.playerid)
ORDER BY steals DESC

查询个人玩家和偷窃的详细信息,给出错误信息:

SELECT tpo.objectid,tpo.date
FROM `playerobjects` AS tpo

LEFT JOIN `playerobjects` AS tpo2 ON (tpo.objectid = tpo2.objectid AND tpo.date < tpo2.date)
LEFT JOIN `playerobjects` AS tpo3 ON (tpo.objectid = tpo3.objectid AND tpo.date > tpo3.date)

WHERE tpo2.objectid IS NULL
AND tpo3.objectid IS NOT NULL

为什么这些查询给出了错误的信息?因为它们还计算对象的先前记录。将其与之前的 Bob Emily Sheldon 示例相关联,虽然它应该只返回 Sheldon 从 Emily 窃取对象的记录,但它还显示 Emily 从 Bob 那里窃取对象的记录。如果不使用复杂的子查询,我不知道如何解决这个问题,这会使查询效率更低。我真的希望有更好的方法来做到这一点。

【问题讨论】:

  • 请为您的所有表格发布一些示例数据,并根据它发布所需的输出
  • 您的查询和提供的架构不匹配。连接条件中的 vidupdated 列是什么?
  • 错误,应该是 objectid 和 date。现已修复
  • 这里,一个小提琴:sqlfiddle.com/#!2/4f23b3

标签: mysql sql many-to-many logic one-to-many


【解决方案1】:
select tpo.objectid, tpo.date from playerobjects  as tpo where (select count(*) from playerobjects as tpo2 where ((tpo2.date>tpo.date) and (tpo2.objectid=tpo.objectid)))=0 and playerid=3;

希望有帮助

【讨论】:

  • 此查询在检索玩家 2 和玩家 1 时失败,因为它还返回玩家已经拥有且没有偷窃的对象。 sqlfiddle.com/#!2/4f23b3/10
  • 不,请验证 * 玩家 2 的真实结果是 (2;5) 而不是你上面写的 5 * 玩家 1 的真实结果是 4 在这两种情况下,sql 代码我写的那么好
  • 是的,它适用于所有情况。如果你愿意,给我一个它不起作用的案例。
【解决方案2】:

去掉 tpo2 JOIN 并将 tpo2 WHERE 子句替换为

WHERE NOT EXISTS (
    SELECT * FROM `playerobjects` AS tpo2 
    WHERE (tpo.objectid = tpo2.objectid AND tpo.date < tpo2.date)
)

也就是说,这里不用JOINing,这会导致复杂化,只是排除后来被盗的物品。

同时,将您的 tpo3“LEFT JOIN”更改为“INNER JOIN”。

【讨论】:

    猜你喜欢
    • 2022-11-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-22
    相关资源
    最近更新 更多