0

I am querying a database which has the following schema:

People
------
PersonId

PeopleTags
----------
PeopleId
TagId

Tags
----
TagId
Guid

A person may have multiple tags and when querying the requirement is to find people that have ALL of a set of mandatory tags and ANY of a set of optional tags.

At the moment, I'm doing a separate subquery per mandatory tag and a single subquery for the optional tags which gives me something like this:

SELECT People.*
FROM People
WHERE
    PeopleId IN (SELECT PeopleId FROM PeopleTags WHERE TagId IN (SELECT TagId FROM Tags WHERE Guid = '83c55f3d...')
    AND PeopleId IN (SELECT PeopleId FROM PeopleTags WHERE TagId IN (SELECT TagId FROM Tags WHERE Guid = '8159248a...')
    AND PeopleId IN (SELECT PeopleId FROM PeopleTags WHERE TagId IN (SELECT TagId FROM Tags WHERE Guid IN ('737d9015...', 'b7a5974e...')

There can be any number of mandatory and optional tags and we occasionally get this error:

The query processor ran out of internal resources and could not produce a query plan. This is a rare event and only expected for extremely complex queries or queries that reference a very large number of tables or partitions. Please simplify the query. If you believe you have received this message in error, contact Customer Support Services for more information.

Researching this error suggests that it is due to sub queries with large IN lists (which is entirely possible with the code as it stands).

Is there a better way to write this query? I have tried joining to the table multiple times but it doesn't return any results if I do.

SELECT People.*
FROM People
INNER JOIN PeopleTags ON PeopleTags.PersonId = People.PersonId
INNER JOIN Tags t1 on t1.TagId = PeopleTags.TagId and t1.Guid = '8159248A...'
INNER JOIN Tags t2 on t2.TagId = PeopleTags.TagId and t2.Guid = '415DF7A3...'

I can change

PeopleId IN (SELECT PeopleId FROM PeopleTags WHERE TagId IN (SELECT TagId FROM Tags WHERE Guid = '83c55f3d...')

to

PeopleId IN (SELECT PeopleId FROM PeopleTags INNER JOIN Tags ON Tags.TagId = PeopleTags.TagId WHERE Guid = '83c55f3d...')

for each mandatory tag, will that make a difference?

4

1 に答える 1

0

アップデート:

JOINに変換しようとしても良かったのですが、LEFT JOINを実行する必要があります。そうしないと、元の回答で説明したように、結果が得られません。さらに、ORDER BYを追加したので、オプションのタグがないものは、オプションのタグがあるものの下にリストされます。次に、結果を制限して、ユーザーが探していた可能性が最も高い結果を取得できます。

SELECT People.*
FROM People
INNER JOIN PeopleTags ON PeopleTags.PersonId = People.PersonId
INNER JOIN Tags t1 on t1.TagId = PeopleTags.TagId and t1.Guid = '8159248A...'
LEFT JOIN Tags t2 on t2.TagId = PeopleTags.TagId and t2.Guid = '415DF7A3...'
ORDER BY CASE WHEN t2.TagId IS NULL THEN 99 ELSE 1 END
LIMIT 10 /*or whatever*/

元の回答:

あなたの質問は少し不明確です。2番目のオプションのタグはタグに参加していますか(私の答えではそう思います)?また、オプションのタグが少なくとも1つある必要がありますか、それとも存在しない可能性がありますか?何も存在しない可能性がある場合(これは私がオプションで定義する方法です)、なぜそれらをまったく気にするのですか?あなたもそうしませんSELECT。したがって、2番目の結合を忘れることができます。これは、あなたの場合は、ちなみに欠陥があります。PeopleTagsの同じ列を同じ列の同じテーブルに複数回結合します。ただし、PeopleTagsの行に2つの異なる値を同時に含めることはできません。したがって、結果はありません。

オプションのタグが少なくとも1つあることを確認する必要があるが、それが何であるかは気にしない場合は、次のように最適化できます。

SELECT People.*
FROM People
INNER JOIN PeopleTags ON PeopleTags.PersonId = People.PersonId
INNER JOIN Tags t1 on t1.TagId = PeopleTags.TagId 
WHERE t1.Guid = '8159248A...'
AND EXISTS (SELECT 1 FROM Tags t2 WHERE t2.TagId = PeopleTags.TagId)
于 2013-03-13T12:32:42.780 に答える