SQL - Joining columns from the same table in a Query - sql

SOLVED ! See the answer bellow !
Before I explain my problem I want to apologise for those who would feel this question is too long but I feel like I must give some details to make things the clearer possible. Though, the problem is simple to understand it is not that simple to me to implement.
I have 3 tables.
Hata and Icon contains images I want to link with Succes which contains texts
[Hata]
id, INTEGER, AUTO_INCREMENT, PRIMARY_KEY
hata Image
idLang, VARCHAR(5)
[Icon]
id, INTEGER, AUTO_INCREMENT, PRIMARY_KEY
icon, IMAGE
idPhrase, INTEGER
[Succes]
id, INTEGER, AUTO_INCREMENT, PRIMARY_KEY
idPhrase, INTEGER
titre, VARCHAR(25)
desc, VARCHAR(125)
idLang, VARCHAR(5)
Here is a sample showing how the Succes table looks like
+----+----------+-----------------+------------------+--------+
| id | idPhrase | titre | desc | idLang |
+----+----------+-----------------+------------------+--------+
| 1 | 1 | Hello | Desc in English | en-GB |
+----+----------+-----------------+------------------+--------+
| 2 | 1 | Salut | Desc in French | fr-FR |
+----+----------+-----------------+------------------+--------+
| 3 | 1 | 今日は | Desc in Japanese | ja-JP |
+----+----------+-----------------+------------------+--------+
| 4 | 2 | Goodbye | Desc in English | en-GB |
+----+----------+-----------------+------------------+--------+
| 5 | 2 | Au revoir | Desc in French | fr-FR |
+----+----------+-----------------+------------------+--------+
| 6 | 2 | またね | Desc in Japanese | ja-JP |
+----+----------+-----------------+------------------+--------+
| 7 | 3 | You're welcome | Desc in English | en-GB |
+----+----------+-----------------+------------------+--------+
| 8 | 3 | Je vous en prie | Desc in French | fr-FR |
+----+----------+-----------------+------------------+--------+
| 9 | 3 | どういたしまして | Desc in Japanese | ja-JP |
+----+----------+-----------------+------------------+--------+
...
The tables are now joined using this WHERE conditions
Icons.idPhrase = Succes.idPhrase AND Hata.idLang=Succes.idLang
Everything would be fine if there would be nothing specific in the Succes table.
In fact, for each Icon there are 3 sentences and the idPhrase links them but in the actual result set I somehow have redundancies.
Icon1|FlagIcon1|TitreLang1|DescLang1
Icon1|FlagIcon2|TitreLang2|DescLang2
Icon1|FlagIcon3|TitreLang3|DescLang3
Icon2|FlagIcon1|TitreLang1|DescLang1
Icon2|FlagIcon2|TitreLang2|DescLang2
Icon2|FlagIcon3|TitreLang3|DescLang3
...
What I'd like to achieve is the following (just the very first row):
Icon1|FlagIcon1|TitreLang1|DescLang1|FlagIcon2|TitreLang2|DescLang2|FlagIcon3|TitreLang3|DescLang
or
Icon1|FlagIcon1|FlagIcon2|FlagIcon3|TitreLang1|DescLang1|TitreLang2|DescLang2|TitreLang3|DescLang3
or even
Icon1|FlagIcon1|FlagIcon2|FlagIcon3|TitreLang1|TitreLang2|TitreLang3|DescLang1|DescLang2|DescLang3
In other words, it would be like I'd joined several queries together such as
SELECT icon FROM Icon
Joined with
SELECT Hata.hata AS fEN, Succes.titre AS tEN, Succes.desc AS dEN
FROM Hata, Succes
WHERE Hata.idLang=Succes.idLang AND Succes.idLang='en-GB'
Joined With
SELECT Hata.hata AS fFR, Succes.titre AS tFR, Succes.desc AS dFR
FROM Hata, Succes
WHERE Hata.idLang=Succes.idLang AND Succes.idLang='fr-FR'
And so on...
Just the problem of ensuring the links between tables (icon 1 with sentence 1)
Here's another sample on how it should (may) look like
+-------+-------+-------+-------+----------------+------------------+------------+-----------------+----------------+------------------+
| icon | fEN | fFR | fJP | tEN | tFR | tJA | dEN | dFR | dJA |
+-------+-------+-------+-------+----------------+------------------+------------+-----------------+----------------+------------------+
| <img> | <img> | <img> | <img> | Hello | Salut | 今日は | Desc in English | Desc in French | Desc in Japanese |
+-------+-------+-------+-------+----------------+------------------+------------+-----------------+----------------+------------------+
| <img> | <img> | <img> | <img> | Goodbye | Au revoir | またね | Desc in English | Desc in French | Desc in Japanese |
+-------+-------+-------+-------+----------------+------------------+------------+-----------------+----------------+------------------+
| <img> | <img> | <img> | <img> | You're welcome | Je vous en pries | どういたしまして | Desc in English | Desc in French | Desc in Japanese |
+-------+-------+-------+-------+----------------+------------------+------------+-----------------+----------------+------------------+
...
I've browsed for SQL reference to try many things but they don't seem to do what I expect (CONCATENATE, UNION, etc...)
I also tried the following query but it gives me an error message.
SELECT Icon.icon, Hata.hata AS fEN,Hata.hata AS fFR,Hata.hata AS fJA
,'FR'.'titre', 'FR'.'desc'
,'JA'.'titre', 'JA'.'desc'
,'UK'.'titre', 'UK'.'desc'
FROM Hata, Icon
LEFT JOIN Succes AS FR ON 'FR'.'idLang' = 'Hata'.'idLang' AND 'FR'.'idLang' = 'fr-FR'
LEFT JOIN Succes AS JA ON 'JA'.'idLang' = 'Hata'.'idLang' AND 'FR'.'idLang' = 'ja-JP'
LEFT JOIN Succes AS UK ON 'UK'.'idLang' = 'Hata'.'idLang' AND 'FR'.'idLang' = 'en-GB'
the message is
Statut SQL: HY000
Error Code: 1000
syntax error, unexpected $end, expecting BETWEEN or IN or SQL_TOKEN_LIKE
but it seems my syntax is good according to sample I've found even on StackOverflow.
I must also specify that I'm using OpenOffice Base and my purpose is publishing a document. Maybe there is something specific to OOo such as LEFT JOIN not implemented but the code get coloured so I think it should be fine.
Thank you for your availability and help.

