How to generate SQL output with pipes with multiple tables - sql

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 |

Related

Pull NULL if column not present in table while UNION SQL Server

I am currently building a dynamic SQL query. The tables and columns are sent as parameters. So the columns may not be present in the table. Is there a way to pull NULL data in the result set when the column is not present in the table?
ex:
SELECT * FROM Table1
Output:
created date | Name | Salary | Married
-------------+-------+--------+----------
25-Jan-2016 | Chris | 2500 | Y
27-Jan-2016 | John | 4576 | N
30-Jan-2016 | June | 3401 | N
So when I run the query below
SELECT Created_date, Name, Age, Married
FROM Table1
I need to get
created date | Name | AGE | Married
-------------+-------+--------+----------
25-Jan-2016 | Chris | NULL | Y
27-Jan-2016 | John | NULL | N
30-Jan-2016 | June | NULL | N
Does anything like IF NOT EXISTS or ISNULL work in this?
I can't use extensive T-SQL in this segment and need to be simple since I am creating a UNION query to more than 50 tables (requirement :| ) . Any advice would be of great help to me.
I can't think of an easy solution. Since you're using dynamic sql, instead of
(previous dynamic string part)+' fieldname '+(next dynamic string part)
you could use
(previous dynamic string part)
+ case when exists (
select 1
from sys.tables t
inner join sys.columns c on t.object_id=c.object_id
where c.name=your_field_name and t.name=your_table_name)
) then ' fieldname ' else ' NULL ' end
+(next dynamic string part)

Concat multiple rows PSQL

id | name | Subject | Lectured_Times | Faculty
3258132 | Chris Smith | SATS1364 | 10 | Science
3258132 | Chris Smith | ECTS4605 | 9 | Engineering
How would I go about creating the following
3258132 Chris Smith SATS1364, 10, Science + ECTS4605, 9,Engineering
where the + is just a new line. Notice how after the '+'(new line) it doesnt concat the id,name
try
SELECT distinct concat(id,"name",string_agg(concat(subject, Lectured_Times , Faculty), chr(10)))
from tn
where id = 3258132
group by id;
As mentioned above string_agg is perfect solution for this.
select
id, name, string_agg(concat(subject, Lectured_Times, Faculty), '\n')
from table
group by id, name

SQL comma separated column loop

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.

Splitting a string column in BigQuery

Let's say I have a table in BigQuery containing 2 columns. The first column represents a name, and the second is a delimited list of values, of arbitrary length. Example:
Name | Scores
-----+-------
Bob |10;20;20
Sue |14;12;19;90
Joe |30;15
I want to transform into columns where the first is the name, and the second is a single score value, like so:
Name,Score
Bob,10
Bob,20
Bob,20
Sue,14
Sue,12
Sue,19
Sue,90
Joe,30
Joe,15
Can this be done in BigQuery alone?
Good news everyone! BigQuery can now SPLIT()!
Look at "find all two word phrases that appear in more than one row in a dataset".
There is no current way to split() a value in BigQuery to generate multiple rows from a string, but you could use a regular expression to look for the commas and find the first value. Then run a similar query to find the 2nd value, and so on. They can all be merged into only one query, using the pattern presented in the above example (UNION through commas).
Trying to rewrite Elad Ben Akoune's answer in Standart SQL, the query becomes like this;
WITH name_score AS (
SELECT Name, split(Scores,';') AS Score
FROM (
(SELECT * FROM (SELECT 'Bob' AS Name ,'10;20;20' AS Scores))
UNION ALL
(SELECT * FROM (SELECT 'Sue' AS Name ,'14;12;19;90' AS Scores))
UNION ALL
(SELECT * FROM (SELECT 'Joe' AS Name ,'30;15' AS Scores))
))
SELECT name, score
FROM name_score
CROSS JOIN UNNEST(name_score.score) AS score;
And this outputs;
+------+-------+
| name | score |
+------+-------+
| Bob | 10 |
| Bob | 20 |
| Bob | 20 |
| Sue | 14 |
| Sue | 12 |
| Sue | 19 |
| Sue | 90 |
| Joe | 30 |
| Joe | 15 |
+------+-------+
If someone is still looking for an answer
select Name,split(Scores,';') as Score
from (
# replace the inner custome select with your source table
select *
from
(select 'Bob' as Name ,'10;20;20' as Scores),
(select 'Sue' as Name ,'14;12;19;90' as Scores),
(select 'Joe' as Name ,'30;15' as Scores)
);

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).