简体   繁体   中英

How do I write a mysql query to get a list of records from one table with columns concatenated from multiple other tables

My question is similar to this one: MySQL concatenate values from one table into a record of another

But it's not the same, I think because I'm trying to make use of multiple concatenated columns from several other tables.

Here are my tables:

CREATE TABLE `Albums` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `userSettingsId` bigint(20) NOT NULL,
  `name` varchar(100) NOT NULL,
  `description` text DEFAULT NULL,
  `isFavorite` tinyint(1) NOT NULL DEFAULT 0,
  `isPublic` tinyint(1) NOT NULL DEFAULT 0,
  `created` datetime NOT NULL DEFAULT current_timestamp(),
  `lastEdited` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `Albums_UN` (`userSettingsId`,`name`),
  CONSTRAINT `Albums_FK` FOREIGN KEY (`userSettingsId`) REFERENCES `UserSettings` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=40 DEFAULT CHARSET=utf8mb4;

CREATE TABLE `AlbumsImages` (
  `albumsId` bigint(20) NOT NULL,
  `imagesId` bigint(20) NOT NULL,
  `isCoverImage` tinyint(1) NOT NULL DEFAULT 0,
  PRIMARY KEY (`albumsId`,`imagesId`),
  KEY `AlbumsImages_FK` (`imagesId`),
  CONSTRAINT `AlbumsImages_FK` FOREIGN KEY (`imagesId`) REFERENCES `Images` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `AlbumsImages_FK_1` FOREIGN KEY (`albumsId`) REFERENCES `Albums` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `Collaborators` (
  `albumsId` bigint(20) NOT NULL,
  `access` enum('view','put','edit') NOT NULL DEFAULT 'view',
  `email` varchar(100) NOT NULL,
  PRIMARY KEY (`albumsId`,`email`),
  CONSTRAINT `Collaborators_FK_1` FOREIGN KEY (`albumsId`) REFERENCES `Albums` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `Images` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `src` longtext NOT NULL,
  `fileName` varchar(100) NOT NULL,
  `alt` varchar(100) NOT NULL,
  `userSettingsId` bigint(20) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `Images_UN_FileName_UserSettingsId` (`fileName`,`userSettingsId`),
  KEY `Images_FK` (`userSettingsId`),
  CONSTRAINT `Images_FK` FOREIGN KEY (`userSettingsId`) REFERENCES `UserSettings` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=91 DEFAULT CHARSET=utf8 COMMENT='All images in the application';

CREATE TABLE `UserSettings` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `email` varchar(100) NOT NULL,
  `firstName` varchar(100) NOT NULL,
  `lastName` varchar(100) NOT NULL,
  `password` text NOT NULL,
  `isDarkThemeEnabled` tinyint(1) NOT NULL DEFAULT 1,
  `sessionId` varchar(255) DEFAULT NULL,
  `profilePicture` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UserSettings_Email_UN` (`email`),
  UNIQUE KEY `UserSettings_SessionId_UN` (`sessionId`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8 COMMENT='User Settings and Info'

NOTE: The Collaborators table doesn't use a foreign key to UserSettings.id because a collaborator doesn't necessarily have to have an account.

So, I want to get all albums and ancillary information about them for a particular user. Something like this:

[
  {
    id: 1,
    name: 'Album Name',
    description: 'Description',
    isFavorite: 0,
    created: '2021-04-26 23:14:05',
    lastEdited: '2021-04-27 19:12:02',
    images: [
      {
        fileName: 'image.jpg',
        alt: 'Image Title',
        src: 'www.image.com/image.jpg',
        isCoverImage: 0,
      }, //...etc
    ],
    collaborators: [
      {
        email: 'someone@example.com',
        firstName: null,
        lastName: null,
        id: null,
        access: 'put',
      },
      {
        email: 'someoneelse@example.com',
        firstName: 'someone',
        lastName: 'else',
        id: 14,
        access: 'view',
      }, //...etc
    ],
  }, //...etc
]

And here is the query I'm currently working with.

SELECT 
    a.id,
    a.name,
    a.description,
    a.isFavorite,
    a.created,
    a.lastEdited,
    concat('[', group_concat(json_object(
        'fileName', i.fileName,
        'alt', i.alt,
        'src', i.src,
        'isCoverImage', ai.isCoverImage
    )), ']') as images,
    concat('[', group_concat(json_object(
        'email', c.email,
        'firstName', u.firstName,
        'lastName', u.lastName,
        'id', u.id,
        'access', c.access
    )), ']') as collaborators
from Albums a 
    left join AlbumsImages ai
        on a.id=ai.albumsId 
    left join Images i 
        on i.id=ai.imagesId 
    left join Collaborators c 
        on c.albumsId = a.id 
    left join UserSettings u
        on c.email = u.email 
where a.userSettingsId=?
group by id;

Aaand it does work... sort of. I get all the albums and all the information for them, but the collaborators are duplicated by the number of images and vice versa. As a band-aid for right now, I've got some deduplication code that runs right after the query, but that's obviously hacky and not something I want to go with long term.

Is there a way to fix this to do what I want, or am I an idiot for wanting to get all this information in a single query in the first place?

Thanks!

So you have a Cartesian product between Collaborators and Images. Thus both are multiplied by the number of results in the other.

You could run multiple queries and then write application code to append the results into your greater JSON document.

Or you could use correlated subqueries:

SELECT 
    a.id,
    a.name,
    a.description,
    a.isFavorite,
    a.created,
    a.lastEdited,
    concat('[', (
      select group_concat(json_object(
        'fileName', i.fileName,
        'alt', i.alt,
        'src', i.src,
        'isCoverImage', ai.isCoverImage))
      from Images i where i.id=ai.imagesId
    ), ']') as images,
    concat('[', (
      select group_concat(json_object(
        'email', c.email,
        'firstName', u.firstName,
        'lastName', u.lastName,
        'id', u.id,
        'access', c.access))
      from Collaborators c
      left join UserSettings u 
        on c.email = u.email 
      where c.albumsId=a.id
    ), ']') as collaborators
from Albums a 
    left join AlbumsImages ai
        on a.id=ai.albumsId 
where a.userSettingsId=?
group by id;

(not tested)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM