MySQL, three tables: Select all rows in right table including rows that are not mapped in middle table
My schema are as follows:
Sites S
| S.Id | S.Url |
| 1 | a.com |
| 2 | b.edu |
| 3 | c.org |
SiteFeatures SF
| SF.SiteId | SF.FeatureID |
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 1 |
| 2 | 2 |
| 2 | 3 |
| 2 | 4 |
| 3 | 2 |
| 3 | 3 |
Features F
| F.Id | F.FeatureName |
| 1 | apple |
| 2 | banana |
| 3 | cherry |
| 4 | diaper |
| 5 | egg |
| 6 | fish |
I want to select all sites, mapped to all features, including features missing from the middle join table. For features that have no entry in the join table, I want to display a "0". For features that exist in the join table, I want a "1".
So the results will look like this:
| SiteId | SiteURL | FeatureName | Enabled |
| 1 | a.com | apple | 1 |
| 1 | a.com | banana | 1 |
| 1 | a.com | cherry | 1 |
| 1 | a.com | diaper | 0 |
| 1 | a.com | egg | 0 |
| 1 | a.com | fish | 0 |
| 2 | b.edu | apple | 1 |
| 2 | b.edu | banana | 1 |
| 2 | b.edu | cherry | 1 |
| 2 | b.edu | diaper | 1 |
| 2 | b.edu | egg | 0 |
| 2 | b.edu | fish | 0 |
| 3 | c.o开发者_运维技巧rg | apple | 0 |
| 3 | c.org | banana | 1 |
| 3 | c.org | cherry | 0 |
| 3 | c.org | diaper | 1 |
| 3 | c.org | egg | 0 |
| 3 | c.org | fish | 0 |
-- EDIT -- Additional Information.
I originally created a pivot table using this article:
http://dev.mysql.com/tech-resources/articles/wizard/print_version.html
Based on that article, I wrote a SQL statement to dynamically generate a SQL query that generates the pivot table.
That statement looked like this:
SELECT
CONCAT( ", SUM(IF( F.FeatureName = '" , F.FeatureName , "', 1,0 ))" , " AS `" , F.FeatureName , "` ") AS CutNPaste
FROM
Features F
WHERE 1
GROUP BY F.FeatureName
ORDER BY F.FeatureName
-- END
The second SQL statement was as follows:
SELECT
, S.Url
/* This section was dynamically generated, and copied into this SELECT statement */
, SUM(IF( F.FeatureName = 'apple', 1,0 )) AS `apple`
, SUM(IF( F.FeatureName = 'banana', 1,0 )) AS `banana`
, SUM(IF( F.FeatureName = 'cherry', 1,0 )) AS `cherry`
, SUM(IF( F.FeatureName = 'diaper', 1,0 )) AS `diaper`
, SUM(IF( F.FeatureName = 'egg', 1,0 )) AS `egg`
, SUM(IF( F.FeatureName = 'fish', 1,0 )) AS `fish`
/* END of dynamic part */
FROM
Sites S
LEFT OUTER JOIN SiteFeatures SF ON S.Id = SF.SiteId
LEFT OUTER JOIN Features F ON SF.FeatureId = F.Id
WHERE 1
AND SF.FeatureId = F.Id
AND S.Enabled = 1
GROUP BY S.Url
-- END
The results looked like this:
| Url | apple | banana | cherry | diaper | egg | fish |
| a.com | 1 | 1 | 1 | 0 | 0 | 0 |
| b.edu | 1 | 1 | 1 | 1 | 0 | 0 |
| c.org | 0 | 1 | 0 | 1 | 0 | 0 |
I tried to repurpose the SQL and the concepts in these two statements, but I am at a loss.
CROSS JOIN
can be used here. (The first query was posted first by @nick rulez):
SELECT s.Id AS SiteId
, s.Url AS SiteURL
, f.FeatureName
, CASE WHEN sf.SiteID = s.Id
THEN 1
ELSE 0
END AS Enabled
FROM
Sites AS s
CROSS JOIN
Features AS f
LEFT JOIN
SiteFeatures AS sf
ON sf.SiteID = s.Id
AND sf.FeatureID = f.Id
SELECT s.Id AS SiteId
, s.Url AS SiteURL
, f.FeatureName
, CASE WHEN EXISTS
( SELECT *
FROM SiteFeatures AS sf
WHERE sf.SiteID = s.Id
AND sf.FeatureID = f.Id
)
THEN 1
ELSE 0
END AS Enabled
FROM
Sites AS s
CROSS JOIN
Features AS f
SELECT s.Id AS SiteId
, s.Url AS SiteURL
, f.FeatureName
, ( SELECT COUNT(*)
FROM SiteFeatures AS sf
WHERE sf.SiteID = s.Id
AND sf.FeatureID = f.Id
) AS Enabled
FROM
Sites AS s
CROSS JOIN
Features AS f
DISCLAIMER: The following solution is written in MS SQL-Server 2008 TSQL.
I don't believe anything I did does not exist in MySQL, but if anything I use is inappropriate in MySQL please let me know so I can correct it. If anyone wishes to edit and correct the syntax to MySQL, you're more than welcome to. I don't know what I need to escape with backticks and whatnot, so I don't want to attempt it and create poor MySQL code out of proper MS Server code.
I also don't pretend to claim that my solution is efficient or elegant, as I am in no way a SQL expert.
The query uses a join to create all possible combinations of Site + Feature. It then checks if that combination exists in the SiteFeature table
--table creation
declare @Sites Table(
[Id] int not null,
[Url] varchar(30)
)
INSERT INTO @Sites ([Id],[Url])
VALUES (1,'a.com'),(2,'b.edu'),(3,'c.org')
declare @Features Table(
[Id] int not null,
[Name] varchar(30)
)
INSERT INTO @Features ([Id],[Name])
VALUES (1,'apple'),(2,'banana'),(3,'cherry'),(4,'diaper'),(5,'egg'),(6,'fish')
declare @SiteFeatures Table(
[SiteId] int not null,
[FeatureID] int not null
)
INSERT INTO @SiteFeatures ([SiteId],[FeatureID])
VALUES (1,1),(1,2),(1,3),(2,1),(2,2),(2,3),(2,4),(3,2),(3,3)
--QUERY HERE
SELECT [SiteId], [SiteURL], [FeatureName], [Enabled] =
CASE
WHEN EXISTS
(SELECT *
FROM @SiteFeatures SF
WHERE SF.SiteId = tmp.SiteId
AND SF.FeatureID = tmp.FeatureId)
THEN (1)
ELSE (0)
END
FROM
(SELECT S.Id as [SiteId],
S.Url as [SiteURL],
F.Id as [FeatureId],
F.Name as [FeatureName]
FROM @Sites S
LEFT JOIN @Features F ON (1 = 1)) as tmp
select s.id,s.url,f.featurename,if(sf.featureid is not null,1,0) as enabled
from features as f
cross join sites as s
left join sitefeatures as sf on sf.siteid = s.id and sf.featureid = f.id
order by s.url,f.featurename;
+----+-------+-------------+---------+
| id | url | featurename | enabled |
+----+-------+-------------+---------+
| 1 | a.com | apple | 1 |
| 1 | a.com | banana | 1 |
| 1 | a.com | cherry | 1 |
| 1 | a.com | diaper | 0 |
| 1 | a.com | egg | 0 |
| 1 | a.com | fish | 0 |
| 2 | b.edu | apple | 1 |
| 2 | b.edu | banana | 1 |
| 2 | b.edu | cherry | 1 |
| 2 | b.edu | diaper | 1 |
| 2 | b.edu | egg | 0 |
| 2 | b.edu | fish | 0 |
| 3 | c.org | apple | 0 |
| 3 | c.org | banana | 1 |
| 3 | c.org | cherry | 1 |
| 3 | c.org | diaper | 0 |
| 3 | c.org | egg | 0 |
| 3 | c.org | fish | 0 |
+----+-------+-------------+---------+
18 rows in set (0.00 sec)
精彩评论