I really don't get it.
I've tried with MySQL and it does something like an exclusive join
mysql> SELECT titre AS tfr, titre AS ten, titre AS tjp FROM data WHERE idlang=1
-> UNION
-> SELECT null,titre AS ten, null FROM data WHERE idlang=2
-> UNION
-> SELECT null, null, titre as tjp FROM data WHERE idlang=3;
+------------------+------------------+------------------+
| tfr | ten | tjp |
+------------------+------------------+------------------+
| Salut | Salut | Salut |
| Au revoir | Au revoir | Au revoir |
| Je vous en pries | Je vous en pries | Je vous en pries |
| NULL | Hello | NULL |
| NULL | Goodbye | NULL |
| NULL | You're Welcome | NULL |
| NULL | NULL | Konnichiha |
| NULL | NULL | Mata ne |
| NULL | NULL | Douitashimashite |
+------------------+------------------+------------------+
9 rows in set (0.00 sec)
If in the 1st SELECT I do titre AS tfr, null, null the column headers get to null.
mysql> SELECT titre AS tfr, titre AS ten, titre AS tjp FROM data WHERE idlang=1
-> UNION
-> SELECT null,titre AS ten, null FROM data WHERE idlang=2
-> UNION
-> SELECT null, null, titre as tjp FROM data WHERE idlang=3;
+------------------+------------------+------------------+
| tfr | NULL | NULL |
+------------------+------------------+------------------+
| Salut | NULL | NULL |
| Au revoir | NULL | NULL |
| Je vous en pries | NULL | NULL |
| NULL | Hello | NULL |
| NULL | Goodbye | NULL |
| NULL | You're Welcome | NULL |
| NULL | NULL | Konnichiha |
| NULL | NULL | Mata ne |
| NULL | NULL | Douitashimashite |
+------------------+------------------+------------------+
It still doesn't looks like the result I want.
I need to concentrate on that data table to get all in one line but I keep wondering how to achieve this. In principle it is very simple but I can't translate that in SQL.
mysql> DESCRIBE data;
+-----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| id_phrase | int(11) | YES | | NULL | |
| titre | varchar(20) | YES | | NULL | |
| desc | varchar(50) | YES | | NULL | |
| idicon | int(11) | YES | | NULL | |
| idlang | int(11) | YES | | NULL | |
+-----------+-------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)
Actually idicon is redundant with id_phrase (don't really need it so pretend it does not exist).
Thank you.

HERE ARE SOME PROPOSITIONS IF YOU WOULD MEET A RESEMBLING PROBLEM.
NB: The column names and table names may differ from the original question but the problem is the same.
I've been asking this question on another forum and here are 2 queries I tested and can certify are working with MySQL 5.5
Query 1 :
SELECT id_phrase
, idicon
, max(case idlang when 1 then titre end) AS tfr
, max(case idlang when 1 then DESC end) AS dfr
, max(case idlang when 2 then titre end) AS ten
, max(case idlang when 2 then DESC end) AS den
, max(case idlang when 3 then titre end) AS tjp
, max(case idlang when 3 then DESC end) AS djp
FROM DATA
WHERE idlang IN (1, 2, 3)
GROUP BY id_phrase, idicon
ORDER BY id_phrase ASC
Query 2 :
SELECT t1.id_phrase, t1.idicon, t1.titre AS tfr, t1.descr AS dfr, t2.titre AS ten, t2.descr AS den, t3.titre AS tjp, t3.descr AS djp
FROM DATA AS t1
LEFT OUTER JOIN DATA AS t2
ON t1.id_phrase=t2.id_phrase
LEFT OUTER JOIN DATA AS t3
ON t1.id_phrase=t3.id_phrase
WHERE t1.idlang=1 AND t2.idlang=2 AND t3.idlang=3
You're welcome if these queries may help you.
Source (french)

Related

Create SQL SELECT Statement to 'modify' small table

I am pretty new to SQL in general and want to 'modify' the following table (Tablename: translations / PK: id, language, key):
| id | language | key | value |
---------------------------------------------
| 1 | DE | #Keyboard | Tastatur |
| 1 | EN | #Keyboard | Keyboard |
| 1 | ES | #Keyboard | Teclado |
| 2 | DE | #Screen | Bildschirm |
| 2 | EN | #Screen | Screen |
| 3 | ES | #Equipment | Equipo |
I want to get a table like this:
| key | valueDE | valueEN |
--------------------------------------------
| #Keyboard | Tastatur | Keyboard |
| #Screen | Bildschirm | Screen |
| #Equipment | null | null |
So basically I want the translation of the values for the languages DE and EN next to each other in columns. Entries with no translation for DE and EN should have an empty field in the column valueDE and valueEN or if only one translation is available the other field in the column should be empty as shown in the table above.
Help would be appreciated a lot.
Here it is, your definition re-worded into SQL.
select key,
(select value from translations where language='DE' and key=t.key limit 1) "valueDE",
(select value from translations where language='EN' and key=t.key limit 1) "valueEN"
from (select distinct key from translations) t;
key
valueDE
valueEN
#Keyboard
Tastatur
Keyboard
#Equipment
#Screen
Bildschirm
Screen

When Querying Many-To-Many Relationship in SQL, Return Multiple Connections As an Array In Single Row?

Basically, I have 3 tables, titles, providers, and provider_titles.
Let's say they look like this:
| title_id | title_name |
|------------|----------------|
| 1 | San Andres |
| 2 |Human Centipede |
| 3 | Zoolander 2 |
| 4 | Hot Pursuit |
| provider_id| provider_name |
|------------|----------------|
| 1 | Hulu |
| 2 | Netflix |
| 3 | Amazon_Prime |
| 4 | HBO_GO |
| provider_id| title_id |
|------------|----------------|
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 3 | 1 |
| 3 | 3 |
| 4 | 4 |
So, clearly there are titles with multiple providers, yeah? Typical many-to-many so far.
So what I'm doing to query it is with a JOIN like the following:
SELECT * FROM provider_title JOIN provider ON provider_title.provider_id = provider.provider_id JOIN title ON title.title_id = provider_title.title_id WHERE provider.name IN ('Netflix', 'HBO_GO', 'Hulu', 'Amazon_Prime')
Ok, now to the actual issue. I don't want repeated title names back, but I do want all of the providers associated with the title. Let me explain with another table. Here is what I am getting back with the current query, as is:
| provider_id| provider_name | title_id | title_name |
|------------|---------------|----------|---------------|
| 1 | Hulu | 1|San Andreas |
| 1 | Hulu | 2|Human Centipede|
| 2 | Netflix | 1|San Andreas |
| 3 | Amazon_Prime | 1|San Andreas |
| 3 | Amazon_prime | 3|Zoolander 2 |
| 4 | HBO_GO | 4|Hot Pursuit |
But what I really want would be something more like
| provider_id| provider_name |title_id| title_name|
|------------|-----------------------------|--------|-----------|
| [1, 2, 3] |[Hulu, Netflix, Amazon_Prime]| 1|San Andreas|
Meaning I only want distinct titles back, but I still want each title's associated providers. Is this only possible to do post-sql query with logic iterating through the returned rows?
Depending on your database engine, there may be an aggregation function to help achieve this.
For example, this SQLfiddle demonstrates the postgres array_agg function:
SELECT t.title_id,
t.title_name,
array_agg( p.provider_id ),
array_agg( p.provider_name )
FROM provider_title as pt
JOIN
provider as p
ON pt.provider_id = p.provider_id
JOIN title as t
ON t.title_id = pt.title_id
GROUP BY t.title_id,
t.title_name
Other database engines have equivalents. For example:
mySQL has group_concat
Oracle has listagg
sqlite has group_concat (as well!)
If your database isn't covered by the above, you can google '[Your database engine] aggregate comma delimited string'

Fetch SQL rows with priority

I have following tables structure:
forms
RID | MODULE
------------
1 | indiv
2 | indiv
3 | indiv
translations
RID | LANG | VALUE | MODULE | TAG |
-----------------------------------
1 | en |car | | |
1 | en |truck |indiv | |
1 | en |boat |indiv |C100 |
2 | en |hat | | |
3 | en |cat | | |
3 | en |dog |indiv | |
4 | en |light | | |
5 | en |dark | | |
I need to fetch only one row per RID from translations table, based on additional (but not mandatory) parameters for module and tag columns, i.e.:
RESULT without input parameters:
RID | LANG | VALUE | MODULE | TAG |
-----------------------------------
1 | en |car | | |
2 | en |hat | | |
3 | en |cat | | |
RESULT with one input parameter module='indiv':
RID | LANG | VALUE | MODULE | TAG |
-----------------------------------
1 | en |truck |indiv | |
2 | en |hat | | |
3 | en |dog |indiv | |
If I have two input parameters the result to be:
RESULT with two parameters: module='indiv' AND tag='c100'
RID | LANG | VALUE | MODULE | TAG |
-----------------------------------
1 | en |boat |indiv |C100 |
2 | en |hat | | |
3 | en |dog |indiv | |
How can I achieve this with SQL only on ORACLE DB server? A query example for the last case with two parameters will be enough for me as previous cases are subsets from last one with NULL of these columns I believe. If you think that all these cases are too different and require different SQL statements, you are more than welcome to write them as well.
Thank you!
SELECT *
FROM (
SELECT t.*,
ROW_NUMBER() OVER (
PARTITION BY RID
ORDER BY CASE
WHEN module = LOWER( :mod ) AND tag = UPPER( :tag ) THEN 1
WHEN tag = UPPER( :tag ) THEN 2
WHEN module = LOWER( :mod ) AND tag IS NULL THEN 3
WHEN module IS NULL AND tag IS NULL THEN 4
ELSE 5
END
) AS rn
FROM translations t
WHERE ( module IS NULL OR module = LOWER( :mod ) )
OR ( tag IS NULL OR tag = UPPER( :tag ) )
)
WHERE rn = 1;
I think this should do.
SELECT *
FROM (
SELECT F.RID,
T.LANG,
T.VALUE,
T.MODULE,
T.TAG,
RANK() OVER(PARTITION BY F.RID
ORDER BY DECODE(T.MODULE, :module, 1, 2),
DECODE(T.TAG, :tag, 1, 2)) RANK
FROM forms F INNER JOIN translations T
ON T.RID = F.RID)
WHERE RANK = 1
So you rank rows with MODULE = :module or/and TAG = :tag higher. You still need something to do with ties, but you get the idea. RANK leaves ties, ROW_NUMBER does not.
I put MODULE higher than TAG because of your examples. You might need to change it if you can input tags without modules.
And also, DECODE maps NULL to NULL, so if :module is not set, you will get match with rows having NULL in MODULE.

Retrieve data from SQL Server and concatenate results over rows based on grouping

I’ve been working on a problem for a couple of days and have finally managed to work out a solution that works for me. In case this solution is useful for someone else, I’m going to ask a question and answer it myself.
I have read-only access to a large SQL Server database containing in excess of 1 million records. Some of the tables in the database are linked in many-to-many relationships through lookup tables. To simplify matters, the tables can be illustrated as shown below:
table names
|-----------|
| id | name |
|----|------|
|  1 | dave |
|  2 | phil |
|  3 | john |     table foods_relationship        table clothes_relationship
|  4 | pete |     |--------------------------|    |----------------------------|
|-----------|     | id | names_id | foods_id |    | id | names_id | clothes_id |
                  |----|----------|----------|    |----|----------|------------|
table foods       |  1 |  1 | 1 | | 1 | 1 | 1 |
|---------------| |  2 |  1 | 3 | | 2 | 1 | 3 |
| id | food | |  3 |  1 | 4 | | 3 | 1 | 4 |
|----|----------| |  4 |  2 | 2 | | 4 | 2 | 2 |
|  1 | beef | |  5 |  2 | 3 | | 5 | 2 | 3 |
|  2 | tomatoes | |  6 |  2 | 4 | | 6 | 2 | 4 |
|  3 | bacon | |  7 |  2 | 5 | | 7 | 3 | 1 |
|  4 | cheese | |  8 |  3 | 3 | | 8 | 3 | 3 |
| 5 | apples | |  9 |  3 | 5 | | 9 | 3 | 5 |
|---------------| | 10 |  4 | 1 | | 10 | 4 | 2 |
| 11 |  4 | 2 | | 11 | 4 | 4 |
table clothes | 12 |  4 | 3 | | 12 | 4 | 5 |
|---------------| | 13 |  4 | 5 | |----------------------------|
| id | clothes | |--------------------------|
|----|----------|
|  1 | trousers |
|  2 | shorts |
|  3 | shirt |
|  4 | socks |
| 5 | jumper |
| 6 | jacket |
|---------------|
The tables can be recreated using the following SQL (adapted from MySQL database so may need minor tweaking to work in SQL Server):
CREATE TABLE `clothes` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`clothes` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `clothes` (`id`, `clothes`)
VALUES
(1,'trousers'),
(2,'shorts'),
(3,'shirt'),
(4,'socks'),
(5,'jumper'),
(6,'jacket');
CREATE TABLE `clothes_relationships` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`names_id` int(11) DEFAULT NULL,
`clothes_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `clothes_relationships` (`id`, `names_id`, `clothes_id`)
VALUES
(1,1,1),
(2,1,3),
(3,1,4),
(4,2,2),
(5,2,3),
(6,2,4),
(7,3,1),
(8,3,3),
(9,3,5),
(10,4,2),
(11,4,4),
(12,4,5);
CREATE TABLE `food_relationships` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`names_id` int(11) DEFAULT NULL,
`foods_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `food_relationships` (`id`, `names_id`, `foods_id`)
VALUES
(1,1,1),
(2,1,3),
(3,1,4),
(4,2,2),
(5,2,3),
(6,2,4),
(7,2,5),
(8,3,3),
(9,3,5),
(10,4,1),
(11,4,2),
(12,4,3),
(13,4,5);
CREATE TABLE `foods` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`food` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `foods` (`id`, `food`)
VALUES
(1,'beef'),
(2,'tomatoes'),
(3,'bacon'),
(4,'cheese'),
(5,'apples');
CREATE TABLE `names` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `names` (`id`, `name`)
VALUES
(1,'dave'),
(2,'phil'),
(3,'john'),
(4,'pete');
I want to query the database and – somehow – get the following output:
|-------------------------------------------------------------|
| name | food     | clothes |
|------|------------------------------|-----------------------|
| dave | beef,cheese,bacon | trousers,socks,shirt |
| john | apples,bacon | jumper,shirt,trousers |
| pete | beef,apples,bacon,tomatoes | shorts,jumper,socks |
| phil | bacon,tomatoes,apples,cheese | shirt,shorts,socks |
|-------------------------------------------------------------|
However, running a SELECT query that joins the ‘names’ table to one or both of the other tables (via the respective lookup tables) results in multiple rows for each name. For example:
SELECT
names.name,
foods.food
FROM
names
LEFT JOIN food_relationships ON names.id = food_relationships.names_id
LEFT JOIN foods ON food_relationships.foods_id = foods.id;
...produces the following set of results:
|-----------------|
| name | food |
|------|----------|
| dave | beef |
| dave | bacon |
| dave | cheese |
| phil | tomatoes |
| phil | bacon |
| phil | cheese |
| phil | apples |
| john | bacon |
| john | apples |
| pete | beef |
| pete | tomatoes |
| pete | bacon |
| pete | apples |
|-----------------|
The problem is compounded even further if the SELECT query returns data from both tables:
SELECT
names.name,
foods.food,
clothes.clothes
FROM
names
LEFT JOIN food_relationships ON names.id = food_relationships.names_id
LEFT JOIN foods ON food_relationships.foods_id = foods.id
LEFT JOIN clothes_relationships ON names.id = clothes_relationships.names_id
LEFT JOIN clothes ON clothes_relationships.clothes_id = clothes.id;
|-----------------------------|
| name | food | clothes |
|------|----------|-----------|
| dave | beef | trousers |
| dave | beef | shirt |
| dave | beef | socks |
| dave | bacon | trousers |
| dave | bacon | shirt |
| dave | bacon | socks |
| dave | cheese | trousers |
| dave | cheese | shirt |
| dave | cheese | socks |
| phil | tomatoes | shorts |
| phil | tomatoes | shirt |
| phil | tomatoes | socks |
| phil | bacon | shorts |
| phil | bacon | shirt |
| phil | bacon | socks |
| phil | cheese | shorts |
| phil | cheese | shirt |
| phil | cheese | socks |
| phil | apples | shorts |
| phil | apples | shirt |
| phil | apples | socks |
| ...
| etc.
The question is, how can I query the SQL Server database to retrieve all the data but process it to have only one line per person?
If the database were MySQL, the solution would be relatively easy because MySQL has a GROUP_CONCAT function which concatenates rows. So, for just one of the tables, I could use:
SELECT
names.name,
GROUP_CONCAT(foods.food)
FROM
names
LEFT JOIN food_relationships ON names.id = food_relationships.names_id
LEFT JOIN foods ON food_relationships.foods_id = foods.id
GROUP BY (names.name);
...to give:
name food
dave beef,cheese,bacon
john apples,bacon
pete beef,apples,bacon,tomatoes
phil bacon,tomatoes,apples,cheese
To get equivalent data from both ‘names’ and ‘clothes’ tables, I could use something like:
SELECT
temp_foods_table.name AS 'name',
temp_foods_table.food AS 'food',
temp_clothes_table.clothes AS 'clothes'
FROM
(
SELECT
names.name,
GROUP_CONCAT(foods.food) AS 'food'
FROM
names
LEFT JOIN food_relationships ON names.id = food_relationships.names_id
LEFT JOIN foods ON food_relationships.foods_id = foods.id
GROUP BY (names.name)
) AS temp_foods_table
LEFT JOIN
(
SELECT
names.name,
GROUP_CONCAT(clothes.clothes) AS 'clothes'
FROM
names
LEFT JOIN clothes_relationships ON names.id = clothes_relationships.names_id
LEFT JOIN clothes ON clothes_relationships.clothes_id = clothes.id
GROUP BY (names.name)
) AS temp_clothes_table
ON temp_foods_table.name = temp_clothes_table.name;
...to give the following results:
name food clothes
dave beef,cheese,bacon trousers,socks,shirt
john apples,bacon jumper,shirt,trousers
pete beef,apples,bacon,tomatoes shorts,jumper,socks
phil bacon,tomatoes,apples,cheese shirt,shorts,socks
However, the situation in SQL SERVER appears to be much less straight-forward. For a single table there are some proposed solutions online which include the use of common table expressions or FOR XML PATH. However, all the solutions appear to have drawbacks and give the distinct impression that they are work-arounds rather than specifically-designed features. Each suggested solution has some weakness (for example, the FOR XML PATH solution assumes that the text is XML and, therefore, special characters included in the text can cause problems). In addition, some commenters expressed concerns that such work-arounds were based on undocumented or deprecated features and, as a result, may not be reliable in the long-term.
As a result, I decided not to tie myself up in SQL knots but to process the data post-retrieval using Python and Pandas. I always transfer data to a Pandas dataframe for plotting and analysis anyway so this wasn't a major inconvenience. In order to concatenate the data over several columns, I used groupby(). However, since there were two many-to-many tables, there was duplication in each column and, therefore, the final concatenated string contained all those duplications. In order to have only unique values, I used Python sets (which, by definition, can only contain unique values). The only potential drawback with this method is that the order of the strings is not maintained but, for my situation, that isn't an issue. The final Python solution looked something like this:
Import necessary libraries:
>>> import pandas as pd
>>> import pymssql
>>> import getpass
Input necessary details to connect to database:
>>> myServer = input("Enter server address: ")
>>> myUser = input("Enter username: ")
>>> myPwd = getpass.getpass("Enter password: ")
Create a connection:
>>> myConnection = pymssql.connect(server=myServer, user=myUser, password=myPwd, port='1433')
Define a query to retrieve the necessary data:
>>> myQuery = """SELECT
names.name,
foods.food,
clothes.clothes
FROM
names
LEFT JOIN food_relationships ON names.id = food_relationships.names_id
LEFT JOIN foods ON food_relationships.foods_id = foods.id
LEFT JOIN clothes_relationships ON names.id = clothes_relationships.names_id
LEFT JOIN clothes ON clothes_relationships.clothes_id = clothes.id """
Run query, put results in dataframe and close connection:
>>> myLatestData = pd.io.sql.read_sql(myQuery, con=myConnection)
>>> myConnection.close()
Concatenate strings in multiple rows and remove duplicates:
>>> tempDF = tempDF.groupby('name').agg(lambda col: ','.join(set(col)))
Print final dataframe:
>>> print(tempDF)
name food clothes
dave beef,bacon,cheese socks,trousers,shirt
john bacon,apples jumper,trousers,shirt
pete tomatoes,beef,bacon,apples socks,jumper,shorts
phil tomatoes,bacon,cheese,apples socks,shorts,shirt
For me, this solution makes much more intuitive sense than trying to do all the data processing within the SQL query. Hope this helps someone else.
If it is MS-Sql Server..
You can use STUFF function. For e.g.
DECLARE #Heroes TABLE (
[HeroName] VARCHAR(20)
)
INSERT INTO #Heroes ( [HeroName] )
VALUES ( 'Superman' ), ( 'Batman' ), ('Ironman'), ('Wolverine')
SELECT STUFF((SELECT ',' + [HeroName]
FROM #Heroes
ORDER BY [HeroName]
FOR XML PATH('')), 1, 1, '') AS [Output]
Output
Batman,Ironman,Superman,Wolverine
I think this should answer your question.
Thanks

