MySQL Results as comma separated list - sql

I need to run a query like:
SELECT p.id, p.name,
(SELECT name
FROM sites s
WHERE s.id = p.site_id) AS site_list
FROM publications p
But I'd like the sub-select to return a comma separated list, instead of a column of data. Is this even possible, and if so, how?

You can use GROUP_CONCAT to perform that, e.g. something like
SELECT p.id, p.name, GROUP_CONCAT(s.name) AS site_list
FROM sites s
INNER JOIN publications p ON(s.id = p.site_id)
GROUP BY p.id, p.name;

Now only I came across this situation and found some more interesting features around GROUP_CONCAT. I hope these details will make you feel interesting.
simple GROUP_CONCAT
SELECT GROUP_CONCAT(TaskName)
FROM Tasks;
Result:
+------------------------------------------------------------------+
| GROUP_CONCAT(TaskName) |
+------------------------------------------------------------------+
| Do garden,Feed cats,Paint roof,Take dog for walk,Relax,Feed cats |
+------------------------------------------------------------------+
GROUP_CONCAT with DISTINCT
SELECT GROUP_CONCAT(TaskName)
FROM Tasks;
Result:
+------------------------------------------------------------------+
| GROUP_CONCAT(TaskName) |
+------------------------------------------------------------------+
| Do garden,Feed cats,Paint roof,Take dog for walk,Relax,Feed cats |
+------------------------------------------------------------------+
GROUP_CONCAT with DISTINCT and ORDER BY
SELECT GROUP_CONCAT(DISTINCT TaskName ORDER BY TaskName DESC)
FROM Tasks;
Result:
+--------------------------------------------------------+
| GROUP_CONCAT(DISTINCT TaskName ORDER BY TaskName DESC) |
+--------------------------------------------------------+
| Take dog for walk,Relax,Paint roof,Feed cats,Do garden |
+--------------------------------------------------------+
GROUP_CONCAT with DISTINCT and SEPARATOR
SELECT GROUP_CONCAT(DISTINCT TaskName SEPARATOR ' + ')
FROM Tasks;
Result:
+----------------------------------------------------------------+
| GROUP_CONCAT(DISTINCT TaskName SEPARATOR ' + ') |
+----------------------------------------------------------------+
| Do garden + Feed cats + Paint roof + Relax + Take dog for walk |
+----------------------------------------------------------------+
GROUP_CONCAT and Combining Columns
SELECT GROUP_CONCAT(TaskId, ') ', TaskName SEPARATOR ' ')
FROM Tasks;
Result:
+------------------------------------------------------------------------------------+
| GROUP_CONCAT(TaskId, ') ', TaskName SEPARATOR ' ') |
+------------------------------------------------------------------------------------+
| 1) Do garden 2) Feed cats 3) Paint roof 4) Take dog for walk 5) Relax 6) Feed cats |
+------------------------------------------------------------------------------------+
GROUP_CONCAT and Grouped Results
Assume that the following are the results before using GROUP_CONCAT
+------------------------+--------------------------+
| ArtistName | AlbumName |
+------------------------+--------------------------+
| Iron Maiden | Powerslave |
| AC/DC | Powerage |
| Jim Reeves | Singing Down the Lane |
| Devin Townsend | Ziltoid the Omniscient |
| Devin Townsend | Casualties of Cool |
| Devin Townsend | Epicloud |
| Iron Maiden | Somewhere in Time |
| Iron Maiden | Piece of Mind |
| Iron Maiden | Killers |
| Iron Maiden | No Prayer for the Dying |
| The Script | No Sound Without Silence |
| Buddy Rich | Big Swing Face |
| Michael Learns to Rock | Blue Night |
| Michael Learns to Rock | Eternity |
| Michael Learns to Rock | Scandinavia |
| Tom Jones | Long Lost Suitcase |
| Tom Jones | Praise and Blame |
| Tom Jones | Along Came Jones |
| Allan Holdsworth | All Night Wrong |
| Allan Holdsworth | The Sixteen Men of Tain |
+------------------------+--------------------------+
USE Music;
SELECT ar.ArtistName,
GROUP_CONCAT(al.AlbumName)
FROM Artists ar
INNER JOIN Albums al
ON ar.ArtistId = al.ArtistId
GROUP BY ArtistName;
Result:
+------------------------+----------------------------------------------------------------------------+
| ArtistName | GROUP_CONCAT(al.AlbumName) |
+------------------------+----------------------------------------------------------------------------+
| AC/DC | Powerage |
| Allan Holdsworth | All Night Wrong,The Sixteen Men of Tain |
| Buddy Rich | Big Swing Face |
| Devin Townsend | Epicloud,Ziltoid the Omniscient,Casualties of Cool |
| Iron Maiden | Somewhere in Time,Piece of Mind,Powerslave,Killers,No Prayer for the Dying |
| Jim Reeves | Singing Down the Lane |
| Michael Learns to Rock | Eternity,Scandinavia,Blue Night |
| The Script | No Sound Without Silence |
| Tom Jones | Long Lost Suitcase,Praise and Blame,Along Came Jones |
+------------------------+----------------------------------------------------------------------------+

Instead of using group concat() you can use just concat()
Select concat(Col1, ',', Col2) as Foo_Bar from Table1;
edit this only works in mySQL; Oracle concat only accepts two arguments. In oracle you can use something like select col1||','||col2||','||col3 as foobar from table1;
in sql server you would use + instead of pipes.

In my case i have to concatenate all the account number of a person who's mobile number is unique. So i have used the following query to achieve that.
SELECT GROUP_CONCAT(AccountsNo) as Accounts FROM `tblaccounts` GROUP BY MobileNumber
Query Result is below:
Accounts
93348001,97530801,93348001,97530801
89663501
62630701
6227895144840002
60070021
60070020
60070019
60070018
60070017
60070016
60070015

Related

How do I identify distinct combinations across array-columns and then unnest in sql presto

I have a database called programs created as
CREATE TABLE programs (
name varchar(200) NOT NULL,
role varchar(200) NOT NULL,
section text[] NOT NULL,
sub_section text[] NOT NULL,
title text[] NOT NULL
);
INSERT INTO programs (name, role, section, sub_section, title) VALUES
('John','Lead','{"VII","VII","VII"}','{"A","A","C"}','{"STUDY","STUDY","STUDY"}'),
('Olga','Member','{"VII","VII"}','{"A","A"}','{"STUDY","STUDY"}'),
('Ben','Co-Lead','{"XI","X"}','{"A","B"}','{"STUDY","TRAVEL"}'),
('Ana','Member','{"VII","II","VI"}','{"A","ALL","B"}','{"STUDY","STUDY","TRAVEL"}');
Here's what the table looks like
| name | role | section | sub_section | title |
| ---- | ------- | ------------ | ----------- | ------------------------ |
| John | Lead | VII,VII,VII | A,A,C | STUDY,STUDY,STUDY |
| Olga | Member | VII,VII | A,A | STUDY,STUDY |
| Ben | Co-Lead | XI,X | A,B | STUDY,TRAVEL |
| Ana | Member | VII,II,VI | A,ALL,B | STUDY,STUDY,TRAVEL |
I want to identify distinct combinations across the section, sub-section, and title columns, as well as unnesting to get this as output
| name | role | section.sub_section | title |
| ---- | ------- | ------------------- | ------------------------ |
| John | Lead | VII.A | STUDY
| John | Lead | VII.C | STUDY
| Olga | Member | VII.A | STUDY
| Ben | Co-Lead | XI.A | STUDY
| Ben | Co-Lead | X.B | TRAVEL
| Ana | Member | VII.A | STUDY
| Ana | Member | II.ALL | STUDY
| Ana | Member | VI.B | TRAVEL
I'm fairly new to SQL and I'm really struggling with getting desired output. Your help would be very much appreciated.
You desired data does not show "combinations across the section, sub-section, and title columns", it seems that you require to match corresponding array based on positions, so you can just unnest and group by fields which you want to distinct on.
Assuming that corresponding columns contain arrays of varchars (if not - you will need to use some string functions to convert them):
-- sample data
WITH dataset (name, role, section, sub_section, title) AS (
VALUES ('John','Lead',array['VII','VII','VII'],array['A','A','C'],array['STUDY','STUDY','STUDY']),
('Olga','Member',array['VII','VII'],array['A','A'],array['STUDY','STUDY']),
('Ben','Co-Lead',array['XI','X'],array['A','B'],array['STUDY','TRAVEL']),
('Ana','Member',array['VII','II','VI'],array['A','ALL','B'],array['STUDY','STUDY','TRAVEL'])
)
--query
select name,
role,
sec || '.' || sub_sec "section.sub_section",
t title
from dataset
cross join unnest(section, sub_section, title) as t(sec, sub_sec, t)
group by name, role, sec, sub_sec, t
order by name
Output:
name
role
section.sub_section
title
Ana
Member
VII.A
STUDY
Ana
Member
II.ALL
STUDY
Ana
Member
VI.B
TRAVEL
Ben
Co-Lead
XI.A
STUDY
Ben
Co-Lead
X.B
TRAVEL
John
Lead
VII.A
STUDY
John
Lead
VII.C
STUDY
Olga
Member
VII.A
STUDY

