sql:发现玩家有多少物被盗?

时间:2013-06-01 18:22:36

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

数据库设计:

小提琴: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将为被盗对象提供两个条目,其中包含不同的玩家和不同的日期
  • 一件物品每天只能被盗一次

因此,我们可以从上述指南中推断出一个对象可能有很多所有者,但每天只有一个。它也可能是player1可以从player2中窃取一些对象,第二天,player2从玩家1中抢回该对象。

我想做什么:

现在,从上面的数据库中,我想知道一个玩家被盗的对象有多少,目前正在追踪它们。此外,我想制作一个排行榜,显示排名靠前的顶级球员。被盗物品。

那就是说,如果Bob首先拥有该对象但是Emily从Bob那里偷走了它并且Sheldon从Emily那里偷走了它,那么查询应该只显示该对象在某个日期被Sheldon窃取而没有别的,因为Emily确实偷了这个对象来自鲍勃,但很久以前,该对象的当前所有者是谢尔顿。

示例代码

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');

个人身份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

我想出的:

Leader Board查询,提供错误的信息,而且我觉得效率低下:

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窃取对象的记录。我不知道如何在不使用复杂的子查询的情况下解决这个问题,这会使查询效率更低。我真的希望有更好的方法来做到这一点。

2 个答案:

答案 0 :(得分:0)

删除tpo2 JOIN并用

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

也就是说,不要在这里引起复杂的JOIN,而是排除以后被盗的物品。

当你在这里时,将你的tpo3“LEFT JOIN”更改为“INNER JOIN”。

答案 1 :(得分:0)

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;

希望这个帮助