开发者

Many-to-many relationship SELECT issue

I have a very annoying problem on the application I've been contracted to work in.

The database structure, which I can't modify for compatibility reasons is a mess. It's basically a many-to-many-to-many-to-many-to-bang-your-head-on-the-desk relationship. It goes like this:

contact

---------------------------
| contact_id | name | ... |
---------------------------
| 1          | foo  |     |
---------------------------

metadefinition

-----------------------------
| metadefinition_id | name  |
-----------------------------
| 1                 | title |
-----------------------------
| 2                 | job   |
-----------------------------

contact_metadata

----------------------------
| contact_id | metadata_id |
----------------------------
| 1          | 1           |
----------------------------
| 1          | 2           |
----------------------------

metadata

-------------------------------------------
| metadata_id | metadefinition_id | value |
-------------------------------------------
| 1           | 1                 | mrs   |
-------------------------------------------
| 2           | 2                 | coder |
-------------------------------------------

So, for a single contact, I want to search on all this and obtain something like this:

-----------------------------------------------------
| contact_id | name | metadata.title | metadata.job |
-----------------------------------------------------
| 1          | foo  | mrs            | coder        |
-----------------------------------------------------

So now what I've tried so far. I can fetch the list of metadefinition in advance, that's not really an issue. So I build a query like this:

SELECT contact.*,m1.value AS `metadata.title` FROM contact
   LEFT JOIN contact_metadata ON contact.contact_id = contact_metadata.contact_id
   LEFT JOIN metadata m1 ON contact_metadata.metadata_id = m1.metadata_id AND m1.metadefinition_id = 1
  开发者_如何学JAVA GROUP BY contact_id

This works for a single metadefinition, I get something like this:

--------------------------------------
| contact_id | name | metadata.title |
--------------------------------------
| 1          | foo  | mrs            |
--------------------------------------

If I try with two, however:

SELECT contact.*,m1.value AS `metadata.title`,m2.value AS `metadata.job` FROM contact
   LEFT JOIN contact_metadata ON contact.contact_id = contact_metadata.contact_id
   LEFT JOIN metadata m1 ON contact_metadata.metadata_id = m1.metadata_id AND m1.metadefinition_id = 1
   LEFT JOIN metadata m2 ON contact_metadata.metadata_id = m2.metadata_id AND m2.metadefinition_id = 2
   GROUP BY contact_id

I get:

-----------------------------------------------------
| contact_id | name | metadata.title | metadata.job |
-----------------------------------------------------
| 1          | foo  | mrs            | NULL         |
-----------------------------------------------------

If I remove the GROUP BY clause, of course, I get:

-----------------------------------------------------
| contact_id | name | metadata.title | metadata.job |
-----------------------------------------------------
| 1          | foo  | mrs            | NULL         |
-----------------------------------------------------
| 1          | foo  | NULL           | coder        |
-----------------------------------------------------

I'm open to anything as long as the query times are relatively acceptable (considering the structure, if it takes 10 seconds for 100000 records it's better than nothing)

Will it be temporary tables, stored procedures, I don't care, as long as I don't have to change the actual database structure.

Trust me, when it'll be the time to remodel, it'll be my pleasure to take this whole thing down.

Is there a solution to this, is it possible?

Thanks in advance.


I'd use a pair of correlated sub-queries. Something like

SELECT c.*
  ,( Select value from metadata m1 
       INNER JOIN contact_metadata cm 
       on cm.metadata_id = m1.metadata_id 
     WHERE m1.metadefinition_id = 1 
       AND cm.contact_id = c.contact_id )AS `metadata.title`
  ,( Select value from metadata m1 
       INNER JOIN contact_metadata cm 
       ON cm.metadata_id = m1.metadata_id 
     WHERE m1.metadefinition_id = 2 
       AND cm.contact_id = c.contact_id )AS `metadata.job`
FROM contact c
WHERE c.contact_id = 1


You'll need to fetch each metadata item (title, job etc.) as a separate subquery, and then join on contact_id to bring these metadata items together for each contact. Like this:

SELECT contact.*, metadata_title, metadata_job FROM contact 
INNER JOIN 
(SELECT contact_id,m.value AS `metadata_title` FROM contact_metadata 
   LEFT JOIN metadata m ON contact_metadata.metadata_id = m.metadata_id AND m.metadefinition_id = 1) metaTitle ON metaTitle.contact_id=contact.contact_id
INNER JOIN 
(SELECT contact_id,m.value AS `metadata_job` FROM contact_metadata 
   LEFT JOIN metadata m ON contact_metadata.metadata_id = m.metadata_id AND m.metadefinition_id = 2) metaJob ON (metaJob.contact_id=contact.contact_id)

If you want to allow for the possibility of a client not having a particular metadata item, use LEFT JOINs instead, as at present, the query will only return clients that have both a job and a title defined.


Now that I've finally read the question properly, here's my two cents, for what's it's worth (no pun intended).

SELECT contact.contact_id, contact.name, m1.value AS 'metadata.title', m2.value AS 'metadata.job'     
FROM dbo.contact
INNER JOIN dbo.contact_metadata cm1 ON cm1.contact_id = cm1.contact_id
INNER JOIN dbo.metadata m1 ON cm1.metadata_id = m1.metadata_id AND m1.metadefinition_id = 1
INNER JOIN dbo.contact_metadata cm2 ON contact.contact_id = cm2.contact_id
INNER JOIN dbo.metadata m2 ON cm2.metadata_id = m2.metadata_id AND m2.metadefinition_id = 2
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