Set inclusion in SQL

The quest is to check if one set fully includes another. As simplified example we can take four tables:
worker (id, name),
worker_skills (worker_id, skill),
job (id, type)
job_required_skills (job_id, skill)
I want to match the worker to the job but only if job required skills are fully match worker skills, i. e. if worker has some skills which are not required on job it's ok, but if job has at least one skill which worker doesn't then they don't match.
All I can think of includes ridiculous amount of joins and can't be used as a serious solution, so any advices are highly appreciated. Database is postgres 9.6. Thanks!
EDIT:
Some sample data:
+------+---------------+
| name | worker_skills |
+------+---------------+
| John | java |
| John | sql |
| John | ruby |
| Jane | js |
| Jane | html |
+------+---------------+
+---------------------+-------------+
| type | job_skills |
+---------------------+-------------+
| Writing_queries | sql |
| Writing_queries | black_magic |
| Generic_programming | java |
| Frontend_stuff | js |
| Frontend_stuff | html |
+---------------------+-------------+
Result:
+------+---------------------+
| John | Generic_programming |
+------+---------------------+
| Jane | Frontend_stuff |
+------+---------------------+
John is perfectly qualified for Generic_programming (the only needed skill is in his skillset) but can't do Writing_queries as it requires some black_magic; Jane can do Frontend_stuff as she has both required skills.
You can use a left join and aggregation:
select jrs.id, ws.id
from job_required_skills jrs left join
worker_skills ws
on jrs.skill = ws.skill
group by jrs.id, ws.id
having count(*) = count(ws.skill)

Query M:N contains

