so I have this left join
LEFT JOIN LATERAL (SELECT d.country FROM db.patient_info d
WHERE d.id IN (SELECT DISTINCT st.category FROM db.surgery_types st, db.surgery_record sr
WHERE sr.id = st.surgery_record_id AND sr.surgery_type_id = m.id)
ORDER BY d.priority, d.country
LIMIT 1
) c ON TRUE
the issue is that sometimes d.country comes back null. How can I add a case statement in the left join so that when d.country IS NULL then 'USA'?
My results look like this
Patient Name
Surgery Type
Dave
USA
Richard
EU
Ben
EU
Sally
JP
Bob
null
Dicky
null
I want to modify the left join so that it looks more like this
Patient Name
Surgery Type
Dave
USA
Richard
EU
Ben
EU
Sally
JP
Bob
USA
Dicky
USA
Thoughts?
Use coalesce which returns the first non-null value.
-- I have no idea if this lateral join is valid.
LEFT JOIN LATERAL (
SELECT coalesce(d.country, 'USA')
FROM db.patient_info d
WHERE d.id IN (
SELECT DISTINCT st.category
FROM db.surgery_types st, db.surgery_record sr
WHERE sr.id = st.surgery_record_id AND sr.surgery_type_id = m.id
)
ORDER BY d.priority, d.country
LIMIT 1
) c ON TRUE
Though the order by will still use null so it might not sort properly. You might want to split this into a CTE.
-- Again, no idea if the lateral join is valid,
-- just showing a technique.
with countries as(
SELECT coalesce(d.country, 'USA') as country
FROM db.patient_info d
WHERE d.id IN (
SELECT DISTINCT st.category
FROM db.surgery_types st
JOIN db.surgery_record sr ON sr.id = st.surgery_record_id
-- Don't know what m is
WHERE sr.surgery_type_id = m.id
)
)
with first_country as (
select country
from countries
order by priority, country
limit 1
)
select
...
LEFT JOIN LATERAL countries on true
Finally, it might be simpler and faster to update the table to set all null countries to USA, and then make the column not null.
Not looking into your business logic and whether a lateral join is needed at all or a scalar subquery in the select list of expressions would be enough, here is my suggestion.
CROSS JOIN LATERAL
(
select coalesce
(
( /* your lateral subquery in the brackets here */),
'USA'
) as country
) as c
You do not need left join anymore. Please note that this will only work if the subquery is scalar.
Related
I have three tables, and I join them and use where Group by - count, I could not get the countries with zero results in the output. I am still lost.
Here is the SQLfiddle
http://sqlfiddle.com/#!4/e330ec/7
CURRENT OUTPUT
(UKD) 3
(EUR) 2
(USA) 2
(CHE) 1
EXPECTED OUTPUT
(UKD) 3
(EUR) 2
(IND) 0
(LAO) 0
(USA) 2
(CHE) 1
You can use a RIGHT JOIN as suggested in another answer or you can reorder your joins and use a LEFT JOIN:
SELECT
C.COUNTRY_CODE,
COUNT(GAME_TYPE)
FROM
COUNTRY_TABLE C
LEFT JOIN PLAYER_TABLE P ON P.COUNTRY_ID = C.COUNTRY_ID
LEFT JOIN PLAYER_GAME_TYPE G ON P.PLAYER_ID = G.PLAYER_ID
WHERE
G.GAME_TYPE = 'GOLF'
OR G.GAME_TYPE IS NULL
GROUP BY
C.COUNTRY_CODE;
Note the inclusion of OR G.GAME_TYPE IS NULL in the WHERE clause -- if you only have G.GAME_TYPE = 'GOLF', then desired results will be filtered out after the joins.
You can prefer applying the following steps as an option :
convert second LEFT JOIN to RIGHT JOIN(since desired missing abbreviations are in COUNTRY_TABLE which stays at right)
make the filtering condition(followed by the WHERE clause) G.GAME_TYPE = 'GOLF' a match condition
by taking next to the ON clause
such as
SELECT C.COUNTRY_CODE, COUNT(GAME_TYPE)
FROM PLAYER_TABLE P
LEFT JOIN PLAYER_GAME_TYPE G
ON P.PLAYER_ID = G.PLAYER_ID
RIGHT JOIN COUNTRY_TABLE C
ON P.COUNTRY_ID = C.COUNTRY_ID
AND G.GAME_TYPE = 'GOLF'
GROUP BY C.COUNTRY_CODE;
Demo
The simple change of tables join order can solve the problem
SELECT C.COUNTRY_CODE, COUNT(GAME_TYPE)
FROM COUNTRY_TABLE C -- get all countries
LEFT JOIN PLAYER_TABLE P ON P.COUNTRY_ID = C.COUNTRY_ID -- join all players
LEFT JOIN PLAYER_GAME_TYPE G ON P.PLAYER_ID = G.PLAYER_ID AND G.GAME_TYPE = 'GOLF' -- join only GOLF games
GROUP BY C.COUNTRY_CODE;
sqlize online
This is your query:
SELECT C.COUNTRY_CODE, COUNT(GAME_TYPE)
FROM PLAYER_TABLE P
LEFT JOIN PLAYER_GAME_TYPE G ON P.PLAYER_ID = G.PLAYER_ID
LEFT JOIN COUNTRY_TABLE C ON P.COUNTRY_ID = C.COUNTRY_ID
WHERE G.GAME_TYPE = 'GOLF'
GROUP BY C.COUNTRY_CODE;
This query seems to try to select all players even when they are no golfers. This doesn't work, however, as WHERE G.GAME_TYPE = 'GOLF' removes all outer joined rows, so you end up with an inner join (all players who play golf.) At last you outer join the countries table, which would give you players that don't belong to a country. Is this indented? I don't think so.
What you want is countries, so select from countries. Then properly outer join players and types in order to count them.
SELECT c.country_code, COUNT(g.game_type) as golfers_in_country
FROM country_table c
LEFT JOIN player_table p ON p.country_id = c.country_id
LEFT JOIN player_game_type g ON g.player_id = p.player_id AND g.game_type = 'GOLF'
GROUP BY c.country_code
ORDER BY c.country_code;
You can use a CTE to get this more readable. It is longer, but makes the intention crystal-clear. Structuring one's queries like this helps avoiding mistakes.
WITH golfers AS
(
SELECT *
FROM player_table
WHERE player_id IN
(
SELECT player_id
FROM player_game_type
WHERE game_type = 'GOLF'
)
)
SELECT c.country_code, COUNT(g.player_id) as golfers_in_country
FROM country_table c
LEFT JOIN golfers g ON g.country_id = c.country_id
GROUP BY c.country_code
ORDER BY c.country_code;
If record exists in the table i need to bring the data from the highest address record linked to the person.
Example:
John Doe have no address at all. Report need to still bring John Doe name but nothing as an address.
John Doe have 3 addresses with address number increasing. Report need to bring John Doe name and only the address with the highest address number.
Code I tried:
Select *
from person p left join address a on p.id = a.person and a.addressnumber = (select max(a2.addressnumber) from address a2 where a2.a_peron = p.id)
Oracle returns error: ORA-01799: a column may not be outer-joined to a
subquery
01799. 00000 - "a column may not be outer-joined to a subquery"
I also tried
Select *
from person p left join address a1 on p.id = a1.person
inner join (select a.person, max(a.addressnumber) MaxAdd, a.postcode, a.country from address a group by a.person, a.postcode, a.country) main on main.person = p.id and main.MaxAdd = a1.addressnumber
This doesnt work neither due to the grouping.
I can probably get this done by using subqueries in the select itself together with the case statement but i would like to avoid that because I will be pulling a lot of data from the address so this would mean case statement with subquery for every single column.
Oracle 11g - 11.2
Any idea? :D
You can use ROW_NUMBER to rank your rows and only keep the last one:
select *
from person p
left join
(
select
ad.*,
row_number() over (partition by person order by addressnumber desc) as rn
from address ad
) a on a.person = p.id and a.rn = 1;
Please try this:
select * from
person p left join
(select a.person, max(a.addressnumber) MaxAdd, a.postcode, a.country from address a
group by a.person, a.postcode, a.country) A
on p.id=A.person
Use row_number():
Select *
from person p left join
(select a.*,
row_number() over (partition by a.person order by a.addressnumber desc) as seqnum
from address a
) a
on p.id = a.person and seqnum = 1;
I'm trying to create a table that with one column containing the number of countries and the next column being the number of official countries. So basically, one row might say there are 32 countries that have 2 official languages, 28 with 3 official languages, etc.
So far I've made a table that counts the number of official languages per each individual country.
select c.name, count(l.language) number_of_languages from world.country c
inner join (select countrycode, language, isofficial from
world.countrylanguage where isofficial='T') l on c.code=l.countrycode group
by c.name order by count(l.language)
Here's a sample of the result:
NAME NUMBER_OF_LANGUAGES
---------------------------------------------------- -------------------
Lesotho 2
Sri Lanka 2
Canada 2
Singapore 3
Belgium 3
Bolivia 3
First, your query can be simplified. It doesn't need to use a subquery:
select c.name, count(cl.language) as number_of_languages
from world.country c inner join
world.countrylanguage cl
on c.code = cl.countrycode
where cl.isofficial = 'T'
group by c.name
order by count(cl.language);
Next, use this as a subquery:
select number_of_languages, count(*)
from (select c.name, count(cl.language) as number_of_languages
from world.country c inner join
world.countrylanguage cl
on c.code = cl.countrycode
where cl.isofficial = 'T'
group by c.name
) cl
group by number_of_languages
order by number_of_languages;
I'm completely new to SQL and need a bit of help.
I have 3 tables in my SQL Server 2008 database. In each table there are different kinds of users (coworkers, students, teachers). All of them have Nicknames. There is also a table with all users.
I would like to join all of them into one single table with one column called nickname. In this column the three different types of users are supposed to be sorted in packages like so regardless of their nickname:
coworker1 coworker2 coworker2 student1 student2 student3 teacher1 teacher2
I tried the following way:
SELECT A.Nickname, B.Nickname, C.Nickname, D.Nickname
FROM users AS A LEFT OUTER JOIN coworkers AS B ON
A.Nickname = B.Nickname
LEFT OUTER JOIN teachers AS C ON
A.Nickname = C.Nickname
LEFT OUTER JOIN students AS D ON
A.Nickname = D.Nickname
Well that does not work at all and gives me a table with 4 columns each full with NULLs and unsorted users
Thanks HABO this did it for me:
select Nickname from (
SELECT Coalesce( B.Nickname, C.Nickname, D.Nickname, A.Nickname ) as Nickname,
case
when B.Nickname is not NULL then 1
when C.Nickname is not NULL then 3
when D.Nickname is not NULL then 2
else 0 end as Package
FROM users AS A LEFT OUTER JOIN coworkers AS B ON
A.Nickname = B.Nickname
LEFT OUTER JOIN teachers AS C ON
A.Nickname = C.Nickname
LEFT OUTER JOIN students AS D ON
A.Nickname = D.Nickname ) as Elmer
order by Package, Nickname
It sounds like you could just union and order your three user type tables instead of doing a bunch of joins.
Try something like this:
SELECT s.nickname nickname, ..., 'student' type FROM students s
UNION
SELECT t.nickname nickname, ..., 'teacher' type FROM teachers t
UNION
...
ORDER BY type;
Does this closer to what you are trying to accomplish?
If you only want the non-NULL column output:
SELECT Coalesce( B.Nickname, C.Nickname, D.Nickname, A.Nickname ) as Nickname
FROM users AS A LEFT OUTER JOIN coworkers AS B ON
A.Nickname = B.Nickname
LEFT OUTER JOIN teachers AS C ON
A.Nickname = C.Nickname
LEFT OUTER JOIN students AS D ON
A.Nickname = D.Nickname
Note that the columns have been rearranged so that the nickname from users is the last choice.
Your comment regarding sorting into packages is unclear to me. Do you mean something like this:
select Nickname from (
SELECT Coalesce( B.Nickname, C.Nickname, D.Nickname, A.Nickname ) as Nickname,
case
when B.Nickname is not NULL then 1
when C.Nickname is not NULL then 3
when D.Nickname is not NULL then 2
else 0 end as Package
FROM users AS A LEFT OUTER JOIN coworkers AS B ON
A.Nickname = B.Nickname
LEFT OUTER JOIN teachers AS C ON
A.Nickname = C.Nickname
LEFT OUTER JOIN students AS D ON
A.Nickname = D.Nickname ) as Elmer
order by Package, Nickname
I am having trouble writing a query that will select all Skills, joining the Employee and Competency records, but only return one skill per employee, their newest Skill. Using this sample dataset
Skills
======
id employee_id competency_id created
1 1 1 Jan 1
2 2 2 Jan 1
3 1 2 Jan 3
Employees
===========
id first_name last_name
1 Mike Jones
2 Steve Smith
Competencies
============
id title
1 Problem Solving
2 Compassion
I would like to retrieve the following data
Skill.id Skill.employee_id Skill.competency_id Skill.created Employee.id Employee.first_name Employee.last_name Competency.id Competency.title
2 2 2 Jan 1 2 Steve Smith 2 Compassion
3 1 2 Jan 3 1 Mike Jones 2 Compassion
I was able to select the employee_id and max created using
SELECT MAX(created) as created, employee_id FROM skills GROUP BY employee_id
But when I start to add more fields in the select statement or add in a join I get the 'Column 'xyz' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.' error.
Any help is appreciated and I don't have to use GROUP BY, it's just what I'm familiar with.
The error that you were getting is because SQL Server requires any item in the SELECT list to be included in the GROUP BY if there is an aggregate function being used.
The problem with that is you might have unique values in some columns which can throw off the result. So you will want to rewrite the query to use one of the following:
You can use a subquery to get this result. This gets the max(created) in a subquery and then you use that result to get the correct employee record:
select s.id SkillId,
s.employee_id,
s.competency_id,
s.created,
e.id employee,
e.first_name,
e.last_name,
c.id competency,
c.title
from Employees e
left join Skills s
on e.id = s.employee_id
inner join
(
SELECT MAX(created) as created, employee_id
FROM skills
GROUP BY employee_id
) s1
on s.employee_id = s1.employee_id
and s.created = s1.created
left join Competencies c
on s.competency_id = c.id
See SQL Fiddle with Demo
Or another way to do this is to use row_number():
select *
from
(
select s.id SkillId,
s.employee_id,
s.competency_id,
s.created,
e.id employee,
e.first_name,
e.last_name,
c.id competency,
c.title,
row_number() over(partition by s.employee_id
order by s.created desc) rn
from Employees e
left join Skills s
on e.id = s.employee_id
left join Competencies c
on s.competency_id = c.id
) src
where rn = 1
See SQL Fiddle with Demo
For every non-aggregated column you add to your SELECT statement you need to update your GROUP BY to include it.
This article may help you understand why.
;WITH
MAX_SKILL_created AS
(
SELECT
MAX(skills.created) as created,
skills.employee_id
FROM
skills
GROUP BY
skills.employee_id
),
MAX_SKILL_id AS
(
SELECT
MAX(skills.id) as id,
skills.employee_id
FROM
skills
INNER JOIN MAX_SKILL_created
ON MAX_SKILL_created.employee_id = skills.employee_id
AND MAX_SKILL_created.created = skills.created
GROUP BY
skills.employee_id
)
SELECT
* -- type all your columns here
FROM
employees
INNER JOIN MAX_SKILL_id
ON MAX_SKILL_id.employee_id = employees.employee_id
INNER JOIN skills
ON skills.id = MAX_SKILL_id.id
INNER JOIN competencies
ON competencies.id = skills.competency_id
If you are using SQL Server than you can use OUTER APPLY
SELECT *
FROM employees E
OUTER APPLY (
SELECT TOP 1 *
FROM skills
WHERE employee_id = E.id
ORDER BY created DESC
) S
INNER JOIN competencies C
ON C.id = S.competency_id