MySQL JOIN wrapping into an array - mysql

I have some minor problem with an SQL query. I have to select data from multiple tables like:
offers:
| id | offer | info
| 1 | City break | information
pictures:
| id | id_offer | picture_name | title
| 1 | 1 | bucharest.jpg | Bucharest
| 2 | 1 | london.jpg | London
sql query:
SELECT offers.* as t1, pictures.* as t2
FROM offers
JOIN t2 ON t1.id=t2.id_offer
WHERE t1.id = '1'
The code is much larger but I don't understand how to wrap results from t2 into an array. Because the length of the array returned is made by t2 which is the pictures table. This will return an array with 2 objects.
It is possible to return one object in the array with both pictures in it?

MySQL does not support array datatypes.
You can return a comma separated list of values instead:
SELECT o.*, GROUP_CONCAT(picture_name ORDER BY p.id)
FROM offers o
JOIN pictures p
ON p.id_offer = o.id
GROUP BY
o.id

Arrays doesn't exist in mysql.
But you can use GROUP_CONCAT to return all images in comma separated list
SELECT offers.*, GROUP_CONCAT(t2.picture_name) AS pictures
FROM offers AS t1
JOIN pictures AS t2 ON t1.id=t2.id_offer
WHERE t1.id = '1'
GROUP BY t1.id

Related

How do I join with multiple SQL tables and return one row per base table record with the correct GROUP_CONCAT data?

I have a situation where I want to join multiple SQL tables and get back one row per record in the base table as well as GROUP_CONCAT the other table data together with |. Unfortunately, with the query method I'm currently using, I'm getting back undesired multiplicity in the GROUP_CONCAT data and I don't know how to solve it.
I have the following basic DB structure:
things
id | name
1 | Some Thing
2 | Some Other Thing
items
id | name
1 | Blob
2 | Starfish
3 | Wrench
4 | Stereo
users
id | name
1 | Alice
2 | Bill
3 | Charlie
4 | Daisy
things_items
thing_id | item_id
1 | 1
1 | 2
2 | 3
2 | 4
things_users
thing_id | user_id
1 | 1
1 | 2
1 | 3
2 | 4
And I would ideally like to write a query that gets back the following for the Some Thing row in the things table:
Some Thing | Blob|Starfish | Alice|Bill|Charlie
However, what I'm getting back is the following:
Some Thing | Blob|Blob|Blob|Starfish|Starfish|Starfish | Alice|Alice|Bill|Bill|Charlie|Charlie
And this is the query I'm using:
SELECT things.name,
GROUP_CONCAT(items.name SEPARATOR '|')
GROUP_CONCAT(users.name SEPARATOR '|')
FROM things
JOIN things_items ON things.id = things_items.thing_id
JOIN items ON things_items.item_id = items.id
JOIN things_users ON things.id = things_users.thing_id
JOIN users ON things_items.user_id = users.id
GROUP BY things.id;
How should I change the query to get the data back the way I'd like to and avoid the multiplying of the GROUP_CONCAT data? Thank you.
You are concatenating along two separate dimensions. The simplest solution is DISTINCT:
SELECT t.name,
GROUP_CONCAT(DISTINCT i.name SEPARATOR '|')
GROUP_CONCAT(DISTINCT u.name SEPARATOR '|')
FROM things t JOIN
things_items ti
ON t.id = ti.thing_id JOIN
items i
ON ti.item_id = i.id JOIN
things_users tu
ON t.id = tu.thing_id JOIN
users u
ON tu.user_id = u.id
GROUP BY t.id;
Note the above filters out things that have either no items or no users.
The above will work fine if there are a handful of items and users for each thing. As the numbers grow, the performance gets worse because it generates a Cartesian product for each thing.
That can be solved by aggregating before joining:
SELECT t.name, i.items, u.users
FROM things t JOIN
(SELECT ti.thing_id, GROUP_CONCAT(i.name SEPARATOR '|') as items
FROM things_items ti JOIN
items i
ON ti.item_id = i.id
GROUP BY ti.thing_id
) i
ON t.id = ti.thing_id JOIN
(SELECT tu.user_id, GROUP_CONCAT(DISTINCT u.name SEPARATOR '|') as users
FROM things_users tu JOIN
users u
ON tu.user_id = u.id
GROUP BY tu.user_id
) tu
ON t.id = tu.thing_id ;
You can replace the outer JOINs with LEFT JOIN if you want all things, even those with no items or names.

select same column twice having different values for single id using mysql

