Home > Software engineering >  MySQL query to retrieve related data from another table
MySQL query to retrieve related data from another table

Time:06-05

I'm not too experienced with SQL queries (most times I used query builder from frameworks, like CodeIgniter and Laravel). Now, I need to retrieve data from a relational DB which have two tables: one for entity entries and other for complemental data of entities. For example, see below:

tbl_posts

id name slug
1 Lorem ipsum lorem-ipsum
2 Testing post testing-post
3 Hello world hello-world

tbl_posts_meta

id post_id key value
1 1 first_name John
2 1 last_name Doe
3 1 rating 5
4 2 parent 1
5 2 rating 3
6 3 rating 4

In this example, I need to retrieve an array of objects in this format:

[
  {
    id: 1,
    name: "Lorem ipsum",
    slug: "lorem-ipsum",
    data: {
      first_name: "John",
      last_name: "Doe",
      rating: 5,
    }
  },
  {
    id: 2,
    name: "Testing post",
    slug: "testing-post",
    data: {
      parent: 1,
      rating: 3,
    }
  },
  {
    id: 3,
    name: "Hello World",
    slug: "hello-world",
    data: {
      rating: 4,
    }
  },
]

I've tried using subquery to handle this, but I'm reveiving Cardinality violation: 1241 Operand should contain 1 column(s) error. My query looks like this:

SELECT *,(SELECT * FROM tbl_posts_meta WHERE "post_id" = "tbl_posts"."id") as data FROM tbl_posts;

I've already tried using JOIN, but the result looks more away from expected (I have one "key" property and one "value" property in result, containing the last found entry in tbl_posts_meta).

SELECT * FROM tbl_posts INNER JOIN tbl_posts_meta ON tbl_posts_meta.post_id = tbl_posts.id;

Is there someway I can retrieve desired results with one query? I don't want to do this by applogic (like first retrieving data from tbl_posts and appending another query on "data" property, that returns all data from tbl_posts_meta), since this way may cause database overload.

Thanks!

CodePudding user response:

The code is not shown right in the comment so here it is for you to check, sorry I can't contribute more to the theoretical debate:

select 
tp.id,
tp.name,
tp.slug,
(select group_concat(`key`, "=",value separator ';' ) from tbl_posts_meta where post_id= tp.id )as datas
from tbl_posts tp

CodePudding user response:

You can follow these simple steps to get to your needed json:

  • join the two tables to have centralized data
  • generate a tbl_posts_meta JSON-like string by aggregating on your columns of interest (tbl_posts.id, tbl_posts.name, tbl_posts.slug) using a GROUP_CONCAT aggregation function. Since your keys can either be integers or strings, you need to do a quick check using the CAST(<col> AS UNSIGNED), which will return 0 if the string is not a number.
  • use the JSON_OBJECT function to transform each row into a JSON
  • use the JSON_ARRAYAGG function to merge all jsons into a single json.
WITH cte AS(
    SELECT p.id,
           p.name, 
           p.slug,
           CONCAT('{',
                  GROUP_CONCAT(
                      CONCAT('"', 
                             m.key_, 
                             '": ', 
                             IF(CAST(m.value_ AS UNSIGNED)=0, 
                                CONCAT('"', m.value_, '"'),
                                CAST(m.value_ AS UNSIGNED))  ) 
                      ORDER BY m.id                             ),
                  '}') AS data_
    FROM       tbl_posts      p
    INNER JOIN tbl_posts_meta m
            ON p.id = m.post_id
    GROUP BY p.id,
             p.name, 
             p.slug
)
SELECT JSON_ARRAYAGG(
           JSON_OBJECT("id",   id, 
                       "name", name, 
                       "slug", slug, 
                       "data", CAST(data_ AS JSON))) 
FROM cte

Check the demo here.

  • Related