SQL comma separated column loop - sql

I have a serious problem with SQL that already took me 3 hours. I have two tables like these:
First table: Employees
ID | NAME
---+--------
1 | John
2 | Mike
3 | Robert
Second table: Customers
ID | NAME | EMPLOYEES
---+---------+--------------
1 | Michael | 2,3
2 | Julia | 1
3 | Mila | 1,2,3
I want the output like this:
Michael | Mike, Robert
Julia | John
Mila | John, Mike, Robert
What should the SQL command to get the expected output?

Select A.Name
,Employees = (Select Stuff((Select Distinct ',' +Name From Employees Where charindex(','+cast(ID as varchar(25))+',',','+A.EMPLOYEE_ID+',')>0 For XML Path ('')),1,1,'') )
From Customers A
Returns
Name Employees
Michael Mike,Robert
Julia John
Mila John,Mike,Robert

This is an awful data structure and you should fix it. That is the primary thing. Storing numbers as strings is bad. Storing multiple values in a column is bad. Not declaring foreign key relationships is bad.
That said, what can you do if someone else set up such a database and did so in this bad way? Well, you can do:
select c.*, e.name
from customers c join
employees e
on ',' + cast(e.id as varchar(255)) + ',' like '%,' + c.employee_id + ',%';
Note that this query cannot be optimized using normal SQL methods, such as indexes, because the JOIN condition is too complicated.
This will give you more rows than you have asked for:
Michael Mike
Michael Robert
Julia John
Mila John
Mila Mike
Mila Robert
However, this is the normal way that SQL works, so you should get used to it.

Related

Concat data from different rows into one in BigQuery

i want to concat my data from a particular column which is present in different rows.
The Data is something like this:
id | Name |
1 | Jack, John |
2 | John |
3 | John, Julie |
4 | Jack |
5 | Jack, Julie |
I want the output as Jack, John, Julie. Every name should be unique.
I tried using string_agg(distinct Name), but the result is coming out as (Jack, John, John, John, Julie, Jack, Jack, Julie).
How can i solve this issue and get desired result?
Thanks in advance
Use below
select string_agg(distinct trim(nm), ', ') as names
from your_table, unnest(split(name)) nm
if applied to sample data in your question - output is
Does this work for you? if it works, please mark as answer
WITH DistinctValues AS(
SELECT DISTINCT
V.DenormalisedData,
SS.[Value]
FROM (VALUES((Select SUBSTRING(( SELECT ',' + trim(Name) AS 'data()' FROM TableName FOR XML PATH('') ), 2 , 9999))))V(DenormalisedData)
CROSS APPLY STRING_SPLIT(V.DenormalisedData,',') SS)
SELECT STRING_AGG(DV.[Value],',') AS RedenormalisedData
FROM DistinctValues DV
GROUP BY DenormalisedData;

How to generate SQL output with pipes with multiple tables

I am using SQL Server.
I have a table of students like this:
StudentID TeacherNumber
123 1
124 1
125 2
126 2
127 1
128 3
I also have a table of teachers like this:
TeacherNumber TeacherName
1 Adams
2 Johnson
3 Marks
I need to have output that looks like this:
TeacherNumber Teacher Students
1 Adams 123|124|127
2 Johnson 125|126
3 Marks 128
I appreciate your help. Thank you.
I posted a similar question previously, and got a response that worked here:
How to generate sql output as piped
Now that I added another table I am having trouble. I appreciate the help.
Fiddle:
http://sqlfiddle.com/#!6/27600/29/0
Query:
select distinct st1.teachernumber,
teachername as teacher,
stuff(( select '|' + cast(st2.studentid as varchar(20))
from students st2
where st1.teachernumber = st2.teachernumber
order by st2.studentid
for xml path('')
),1,1,'') as students
from students st1
join teachers t
on st1.teachernumber = t.teachernumber
The reason I had to convert STUDENTID to VARCHAR is because by adding the pipe character that data type would no longer be valid and you'd get an error. You have to cast it as varchar to get the pipe delimiter to work with an integer field (I assume STUDENTID is an INT field).
Output:
| TEACHERNUMBER | TEACHER | STUDENTS |
|---------------|---------|-------------|
| 1 | Adams | 123|124|127 |
| 2 | Johnson | 125|126 |
| 3 | Marks | 128 |

Dynamically creating Columns from rows Access 2010