I have these three tables movies, category and relationship as shown below.
movies--
-----------------------
id|name|duration|
1 |x |5 mins |
2 |y |10 mins |
----------------------
category--
-----------------------
id|type |value |
1 |genre |action |
2 |language|english |
3 |genre |thriller |
4 |language|spanish |
------------------------
relationship--
id| movie_id|category_id|
1 |1 | 2 |
2 |1 | 3 |
------------------------------
i want a query that will fetch both genre and language for the movie in a single query.
below is the expected output.
name|duration|language|genre |
x |5 mins |english |thriller|
--------------------------------
in short i want to use the type column twice.
Please help
You need mysql pivot table for that. That is turn some columns into row data. The following query will produce what you want:
SELECT
m.name,
m.duration,
MAX(IF(c.type = 'language', c.value, NULL)) AS language,
MAX(IF(c.type = 'genre', c.value, NULL)) AS genre
FROM movies AS m
INNER JOIN relationship AS r ON m.id = r.movie_id
INNER JOIN category AS c ON r.category_id = c.id
WHERE m.name = 'x'
GROUP BY m.name;
That will produce:
| name | duration | language | genre |
| x | 5 mins | english | thriller |
See DEMO
Step 1: Join all the three table together. Now you get all the category infos for each movie.
Step 2: Select what you want from the big combined table.
Step 3: Use two subquery to satisfy your special needs for language and genre.
Step 4: Add a LIMIT 1 to avoid redundant records.
The final query might be something like this:
SELECT name, duration, (SELECT value FROM t WHERE type = 'language' AND name = 'x') AS language, (SELECT value FROM t WHERE type = 'genre' AND name = 'x') AS genre
FROM
(
SELECT m.name, m.duration, c.type FROM movies AS m
JOIN relationship AS r ON m.id = r.movie_id
JOIN category AS c ON r.category_id = c.id
) AS t LIMIT 1;
Note:
Replace your own query condition for WHERE clause.
This query might not be strictly syntax correct. Please fix it by yourself.
One method uses two joins, one for each type:
select m.*, cl.value as language, cg.language as genre
from movies m join
relationships r
on m.id = r.movie_id left join
categories cl
on cl.id = r.category_id and type = 'language' left join
categories cg
on cg.id = r.category_id and type = 'genre';
Note that movies typically have only one language, but they can have multiple genres. If this is the case you will get a separate row for each genre.

Joining 2 tables with same id PDO

I am sorry this is not a high quality question and I know I am risking downvotes, but I am trying to learn as I go. I am currently working on a side project and stumbled into a situation I am not sure of.
I have two tables and need to call the data from both sharing the same id number (different names)
I will now attempt to give an example
Table 1
| psid | idd |
| 1 | 999 |
| 2 | 42 |
Table 2
| aid | other |
| 999 | hello world |
| 42 | welcome |
I am trying to link idd and aid whilst displaying all rows from table one
Example
id = 1 / Title : hello world
id = 2 / Title : welcome
I am not sure if this can be achieved with a single query to the database I have tried adding a second but it goes in a nonstop loop.
I have not done much searching as not sure what to search for.
Thanks and sorry
Cartesian Join
SQLFiddle
select
table1.*,
table2.*
from
table1,
table2
where
table1.idd = table2.aid and
table1.idd = :id
Or Left Join
SQL Fiddle
select
t1.*,
t2.*
from
table1 t1
left join
table2 t2
on
t1.idd = t2.aid
where
t1.idd = :id
SELECT table1.psid, table2.other FROM table1
JOIN table2 ON table1.idd = table2.aid
WHERE table1.idd= 'X' AND table2.aid = 'X'
this should JOIN the two tables together and by specifying the matching id's for each table in the WHERE clause should get the relevant information.
EDIT fixed SQL

MySQL - select 3 tables with correct left join syntax

Hope you can help me with correct syntax of a SQL query (using MySQL 5.5.25).
I have 3 tables:
data
data_tmp
users
data table is empty - has it's own structure but no rows
data:
id | name | who
----------------
data_tmp:
id | cars | who
---------------
1 | lambo| 2
users
who | name |
------------
2 | john
My query is:
SELECT DISTINCT
users.name,
(SELECT count(id) FROM data WHERE who = 1) as number,
data_tmp.cars
FROM
users, data, data_tmp
WHERE
users.who = 2
AND data_tmp.who = 2
AND data.who = 2
This of course returns an empty result (there is no row that suits to all parameters because data is empty).
What I would like to achieve is:
users.name | number | data_tmp.cars |
-------------------------------------
john | 0 | lambo |
I am sure I have to - in some way - use LEFT JOIN but can't find correct syntax. Hope you can help me.
Kalreg
give this a try (without using subquery)
SELECT a.name, b.cars, count(c.id) as number
FROM users a
INNER JOIN data_tmp b
on a.who = b.who
LEFT JOIN data c
on a.who = c.who AND
a.name = c.name
WHERE a.who = 2
GROUP BY a.name, b.cars
this works on different servers:
MSSQL SERVER # SQLFIDDLE
MYSQL # SQLFIDDLE
Your assumption is right: you have to use left JOIN, in this way :
SELECT DISTINCT users.name, (SELECT count(id) FROM data WHERE who = 1) as number, data_tmp.cars
FROM users
JOIN data_tmp USING (who)
LEFT JOIN data USING(who)
WHERE users.who = 2