I am trying to filter a set of tables that includes an M:N junction table in Android Room (SQLite).
An image can have many subjects. I'd like to allow filtering by a subject, so that I get a row with complete image information (including all subjects). So if an image had (National Park, Yosemite) filtering for either would result in one row with both keywords. Unless I messed something up, a typical join will result in multiple rows such that matching Yosemite would get the right image, but you'd be lacking National Park. I came up with this:
SELECT *,
(SELECT GROUP_CONCAT(name)
FROM meta_subject_junction
JOIN subject
ON subject.id = meta_subject_junction.subjectId
WHERE meta_subject_junction.metaId = meta.id) AS keywords,
(SELECT documentUri
FROM image_parent
WHERE meta.parentId = image_parent.id ) AS parentUri
FROM meta
Now this gets me the complete rows, but I think at this point I'd need to:
WHERE keywords LIKE(%YOSEMITE%)
and I think the LIKE is less than ideal, not to mention an imprecise match. Is there a better way to accomplish this? Thanks, this is bending my novice SQL brain.
Further details
meta
+----+----------+--+
| id | name | |
+----+----------+--+
| 1 | yosemite | |
| 2 | bryce | |
| 3 | flowers | |
+----+----------+--+
subject
+----+---------------+--+
| id | name | |
+----+---------------+--+
| 1 | National Park | |
| 2 | Yosemite | |
| 3 | Tulip | |
+----+---------------+--+
junction
+--------+-----------+
| metaId | subjectId |
+--------+-----------+
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 3 | 3 |
+--------+-----------+
Although I may have done something wrong, as far as I can tell Android Room doesn't like:
+----+-----------+---------------+
| id | name | subject |
+----+-----------+---------------+
| 1 | yosemite | National Park |
| 1 | yosemite | Yosemite |
+----+-----------+---------------+
so I'm trying to reduce the rows:
+----+-----------+-------------------------+
| id | name | subject |
+----+-----------+-------------------------+
| 1 | yosemite | National Park, Yosemite |
+----+-----------+-------------------------+
which the above query does. However, I also want to query for a subject. So that National Park filter will yield:
+----+-----------+-------------------------+
| id | name | subject |
+----+-----------+-------------------------+
| 1 | yosemite | National Park, Yosemite |
| 2 | bryce | National Park |
+----+-----------+-------------------------+
I'd like to be more precise/efficient than LIKE with the already 'concat' subject. Most of my attempts end up with no results in Room (multi-row) or reducing the subject to only the filter keyword.
Update
Here's a test I've been using to compare the actual SQL results from a query to what Android Room ends up with:
http://sqlfiddle.com/#!7/0ac11/10/0
That join query is interpreted as four objects in Android Room, so I'm trying to reduce the rows, but retain the full subject results while filtering for any image containing the subject keyword.
If you want multiple keywords, then where and group by and having can be used:
select image_id
from image_subject
where subject_id in ('a', 'b', 'c') -- whatever
group by image-id
having count(distinct subject_id) = 3; -- same count as in `where`
This gets the result I need, though I'd love to hear a better option if this is particularly inefficient.
SELECT meta.*,
(SELECT GROUP_CONCAT(name)
FROM junction
JOIN subject
ON subject.id = junction.subjectId
WHERE junction.metaId = meta.id) AS keywords,
junction.subjectId
FROM meta
LEFT JOIN junction ON junction.metaId = meta.id
WHERE subjectId IN (1,2)
GROUP BY meta.id
+----+----------+------------------------+-----------+
| id | name | keywords | subjectId |
+----+----------+------------------------+-----------+
| 1 | yosemite | National Park,Yosemite | 2 |
| 2 | bryce | National Park | 1 |
+----+----------+------------------------+-----------+
http://sqlfiddle.com/#!7/86a76/13

How to tokenize a SQL Server column for use in frequency distribution in SSAS

