Search code examples
mysqlsqlselectsubquerygroup-concat

Subqueries in SELECT clause


I need to SELECT from multiple tables to get a result table like the following:

+--------+-------+-------------------+----------------------+
| itemID | level | studyPhraseString | meaningPhraseStrings |
+--------+-------+-------------------+----------------------+
| 1      | 4     | la maison         | house                |
+--------+-------+-------------------+----------------------+
| 2      | 3     | voler             | to fly,to steal      |
+--------+-------+-------------------+----------------------+

Note: studyPhraseString and meaningPhraseStrings should be concatenated strings made up of values from the word table.

My tables:

item

CREATE TABLE `item` (
`itemID` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
`groupID` bigint(11) unsigned NOT NULL,
`studyLang` varchar(5) NOT NULL,
`meaningLang` varchar(5) NOT NULL,
`studyPhraseID` bigint(11) unsigned NOT NULL,
PRIMARY KEY (`itemID`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=latin1;

meaning

CREATE TABLE `meaning` (
  `meaningID` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
  `itemID` bigint(11) unsigned NOT NULL,
  `meaningPhraseID` bigint(11) unsigned NOT NULL,
  `meaningIndex` int(11) unsigned NOT NULL,
  PRIMARY KEY (`meaningID`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=latin1;

phrase

CREATE TABLE `phrase` (
  `phraseID` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
  `phraseLang` varchar(5) NOT NULL DEFAULT '',
  PRIMARY KEY (`phraseID`)
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=latin1;

phrase_word

CREATE TABLE `phrase_word` (
  `phrase_wordID` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
  `phraseID` bigint(11) unsigned NOT NULL,
  `wordID` bigint(11) unsigned NOT NULL,
  `wordIndex` int(11) unsigned NOT NULL,
  PRIMARY KEY (`phrase_wordID`)
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=latin1;

status

CREATE TABLE `status` (
  `statusID` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
  `itemID` bigint(11) unsigned NOT NULL,
  `level` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `nextReviewTime` int(11) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`statusID`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=latin1;

word

CREATE TABLE `word` (
  `wordID` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
  `string` varchar(64) NOT NULL DEFAULT '',
  PRIMARY KEY (`wordID`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1;

I have written the following SELECT statement:

SELECT item.itemID, status.level, 
(SELECT GROUP_CONCAT(word.string ORDER BY phrase_word.wordIndex SEPARATOR ' ')
FROM word INNER JOIN phrase_word ON word.wordID=phrase_word.wordID
INNER JOIN item AS subItem ON phrase_word.phraseID=subItem.studyPhraseID 
WHERE subItem.itemID=item.itemID
GROUP BY subItem.itemID
) AS studyPhraseString
FROM item INNER JOIN status ON item.itemID=status.itemID
WHERE item.groupID=5
ORDER BY status.statusID DESC

This works but does not include the meaningPhraseString. I can’t figure out how to concat the words into phrases AND concat the phrases into one string separated by ,

I have tried nested GROUP_CONCAT clauses with no success (subquery returns more than 1 row):

The question:

How should this statement be written to include meaningPhraseStrings? Thanks in advance.

PS: I'd like this to be a single query

I have tried the following but it fails. Why? It has two levels of correlated queries.

SELECT 
item.itemID, 
status.level, 
(
    SELECT GROUP_CONCAT(word.string ORDER BY phrase_word.wordIndex SEPARATOR ' ')
    FROM word INNER JOIN phrase_word ON word.wordID=phrase_word.wordID
    INNER JOIN item AS subItem ON phrase_word.phraseID=subItem.studyPhraseID 
    WHERE subItem.itemID=item.itemID
    GROUP BY subItem.itemID
) AS studyPhraseString, 

(
    SELECT GROUP_CONCAT(meaningPhraseString SEPARATOR '.') 
    FROM (
        (
            SELECT GROUP_CONCAT(word.string ORDER BY phrase_word.wordIndex SEPARATOR ' ') AS meaningPhraseString
            FROM word INNER JOIN phrase_word ON word.wordID=phrase_word.wordID
            INNER JOIN meaning ON phrase_word.phraseID=meaning.meaningPhraseID
            INNER JOIN item AS subItem ON meaning.itemID=subItem.itemID 
            
            WHERE subItem.itemID=item.itemID #This fails
            
            GROUP BY meaning.meaningPhraseID
        ) AS meaningPhraseString
    )
) AS meaningPhraseStrings

FROM item INNER JOIN status ON item.itemID=status.itemID
WHERE item.groupID=5
ORDER BY item.itemID DESC

Sample data:

INSERT INTO `status` VALUES (22,22,0,0),(23,23,0,0),(24,25,0,0),(25,24,0,0),(26,26,0,0);
INSERT INTO `item` VALUES (22,5,'fr','en',49),(23,5,'fr','en',48),(24,5,'fr','en',56),(25,5,'fr','en',50),(26,5,'fr','en',57);
INSERT INTO `meaning` VALUES (27,22,51,0),(28,23,52,0),(29,23,54,1),(30,24,59,0),(31,24,61,1),(32,25,53,0),(33,25,55,1),(34,26,58,0),(35,26,60,1);
INSERT INTO `phrase` VALUES (48,'fr'),(49,'fr'),(50,'fr'),(51,'en'),(52,'en'),(53,'en'),(54,'en'),(55,'en'),(56,'fr'),(57,'fr'),(58,'en'),(59,'en'),(60,'en'),(61,'en');
INSERT INTO `word` VALUES (46,'l\'autobus'),(47,'bus'),(48,'pourquoi'),(49,'comment'),(50,'why'),(51,'ça'),(52,'va?'),(53,'voler'),(54,'incroyable'),(55,'how'),(56,'is'),(57,'to'),(58,'are'),(59,'incredible'),(60,'that?'),(61,'you?'),(62,'fly'),(63,'amazing'),(64,'hi'),(65,'steal');
INSERT INTO `phrase_word` VALUES (86,49,46,0),(87,51,47,0),(88,48,48,0),(89,50,49,0),(90,52,50,0),(91,54,50,0),(92,50,51,1),(93,50,52,2),(94,57,53,0),(95,53,55,0),(96,56,54,0),(97,54,56,1),(98,53,58,1),(99,58,57,0),(100,59,59,0),(101,54,60,2),(102,53,61,2),(103,58,62,1),(104,61,63,0),(105,60,57,0),(106,55,64,0),(107,60,65,1);

Final answer:

SELECT i.itemID, 
        s.level,
        sp.studyPhraseString,
        GROUP_CONCAT(mp.meaningPhraseString
                     SEPARATOR ', ') AS meaningPhraseStrings            
   FROM item AS i
   JOIN meaning AS m ON i.itemID = m.itemID
   JOIN status AS s ON i.itemID = s.itemID
   JOIN ( 
       SELECT subItem.studyPhraseID,
              GROUP_CONCAT(word.string 
                           ORDER BY phrase_word.wordIndex 
                           SEPARATOR ' ') AS studyPhraseString
         FROM word 
         JOIN phrase_word
           ON word.wordID=phrase_word.wordID
         JOIN item AS subItem 
           ON phrase_word.phraseID=subItem.studyPhraseID 
     GROUP BY subItem.studyPhraseID
        ) AS sp ON i.studyPhraseID = sp.studyPhraseID   
   JOIN ( 
       SELECT meaning.meaningPhraseID,
              GROUP_CONCAT(word.string 
              ORDER BY phrase_word.wordIndex
              SEPARATOR ' ') AS meaningPhraseString
         FROM word 
         JOIN phrase_word ON word.wordID=phrase_word.wordID
         JOIN meaning ON phrase_word.phraseID=meaning.meaningPhraseID
         JOIN item AS subItem ON meaning.itemID=subItem.itemID 
     GROUP BY meaning.meaningPhraseID
        ) AS mp ON m.meaningPhraseID = mp.meaningPhraseID
  GROUP BY i.itemID, s.level, sp.studyPhraseString
  ORDER BY i.itemID, s.level, sp.studyPhraseString

Solution

  • Your question seems to be this:

    how to concat the words into phrases AND concat the phrases into one string

    Let's break it down. You need to join together five tables. Three of them are physical tables, namely item, meaning, and status. From those tables you get the references to the result set items you need called itemID and level, and you get the relationship between items and their meanings.

    The other two tables you need are virtual tables (that is, subqueries). One of these gives you your French language phrases, and the other gives you your English-language translations.

    Let us create the two queries for the virtual tables. Let's put the words into phrases first. A query like this achieves that goal.

     SELECT subItem.studyPhraseID,
            GROUP_CONCAT(word.string 
                         ORDER BY phrase_word.wordIndex 
                         SEPARATOR ' ') AS studyPhraseString
       FROM word 
      INNER JOIN phrase_word ON word.wordID=phrase_word.wordID
      INNER JOIN item AS subItem ON phrase_word.phraseID=subItem.studyPhraseID 
      GROUP BY subItem.studyPhraseID
    

    This gives you a resultset table of phrase ID numbers and the phrases. Here's a SQL fiddle for this one based on your samples. http://sqlfiddle.com/#!2/11ae2/9/0

    Then, create a similar query giving you the meaningPhraseString values.

     SELECT meaning.meaningPhraseID,
            GROUP_CONCAT(word.string 
            ORDER BY phrase_word.wordIndex
            SEPARATOR ' ') AS meaningPhraseString
       FROM word 
      INNER JOIN phrase_word ON word.wordID=phrase_word.wordID
      INNER JOIN meaning ON phrase_word.phraseID=meaning.meaningPhraseID
      INNER JOIN item AS subItem ON meaning.itemID=subItem.itemID 
      GROUP BY meaning.meaningPhraseID
    

    This gives a list of ids and meaning phrases. Here's the fiddle. http://sqlfiddle.com/#!2/11ae2/6/0

    So, we're going to need a five-way join (three physical tables and two subqueries) to get to our final result set. In summary, it will look like this:

     SELECT i.itemID, 
            s.level,
            sp.studyPhraseString,
            mp.meaningPhraseString            
       FROM item AS i
       JOIN meaning AS m ON i.itemID = m.itemID
       JOIN status AS s ON i.itemID = s.itemID
       JOIN ( 
               /* the studyPhrase subquery */ 
            ) AS sp ON i.studyPhraseID = sp.studyPhraseID   
       JOIN ( 
               /* the meaningPhrase subquery */ 
            ) AS mp ON m.meaningPhraseID = mp.meaningPhraseID
    

    The trick here is that you can use a query (or virtual table) and a physical table interchangeably. So when you need to summarize a bunch of tables, you create a query to do that and then paste it into JOIN (/*query*/) AS alias.

    Finally, you need to create the comma-joined strings (e.g. to fly, to steal) by adding yet another GROUP_CONCAT() and GROUP BY to your query. The end result is then

     SELECT i.itemID, 
            s.level,
            sp.studyPhraseString,
            GROUP_CONCAT(mp.meaningPhraseString
                         SEPARATOR ', ') AS meaningPhraseStrings            
       FROM item AS i
       JOIN meaning AS m ON i.itemID = m.itemID
       JOIN status AS s ON i.itemID = s.itemID
       JOIN ( 
               /* the studyPhrase subquery */ 
            ) AS sp ON i.studyPhraseID = sp.studyPhraseID   
       JOIN ( 
               /* the meaningPhrase subquery */ 
            ) AS mp ON m.meaningPhraseID = mp.meaningPhraseID
      GROUP BY i.itemID, s.level, sp.studyPhraseString
      ORDER BY i.itemID, s.level, sp.studyPhraseString
    

    And that is your query. http://sqlfiddle.com/#!2/11ae2/16/0 It definitely takes advantage of the Structured in Structured Query Language.