我们有一个表格,其中包含经常查询的事件(如在开始和结束时间的日历事件中):
TABLE event (
`id` varchar(32) NOT NULL,
`start` datetime,
`end` datetime,
`derivedfrom_id` varchar(32),
`parent_id` varchar(32) NOT NULL
)
parent_id
指向提供一些其他信息的日历表。derivedfrom_id
列指向该“origin”事件。检索一组事件时,我们通常按日期(start
/ end
)和日历(parent_id
)进行查询,并通过limit
限制结果数量寻呼。
我们现在面临的问题:有时我们需要将用户的相关事件合并为单个表示。所以我们进行常规查询
SELECT id, start, parent_id
FROM event
WHERE parent_id in (<list of calendars>)
AND start >= 'some date'
LIMIT x
...然后过滤掉原始事件,因为衍生物有不同的信息并且无论如何都要参考它们的来源。
正如您可能已经看到的(比我们更早),我们在过滤之前执行限制,因此接收一组基数小于我们最初预期的事件,即结果数低于'x'之后过滤。
我唯一能想到的是复制查询并进行子选择:
SELECT id, start, parent_id
FROM event
WHERE parent_id in (<list_of_calendars>)
AND start >= 'some date'
AND (/* the part below duplicates the previous conditions */
derivedfrom_id is not null
or id not in (
SELECT derivedfrom_id
FROM event
WHERE parent_id in (<list_of_calendars>)
AND start >= 'some date'
AND derivedfrom_id is not null
)
)
LIMIT x
但我几乎不相信这是实现这一目标的唯一方法。特别是,因为我们的查询要复杂得多。
有更好的方法吗?
(根据评论的要求)
鉴于这三个事件:
│ *ID* │ *DERIVEDFROM_ID* │ *PARENT_ID* │ *START*
├──────┼──────────────────┼─────────────┼─────────────────
│ 100 │ - │ A │ 2014-11-18 15:00
│ 101 │ 100 │ B │ 2014-11-18 15:00
│ 150 │ - │ A │ 2014-11-20 08:00
...并且限制为2,我想获得事件101和150。
相反,采用当前的方法:
上面的SQL实际上是从使用JPA的Java应用程序生成的。我目前的解决方案是生成一个where子句并复制它。如果有特定的JPA特定的东西,我会很感激任何指针。
答案 0 :(得分:4)
试试这个:
SELECT e.*
FROM `event` e # 'e' from 'event'
LEFT JOIN `event` d # 'd' from 'derived'; `LEFT JOIN` gets ALL entries from `e`
ON e.id = d.derivedfrom_id # match an event `e` with all those `d` derived from it
WHERE d.id IS NULL # keep only events `e` without derived events `d`
;
LEFT JOIN
选择e
中的所有事件,并将其与从中派生的事件d
配对。它确保{strong>所有来自e
的条目都有机会被选中,无论它们是否有派生事件。 WHERE
子句仅保留e
中没有派生事件的事件。它保留派生事件以及没有派生事件的原始事件,但删除那些具有派生事件的原始事件。
根据需要在表WHERE
的字段中添加其他e
条件,使用LIMIT
条款,搅拌均匀,冷却。
答案 1 :(得分:3)
我建议按照DERIVEDFROM_ID对事件进行分组,或者 - 如果它不是派生事件,则使用MySQL的IFNULL
方法识别其ID,请参阅SELECT one column if the other is null
SELECT id, start, parent_id, text, IFNULL(derivedfrom_id, id) as grouper
FROM event
WHERE parent_id in (<list_of_calendars>)
AND start >= '<some date>'
GROUP BY grouper
LIMIT <x>
然而,这将随机返回原始事件或派生事件。如果您只想获得派生事件,则必须在分组之前按ID对结果进行排序(假设ID为升序,派生事件的ID高于其祖先)。因为在MySQL中ORDER BY
之前无法运行GROUP BY
,您必须重新加入内部联接(MySQL order by before group by):
SELECT e1.* FROM event e1
INNER JOIN
(
SELECT max(id) maxId, IFNULL(derivedfrom_id, id) as grouper
FROM event
WHERE parent_id in (<list_of_calendars>)
AND start >= '<some date>'
GROUP BY grouper
) e2
on e1.id = e2.maxId
LIMIT <x>
编辑:正如Aaron所指出的,提升ID的假设与给定的数据结构相冲突。假设有一个时间戳created
,您可以使用这样的查询:
SELECT e1.* FROM event e1
INNER JOIN
(
SELECT max(created) c, IFNULL(derivedfrom_id, id) grouper
FROM event
WHERE parent_id IN (<list_of_calendars>)
AND start >= '<some date>'
GROUP BY grouper
) e2
ON (e1.id = e2.grouper AND e1.created = c) OR (e1.derivedfrom_id = e2.grouper AND e1.created = c)
LIMIT <x>
答案 2 :(得分:0)
正在寻找类似的东西::
Select a.id, a.start, a.parent_id from
event a , event b
Where a.parent_id in (<list_of_calendars>)
And a.start >= 'some date'
And b.parent_id = a.parent_id
And b.start = a.start
And a.id != b.derivedfrom_id
Limit x
答案 3 :(得分:0)
要省略那些在结果集中派生事件的事件,您可以测试每个id是否省略它,或者在要排除的ID的派生表上连接
加入:
SELECT id, start, parent_id
FROM event
LEFT JOIN (
SELECT DISTINCT derived_id AS id FROM event
WHERE start >= 'some date' AND parent_id IN (<calendars>)
) omit
ON omit.id = event.id
WHERE parent_id IN (<calendars>)
AND start >= 'some date'
AND omit.id IS NULL
LIMIT x
嵌套选择:如果derived_id被索引,则效率相当
SELECT e.id, e.start, e.parent_id
FROM event e
WHERE parent_id IN (<calendars>)
AND start >= 'some date'
AND (SELECT e2.id FROM event e2 /* and does not have derived events */
WHERE e2.derived_id = e.id
AND e2.start >= 'some date'
LIMIT 1) IS NULL
LIMIT x
在mysql中你无法测试否定,你必须建立排除列表并明确省略
由于parent_id(日历)可能不同,因此所有选择都必须对其进行测试。如果我们可以假设在其原始事件之前不会发生派生事件,则不必重复检查开始。
请注意,您指的是过滤掉原始事件(id 100,因为它已派生事件101),但我认为您的示例嵌套选择是过滤掉派生事件。
答案 4 :(得分:0)
假设'衍生'行中的parent_id
值与'origin'行上的parent_id
值匹配,并且保证衍生行上的start
值不是早于父行的start
...(这些是假设,因为我不相信其中任何一个被指定)......然后......
一个快速解决方法是在现有查询中添加“NOT EXISTS
”谓词。我们只是在原始查询中为表引用分配别名(例如e
),然后添加到WHERE子句中......
AND NOT EXISTS (SELECT 1 FROM event d WHERE d.derivedfrom_id = e.id)
要解释一点......对于'origin'行,子查询将找到匹配的'衍生'行,当找到该行时,'origin'行将从结果集中排除。< / p>
回到这些假设......如果我们对'origin'和'衍生'行中的parent_id
匹配没有保证......和/或我们没有保证start
值,然后我们需要在相关子查询中重复相应的谓词(在parent_id
和start
上),以检查是否会返回匹配的'派生'行,谓词的添加使查询看起来更复杂:
AND NOT EXISTS ( SELECT 1
FROM event d
WHERE d.derivedfrom_id = e.id
AND d.parent_id IN parent_id IN (<list of calendars>)
AND d.start > 'some date'
)
有时候,我们可以通过重写查询以使用等效的“反连接”模式替换NOT EXISTS
来获得更好的性能。
为了描述这一点,它是一个“外部联接”,用于查找匹配的“派生”行,然后过滤掉至少有一个匹配的“衍生”行的行。
就个人而言,我认为NOT EXISTS
形式更直观,反连接模式更加模糊。反连接的好处是性能更好(在某些情况下)。
作为反连接模式的一个例子,我将重写这样的查询:
SELECT e.id
, e.start
, e.parent_id
FROM event e
LEFT
JOIN event d
ON d.derivedfrom_id = e.id
AND d.parent_id IN (<list of calendars>)
AND d.start >= 'some date'
WHERE d.derivedfrom_id IS NULL
AND e.parent_id IN (<list of calendars>)
AND e.start >= 'some date'
ORDER BY e.id
LIMIT x
要解压缩一点...... LEFT [OUTER] JOIN
操作会找到匹配的'衍生'行,它会从e
返回具有匹配'派生'行的行,以及来自{{1}的行没有匹配的。 “技巧”是列上的e
条件,当找到匹配的派生行时,该条件保证为非NULL,该谓词排除发现匹配的行。
(我还添加了一个ORDER BY子句,以使结果更具确定性。)