I have a field in my table called Description . Here is an examples of a few records:
+-----------+------------+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------+------------+---------+---------+-------------------+---------------+-------------+-------------+
| RecordKey | RecordType | Price | Description | RecordNumber | DiscsinSet | Country | Company | DigitalAnalogCode | Genre | UPC | datecreated |
+-----------+------------+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------+------------+---------+---------+-------------------+---------------+-------------+-------------+
| 100488 | CD | 5.99 | Korngold, Honegger, Verdi, Wagner, Puccini, Leoncavallo, Giordano: Opera Arias + 'I Know Where I'm Going'. (Ellen Faull, soprano. Taken from the Sylvan Levin Opera Concert Broadcasts of 1951 & 1952. Total time: 65'47') | VAIA 1173 | 1 | AMERICA | VAI | M | Songs & Arias | 89948117322 | 42:38.4 |
| 100503 | CD | 11.98 | Puccini, Madama Butterfly. (Kirsten, Barioni, Nadell et al. New Orleans Opera/ Cellini. Rec.3/60) | VAIA 1054-2 | 2 | AMERICA | VAI | A | Opera | 89948105428 | 42:38.4 |
| 100516 | MV | 8.99 | Brahms, 8 Gypsy Songs. Schumann, 2 Short Gypsy Songs. Liszt, The 3 Gypsies. Verdi, The Gypsy Woman. J.Strauss, 'Gypsy Baron'- Song of Sapphi + Other Gypsy Songs by Balakirev, Varlamov, Tchaikovsky, Verstovskij, Dvorak & Lehar. (Ljuba Kazarnovskaya, soprano w.Mark Morash, piano. Rec.Moscow, 2/19/98) | 69503 | 1 | AMERICA | VAI | S | NULL | 89948695035 | 42:38.4 |
+-----------+------------+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------+------------+---------+---------+-------------------+---------------+-------------+-------------+
Apologies, if it is difficult to read, but the description field has a lot of text.
I would like to create a frequency distribution of every word in this field.
The output I would like would look something like this:
+-----------+-------+
| word | count |
+-----------+-------+
| Beethoven | 344 |
| Strauss | 34533 |
| Piano | 3 |
| Webber | 34 |
+-----------+-------+
If it makes more sense, could you point me in the right direction on how this can be achieved with SSAS?
If you have a separate list of valid words, you can just do:
select w.word, count(*)
from mytable t join
words w
on ', ' + w.word + ', ' like '%, ' + t.description + ', %'
group by w.word;
If you don't, then look around the web for a split() function. You can then use cross apply for something like:
select w.value, count(*)
from mytable t cross apply
(select *
from split(t.description, ', ')
) w
group by w.value;
If you have control over the data structure, then naughty, naughty. SQL has this wonderful data structure for storing lists. It is called a table. It is not called a string. You should be using a junction table -- if you have control. One doesn't always have control over such issues, though.

Grouped string aggregation / LISTAGG for SQL Server

I'm sure this has been asked but I can't quite find the right search terms.
Given a schema like this:
| CarMakeID | CarMake
------------------------
| 1 | SuperCars
| 2 | MehCars
| CarMakeID | CarModelID | CarModel
-----------------------------------------
| 1 | 1 | Zoom
| 2 | 1 | Wow
| 3 | 1 | Awesome
| 4 | 2 | Mediocrity
| 5 | 2 | YoureSettling
I want to produce a dataset like this:
| CarMakeID | CarMake | CarModels
---------------------------------------------
| 1 | SuperCars | Zoom, Wow, Awesome
| 2 | MehCars | Mediocrity, YoureSettling
What do I do in place of 'AGG' for strings in SQL Server in the following style query?
SELECT *,
(SELECT AGG(CarModel)
FROM CarModels model
WHERE model.CarMakeID = make.CarMakeID
GROUP BY make.CarMakeID) as CarMakes
FROM CarMakes make
http://www.simple-talk.com/sql/t-sql-programming/concatenating-row-values-in-transact-sql/
It is an interesting problem in Transact SQL, for which there are a number of solutions and considerable debate. How do you go about producing a summary result in which a distinguishing column from each row in each particular category is listed in a 'aggregate' column? A simple, and intuitive way of displaying data is surprisingly difficult to achieve. Anith Sen gives a summary of different ways, and offers words of caution over the one you choose...
If it is SQL Server 2017 or SQL Server VNext, Azure SQL database you can use String_agg as below:
SELECT make.CarMakeId, make.CarMake,
CarModels = string_agg(model.CarModel, ', ')
FROM CarModels model
INNER JOIN CarMakes make
ON model.CarMakeId = make.CarMakeId
GROUP BY make.CarMakeId, make.CarMake
Output:
+-----------+-----------+---------------------------+
| CarMakeId | CarMake | CarModels |
+-----------+-----------+---------------------------+
| 1 | SuperCars | Zoom, Wow, Awesome |
| 2 | MehCars | Mediocrity, YoureSettling |
+-----------+-----------+---------------------------+