I am relatively new to Access and I have a table that has AuthorName, BookTitle, and CoAuthor. AuthorName and BookTitle are a composite key.
Currently the query pulls information like:
AuthorName---------BookTitle------CoAuthor
Steven King--------Dark Half------Peter Straub
Steven King--------Dark Half------John Doe
James Patterson----Another Time
Jeff Hanson--------Tales of Time---Joe Smith
I would like it to read (dynamically) if possible
AuthorName---------BookTitle---------CoAuthor1--------CoAuthor2
Steven King----------Dark Half--------Peter Straub-----Joe Doe
James Patterson----Another Time
Jeff Hanson----------Tales of Time----Joe Smith
So if there is another author that is later added, a third column for CoAuthor would appear.
Is this possible with VBA or SQL?
What you are asking goes against the whole point of using a relational database; in short, you should design your tables in a way that minimizes (rather eliminates) the need to redesign the tables. I suggest you read about database normalization.
Your question is, as a matter of fact, an example that I use very frequently when I teach about databases: How would you design a database to hold all the information about books and authors. The scenarios are:
A book can have one or more authors
An author may have written one or more books
So, this is a many-to-many relation, and there's a way to design such a database.
First, design a table for the authors:
tbl_authors
author_idd (primary key, numeric, preferibly autonumeric)
first_name (String)
last_name (String)
Then, design a table for the books:
tbl_books
book_id (primary key, numeric, preferibly autonumeric)
book_title (String)
And finally, a third table is needed to relate authors and books:
tbl_books_authors
book_id (primary key, numeric)
author_id (primary key, numeric)
main_author (boolean: "Yes" if it is the main author, "No" otherwise)
(Both fields must be part of the primary key)
And now, the main question: How to query for books and its authors?
Asuming the above design, you could write an SQL query to get the full list of books and its authors:
select book_title, first_name, last_name
from
tbl_authors as a
inner join tbl_books_authors as ba on a.authorId = ba.authorId
inner join tbl_books as b on ba.bookId = b.bookId
This way, you'll have something like this:
book_title | first_name | last_name
-----------+------------+-----------
book1 | John | Doe
book1 | Jane | Doe
book2 | John | Doe
book2 | Peter | Who
book3 | Jane | Doe
book4 | Peter | Who
book5 | John | Doe
book5 | Jane | Doe
book5 | Peter | Who
book5 | Jack | Black
Why is this design better than your original idea?
Because you won't need to alter the structure of your tables to add another author
Because you don't know a priori how many authors a book can have
Because you avoid redundancy in your database
Because, with this design, you'll be able to use front-end tools (like Access forms and reports, or other tools) to create almost any arraingment from this data.
Further reading:
The Access Web (specially "The Ten Commandments of Access")
Minor update
This kind of design will help you avoid lots and lots of headaches in the future, because you won't need to alter your tables every time you add a third or fourth author. I learned about database normalization some years ago reading "Running Access 97" by Jon Viescas (this is not a commercial, it's just a reference ;) )... an old book, yes, but it has a very good introduction on the topic.
Now, after you have normalized your database, you can use pivot queries to get what you need (as noted in the answer posted by Conrad Frix).
If your table had type like below
+-----------------+---------------+--------------+-----------+
| AuthorName | BookTitle | CoAuthor | Type |
+-----------------+---------------+--------------+-----------+
| Steven King | Dark Half | Peter Straub | CoAuthor1 |
| Steven King | Dark Half | John Doe | CoAuthor2 |
| James Patterson | Another Time | | CoAuthor1 |
| Jeff Hanson | Tales of Time | Joe Smith | CoAuthor1 |
+-----------------+---------------+--------------+-----------+
it would be a pretty simple transform
TRANSFORM First(Books.CoAuthor) AS FirstOfCoAuthor
SELECT Books.AuthorName, Books.BookTitle
FROM Books
GROUP BY Books.AuthorName, Books.BookTitle
PIVOT Books.Type;
Since it doesn't we need to create it on the fly by first assigning a number to each row simulating ROW_NUMBER OVER and then transforming. On large data sets this may be quite slow
TRANSFORM First(b.coauthor) AS firstofcoauthor
SELECT b.authorname,
b.booktitle
FROM (SELECT authorname,
booktitle,
coauthor,
'CoAuthor' & k AS Type
FROM (SELECT b.authorname,
b.booktitle,
b.coauthor,
Count(*) AS K
FROM books AS b
LEFT JOIN books AS b1
ON b.authorname = b1.authorname
WHERE [b].[coauthor] <= [b1].[coauthor]
OR (( ( b1.coauthor ) IS NULL ))
GROUP BY b.authorname,
b.booktitle,
b.coauthor) AS t) AS b
GROUP BY b.authorname,
b.booktitle
PIVOT b.type