Joining two tables and show data from one if there is any

I have these two tables that i need to join
fields_data fields
+------------+-----------+------+ +------+-------------+----------+
| relationid | fieldname | data | | name | displayname | position |
+------------+-----------+------+ +------+-------------+----------+
| 2 | ftp | test | | user | Username | top |
| 2 | other | 1234 | | pass | Password | top |
+------------+-----------+------+ | ftp | FTP | top |
| log | Log | top |
| txt | Text | mid |
+------+-------------+----------+
I want to get all the rows from the "fields" table if they have the position "top" AND if a row has a match on name = fieldname from fields_data it should also show the data. This is my join
SELECT
fd.`data`,
fd.`relationid`,
fd.`fieldname`,
f.`name`,
f.`displayname`
FROM `fields` AS f
LEFT OUTER JOIN `fields_data` AS fd
ON fd.`fieldname` = f.`name`
WHERE f.`position`='top' AND (fd.`relationid`='3' OR fd.`relationid` IS NULL)
My problem is that the above query only gives me this result:
+------+------------+-----------+------+-------------+
| data | relationid | fieldname | name | displayname |
+------+------------+-----------+------+-------------+
| NULL | NULL | NULL | user | Username |
| NULL | NULL | NULL | pass | Password |
| NULL | NULL | NULL | log | Log |
+------+------------+-----------+------+-------------+
The field called "ftp" is missing due to it having a relation to "2".. However i still want to display it as result but like the others with NULL in it. And if the SQL query had "fd.relationid='2'" instead of 3 it would give same result, but with the row containing ftp in name, holding data in the three fields.
I hope you get what i mean.. My english is not the best.. Heres the result i want:
with above query containing fd.`relationid`='3'
+------+------------+-----------+------+-------------+
| data | relationid | fieldname | name | displayname |
+------+------------+-----------+------+-------------+
| NULL | NULL | NULL | user | Username |
| NULL | NULL | NULL | pass | Password |
| NULL | NULL | NULL | ftp | FTP |
| NULL | NULL | NULL | log | Log |
+------+------------+-----------+------+-------------+
with above query containing fd.`relationid`='2'
+------+------------+-----------+------+-------------+
| data | relationid | fieldname | name | displayname |
+------+------------+-----------+------+-------------+
| NULL | NULL | NULL | user | Username |
| NULL | NULL | NULL | pass | Password |
| test | 2 | ftp | ftp | FTP |
| NULL | NULL | NULL | log | Log |
+------+------------+-----------+------+-------------+
You want to move the condition to the on clause:
SELECT fd.`data`, fd.`relationid`, fd.`fieldname`, f.`name`, f.`displayname`
FROM `fields` f LEFT OUTER JOIN
`fields_data` fd
ON fd.`fieldname` = f.`name` AND fd.`relationid` = '3'
WHERE f.`position`='top' ;
It is interesting that the semantics of your query and this query are different -- and you found the exact situation: when there is a match on another value, the where clause form filters out the row. This will still keep everything.
As a note, the following also does what you want:
SELECT fd.`data`, fd.`relationid`, fd.`fieldname`, f.`name`, f.`displayname`
FROM `fields` f LEFT OUTER JOIN
(SELECT fd.*
FROM `fields_data` fd
WHERE fd.`relationid` = '3'
) fd
ON fd.`fieldname` = f.`name`
WHERE f.`position` = 'top' ;
I wouldn't recommend writing the query this way, particularly in MySQL (because the subquery is materialized). However, understanding why your version is different from these versions (and why these are the same) is a big step forward in mastering outer joins.