MySQL many-to-many complement set

I've been looking all over the net and asking people for guidance but nobody seems to know the right (relatively fast) solution to the problem:
I have three tables, classic many-to-many solution:
entries: id (int), title (varchar[255]), content (text)
tags: id (int), name (varchar[255]), slug (varchar[255])
entries_tags: id (int), entry_id (int), tag_id (int)
Nothing out of ordinary so far. Now let's say I have test data in tags (I'm keeping out slugs as they are not important):
ID | name
1. | one
2. | two
3. | three
4. | four
5. | five
I also have three entries:
ID | title
1. | Something
2. | Blah blah blah
3. | Yay!
And relations:
ID | entry_id | tag_id
1. | 1 | 1
2. | 1 | 2
3. | 2 | 1
4. | 2 | 3
5. | 3 | 1
6. | 3 | 2
7. | 3 | 3
8. | 4 | 1
9. | 4 | 4
OK, we have our test data. I want to know how to get all entries that have tag One, but doesn't have tag Three (that'd be entries 1 and 4).
I know how to do it with subquery, the problem is, it takes a lot of time (with 100k entries it took about 10-15 seconds). Is there any way to do it with JOINs? Or am I missing something?
edit I guess I should've mentioned I need a solution that works with sets of data rather than single tags, so replace 'One' in my question with 'One', 'Two' and 'Two' with 'Three','Four'
edit2 The answer provided is right, but it's too slow to be used practically. I guess the only way of making it work is using a 3rd-party search engine like Lucene or ElasticSearch.
The following script selects entries that have tags One and Two and do not have tags Three and Four:
SELECT DISTINCT
et.entry_id
FROM entries_tags et
INNER JOIN tags t1 ON et.tag_id = t1.id AND t1.name IN ('One', 'Two')
LEFT JOIN tags t2 ON et.tag_id = t2.id AND t2.name IN ('Three', 'Four')
WHERE t2.id IS NULL
Alternative solution: the INNER JOIN is replaced with WHERE EXISTS, which allows us to get rid from the (rather expensive) DISTINCT:
SELECT
et.entry_id
FROM entries_tags et
LEFT JOIN tags t2 ON et.tag_id = t2.id AND t2.name IN ('Three', 'Four')
WHERE t2.id IS NULL
AND EXISTS (
SELECT *
FROM tags t1
WHERE t1.id = et.tag_id
AND t1.name IN ('One', 'Two')
)
This should do what you want.
(It may or may not be faster than the sub query solution, I suggest you compare the query plans)
SELECT DISTINCT e.*
FROM tags t1
INNER JOIN entries_tags et1 ON t1.id=et1.tag_id
INNER JOIN entries e ON e.entry_id=et1.entry_id
INNER JOIN tags t2 on t2.name='three'
INNER JOIN tags t3 on t3.name='four'
LEFT JOIN entries_tags et2 ON (et1.entryid=et2.entryid AND t2.id = et2.tag_id )
OR (et1.entryid=et2.entryid AND t3.id = et2.tag_id )
WHERE t1.name IN ('one','two') AND et2.name is NULL
By LEFT Joining the entries_tags table et2 (the data you do not want), you can then only select the records where the et2.name IS NULL (where the et2 record does not exist).
You mentioned trying a subquery. Is this what you tried?
SELECT entries.id, entries.content
FROM entries
LEFT JOIN entries_tags ON entries.id=entries_tags.entries_id
LEFT JOIN tags ON entries_tags.tag_id=tags.id
WHERE tag.id=XX
and entries.id NOT IN (
SELECT entries.id
FROM entries
LEFT JOIN entries_tags ON entries.id=entries_tags.entries_id
LEFT JOIN tags ON entries_tags.tag_id=tags.id
WHERE tag.id=YY
)
(Where XX is the tag you do want and YY is the tag you do not want)
With indices on the ID fields, that shouldn't be as slow as you say it is. It will depend on the data set, but it should be fine with indices (and with string comparisons omitted).