Generating a hierarchy

I got the following question at a job interview and it completely stumped me, so I'm wondering if anybody out there can help explain it to me. Say I have the following table:
employees
--------------------------
id | name | reportsTo
--------------------------
1 | Alex | 2
2 | Bob | NULL
3 | Charlie | 5
4 | David | 2
5 | Edward | 8
6 | Frank | 2
7 | Gary | 8
8 | Harry | 2
9 | Ian | 8
The question was to write a SQL query that returned a table with a column for each employee's name and a column showing how many people are above that employee in the organization: i.e.,
hierarchy
--------------------------
name | hierarchyLevel
--------------------------
Alex | 1
Bob | 0
Charlie | 3
David | 1
Edward | 2
Frank | 1
Gary | 2
Harry | 1
Ian | 2
I can't even figure out where to begin writing this as a SQL query (a cursor, maybe?). Can anyone help me out in case I get asked a similar question to this again? Thanks.
The simplest example would be to use a (real or temporary) table, and add one level at a time (fiddle):
INSERT INTO hierarchy
SELECT id, name, 0
FROM employees
WHERE reportsTo IS NULL;
WHILE ((SELECT COUNT(1) FROM employees) <> (SELECT COUNT(1) FROM hierarchy))
BEGIN
INSERT INTO hierarchy
SELECT e.id, e.name, h.hierarchylevel + 1
FROM employees e
INNER JOIN hierarchy h ON e.reportsTo = h.id
AND NOT EXISTS(SELECT 1 FROM hierarchy hh WHERE hh.id = e.id)
END
Other solutions will be slightly different for each RDBMS. As one example, in SQL Server, you can use a recursive CTE to expand it (fiddle):
;WITH expanded AS
(
SELECT id, name, 0 AS level
FROM employees
WHERE reportsTo IS NULL
UNION ALL
SELECT e.id, e.name, level + 1 AS level
FROM expanded x
INNER JOIN employees e ON e.reportsTo = x.id
)
SELECT *
FROM expanded
ORDER BY id
Other solutions include recursive stored procedures, or even using dynamic SQL to iteratively increase the number of joins until everybody is accounted for.
Of course all these examples assume there are no cycles and everyone can be traced up the chain to a head honcho (reportsTo = NULL).

How to join (without concatenating) two result sets of two fields together as one long ‘list’

What SQL Code do I require to perform the following? :
I have one table (lets arbitrarily call the table 'Names'):
ID | Name1 | Name2
---+--------+-------
1 | Fred | Jack
2 | Jack | Jim
3 | Jill | Fred
4 | Jim | Jack
etc
What I'd like is to produce is a single list of Name1 and Name2 (I don't even care about Grouping or Ordering) as such, but I would like to keep the original 'ID' association with the name:
ID | Names
---+------
1 | Fred
1 | Jack
2 | Jill
2 | Jim
3 | Jack
3 | Jim
4 | Fred
4 | Jack
Why do I want to do this? Because it looks easy and as a SQL coder I should probably be able to perform this task, but I can't figure out a solution that will create this output. Further more I've only manage to find people with the desire to concatenate the fields, which is a simple task, but I'm not interested in concatenation.
Additional Question: Would the SQL query be vastly different if Name1 field was in a different table to Name2? (if it is different, what would it look like?)
Additional Question: Would the SQL query be simpler if we didn't care about the ID field? If so, what would that look like.
You can use this form to include the id, and give you a specific ordering by ID.
SELECT n.id, n.name1 FROM names n
UNION
SELECT m.id, m.name2 from names m
ORDER BY id ASC;
If it were in a different table, the use of UNION doesn't have to change, since we're bringing the results from the table together, and ordering them by ID. This doesn't mean that the data is related, though.
SELECT n.id, n.name FROM name_one n
UNION
SELECT m.id, m.name from name_two m
ORDER BY id ASC;
If we didn't care about the ID field, it would be ever so lightly simpler - it's just selecting one column at that point.
SELECT ID, Name1 as Names FROM person
UNION
SELECT ID, Name2 as Names FROM person
?
. . What you mean by "without concatenating"?