I'm trying to create a SQL query to return the column values from a table that meet certain criteria.
Currently I have used the CONCAT function to join the first and last names into a single column in the query result for employees that have the role of 'Programmer'.
SELECT
person.id, CONCAT(person.firstname,' ', person.lastname) AS FULLNAME
FROM
person, role
WHERE
person.role_id = role.id AND role.name = 'Programmer'
This successfully runs and returns all programmers from the tables. Notice in my table structure I have an actingas_id column. This is the key to another person.id for people who are working on behalf of another people whilst they're on leave from work.
Thus, we arrive at my Question: How do I modify the SQL query such that when a person is acting that the query retrieves the first and last name of this person as well as the person who's 'shoes are being filled'?
My table structure is as follows:
person:
id | firstname | lastname | role_id | actingas_id |
role:
id | name |
+----+-----------+----------+---------+-------------+
| id | firstname | lastname | role_id | actingas_id |
+----+-----------+----------+---------+-------------+
| 1 | John | Smith | 1 | 0 |
| 2 | Kevin | Tull | 2 | 1 |
| 3 | Michael | Woods | 1 | 0 |
+----+-----------+----------+---------+-------------+
Here Kevin is Acting for for John, and Michael is also a Programmer, so the result of my query should be:
+----+-------------------------+
| id | NAME |
+----+-------------------------+
| 1 | John Smith - Kevin Tull |
| 3 | Michael Woods |
| x | Other Programmers.. |
+----+-------------------------+
This untested query should give you the result you whant:
SELECT person1.id, CASE WHEN person1.actingas_id =0 then CONCAT(person1.firstname,' ', person1.lastname) else CONCAT(person1.firstname,' ', person1.lastname,' - ', person2.firstname,' ', person2.lastname) AS FULLNAME
FROM person person1 left join person person2 on person1.actingas_id=person2.id
join role on person1.role_id=role.id
WHERE role.name = 'Programmer'
Use UNION ALL to add those additional records:
SELECT person.id,
CONCAT(person.firstname,' ', person.lastname) AS FULLNAME
FROM person INNER JOIN role
ON person.role_id = role.id
WHERE role.name = 'Programmer'
AND NOT EXISTS(SELECT 1 FROM person p WHERE p.actingas_id = person.id)
UNION ALL
SELECT a.id,
CONCAT(a.firstname,' ', a.lastname, ' - ', person.firstname,' ', person.lastname) AS FULLNAME
FROM person INNER JOIN person a
ON a.acting_as = person.id
INNER JOIN role
ON person.role_id = role.id
WHERE role.name = 'Programmer' AND a.actingas_id <> 0
Also, avoid using old style comma-separated JOINs. Use INNER JOINs
Related
I have the a postgre table with a similar representation to the data below.
Caters Table:
-----------------------
| Name | Option |
-----------------------
| jane | social |
| jane | vegan |
| jane | gmo-free |
| jane | italian |
| jack | social |
| jack | corporate |
| jack | gmo-free |
| jack | greek |
| rodz | social |
| rodz | wedding |
| rodz | gmo-free |
| rodz | vegan |
| rodz | french |
This is the "pseudo" query I'm trying to run
SELECT * FROM caters
WHERE option is either ['italian', 'french']
AND WHERE option is both ['wedding', 'social']
This pseudo query should return rodz. Because it either has italian or french and it has both wedding and social.
This is the query I tried to write to accomplish my sudo query
SELECT c.name FROM caters c
WHERE c.option in ('italian', 'french')
GROUP BY c.name
HAVING array_agg(c.option) #> array['wedding', 'social']
How ever this returns no results. Running the query individually
SELECT c.name FROM caters c
WHERE c.option in ('italian', 'french')
GROUP BY c.name
Result:
-----------
| Name |
-----------
| jane | // has italian
| rodz | // has french
The other query
SELECT c.name FROM caters c
GROUP BY c.name
HAVING array_agg(c.option) #> array['wedding', 'social']
Result:
-----------
| Name |
-----------
| rodz | // has wedding and social
So I can see individually the queries are correct. This made me think well if I have 2 queries giving me the correct results just need to filter out results that are in both queries why don't I JOIN them.
So I tried
SELECT c.name FROM caters c
JOIN caters c1
ON c1.name = c.name and c1.option = c.option
WHERE c1.option in ('italian', 'french')
GROUP BY c.name
HAVING array_agg(c.option) #> array['wedding', 'social']
But this also yielding no results. Any idea how I can go about this?
NOTE: The query is dynamic each time its ran the values being used could be different sometimes maybes it 5 languages sometimes its 2 languages like in this example ('italian', 'french'). To give an example what I mean by dynamic query another query could be
SELECT * FROM caters
WHERE option is either ['italian']
AND WHERE option is both ['corporate', 'social']
// returns none
----------------------------------------------------------
SELECT * FROM caters
WHERE option is either ['french', 'greek']
AND WHERE option is either ['gmo-free', 'vegan']
AND WHERE option is both ['corporate', 'social']
// returns jack
----------------------------------------------------------
SELECT * FROM caters WHERE option is ['social']
// returns jack, and rodz
You can try using a correlated subquery
DEMO
select distinct name from tablename a
where option in ('italian', 'french') and exists
(
select 1 from tablename b where a.name=b.name and option in ('wedding', 'social')
group by b.name having count(distinct option)=2
)
OUTPUT:
name
rodz
Here is one method:
SELECT c.name
FROM caters c
WHERE c.option in ('italian', 'french', 'wedding', 'social')
GROUP BY c.name
HAVING COUNT(*) FILTER (WHERE c.option IN ('italian', 'french')) >= 1 AND
COUNT(*) FILTER (WHERE c.option IN ('wedding', 'social')) = 2;
I have two tables in my PostgreSQL 9.6 instance.
users
+----+------------+-----------+-------------------+
| id | first_name | last_name | email |
+----+------------+-----------+-------------------+
| 1 | John | Doe | john.doe#test.com |
+----+------------+-----------+-------------------+
| 2 | Jane | Doe | jane.doe#test.com |
+----+------------+-----------+-------------------+
| 3 | Mike | Doe | mike.doe#test.com |
+----+------------+-----------+-------------------+
surveys
+----+---------+----------------------------------------------------------------------------------------------------+
| id | user_id | survey_data |
+----+---------+----------------------------------------------------------------------------------------------------+
| 1 | 1 | {'child_list': [{'gender': 1, 'birthday': '2015-10-01'}, {'gender': 2, 'birthday': '2017-05-01'}]} |
+----+---------+----------------------------------------------------------------------------------------------------+
| 2 | 2 | {'child_list': []} |
+----+---------+----------------------------------------------------------------------------------------------------+
| 3 | 3 | {'child_list': [{'gender': 2, 'birthday': '2008-01-01'}]} |
+----+---------+----------------------------------------------------------------------------------------------------+
I would like be able to query these two tables to get the number of users who have children between certain age. The survey_data column in surveys table is a JSONB column.
So far I've tried using jsonb_populate_recordset with LATERAL joins. I was able to SELECT the child_list array as two columns but couldn't figure out how to use that with my JOIN between users and surveys tables. The query I used is as below:
SELECT DISTINCT u.email
FROM surveys
CROSS JOIN LATERAL (
SELECT *
FROM jsonb_populate_recordset(null::json_type, (survey.survey_data->>'child_list')::jsonb) AS d
) d
INNER JOIN users u ON u.id = survey.user_id
WHERE d.birthday BETWEEN '2014-05-05' AND '2018-05-05';
This also uses a custom type which was created using this:
CREATE type json_type AS (gender int, birthday date)
My question is, is there an easier to read way to do this? I would like to use this query with many other JOINs and WHERE clauses and I was wondering if there is a better way of doing this.
Note: this is mainly going to be used by a reporting system which does not need to be super fast but of course any speed gains are welcome.
Use the function jsonb_array_elements(), examples:
select email, (elem->>'gender')::int as gender, (elem->>'birthday')::date as birthday
from users u
left join surveys s on s.user_id = u.id
cross join jsonb_array_elements(survey_data->'child_list') as arr(elem)
email | gender | birthday
-------------------+--------+------------
john.doe#test.com | 1 | 2015-10-01
john.doe#test.com | 2 | 2017-05-01
mike.doe#test.com | 2 | 2008-01-01
(3 rows)
or
select distinct email
from users u
left join surveys s on s.user_id = u.id
cross join jsonb_array_elements(survey_data->'child_list') as arr(elem)
where (elem->>'birthday')::date between '2014-05-05' and '2018-05-05';
email
-------------------
john.doe#test.com
(1 row)
You can make your life easier using a view:
create view users_children as
select email, (elem->>'gender')::int as gender, (elem->>'birthday')::date as birthday
from users u
left join surveys s on s.user_id = u.id
cross join jsonb_array_elements(survey_data->'child_list') as arr(elem);
select distinct email
from users_children
where birthday between '2014-05-05' and '2018-05-05';
I have a database of names, some of the names haven't been insert in the correct fashion. SecondName has sometimes been entered as FirstName.
+-----------------+--------------+
| FirstName | SecondName |
+-----------------+--------------+
| Bob | Smith |
| Gary | Rose |
| John | Jones |
| Smith | Bob |
| Gary | Oberstein |
| Adam | Sorbet |
| Jones | John |
+-----------------+--------------+
I've tried different grouping queries
select `FirstName`
, `SecondName`
from `names`
where ( `FirstName`
, `SecondName` )
in ( select `FirstName`
, `SecondName`
from `names`
group
by `FirstName`
, `SecondName`
having count(*) > 1
)
But I can't get anything to produce
+-----------------+--------------+---------+
| FirstName | SecondName | Count |
+-----------------+--------------+---------+
| Bob | Smith | 2 |
| John | Jones | 2 |
+-----------------+--------------+---------+
There is a trick to do this, you need to normalize your names, a quick way to do this is if you alphabetize first name and last name then group on the result.
SELECT name_normalized, count(*) as c
FROM (
SELECT CASE WHEN FIRSTNAME < LASTNAME THEN FIRSTNAME||LASTNAME
ELSE LASTNAME|| FIRSTNAME END as name_normalized
FROM names
) X
GROUP BY name_normalized
Notes:
This is the simple case, you could add the normalized result as a column if you want to see the original values.
You may need other normalization -- it depends on what your rules are. For example UPPER() to ignore case and TRIM() to remove whitespace.
You can add or ignore other columns as is required for matching normalization -- Birthday, Middle Initial etc.
Oten time a hash on the normalized string is faster to work with than the string -- your data model might require one or the other.
If the COUNT() itself isn't important, you can easily separate duplicates with an INNER JOIN
SELECT n.FirstName, n.SecondName, n2.FirstName, n2.SecondName
FROM Names n
INNER JOIN Names n2 on n.FirstName = n2.SecondName and n.SecondName = n2.FirstName
Apologies if a similar problem is posted earlier, I couldn't find the same.
Problem: I need to join two tables based a conditional look up in the second table.
Tables: Below are the two tables which have a subset of the total fields.
+-------------------------------------------------------+
| Persons |
+----------+------------+---------------+---------------+
| PersonID | PersonName | HomeAddressID | WorkAddressID |
+----------+------------+---------------+---------------+
| P1 | Doe, John | HA1 | WA1 |
+----------+------------+---------------+---------------+
| P2 | Doe, Jane | HA2 | WA2 |
+----------+------------+---------------+---------------+
| P3 | Doe, Jane | | WA3 |
+----------+------------+---------------+---------------+
+-----------------------------------+
| Addresses |
+-----------+--------+------+-------+
| AddressID | Street | City | State |
+-----------+--------+------+-------+
| HA1 | 123 | A | B |
+-----------+--------+------+-------+
| WA1 | 456 | C | D |
+-----------+--------+------+-------+
| HA2 | 111 | | |
+-----------+--------+------+-------+
| WA2 | 101 | G | H |
+-----------+--------+------+-------+
| WA3 | 333 | I | J |
+-----------+--------+------+-------+
Current Scenario: The SELECT query in a view fetches PersonName from first table and work address fields from second table. (Join is on WorkAddressID)
Expected Result: The SELECT query should fetch PersonName field from first table and address fields from second table conditions being:
If state for home address is available then display Street, City and State for home address.
If state for home address is NULL/blank then display Street, City and State for work address.
Notes:
Many rows in Persons table do not have HomeAddressID but all do have WorkAddressID.
Many rows in Addresses table do not have City and State information for Home addresses.
While this may look like a design flaw, I'm not in a position to re-engineer the database as there are hundreds of objects and sub-objects depending on the original view.
There are 3 million+ rows in the Persons table so performance needs to be acceptable.
The current query has joins to at least 5 other views.
Please advise as to how I can address this problem.
Many thanks,
-V
Here's a MySQL solution:
SELECT PersonName,
IF(h.State = '' OR h.State IS NULL, w.Street, h.Street) AS Street,
IF(h.State = '' OR h.State IS NULL, w.City, h.City) AS City,
IF(h.State = '' OR h.State IS NULL, w.State, h.State) AS State
FROM Persons AS p
JOIN Addresses AS w ON w.AddressID = p.WorkAddressID
LEFT JOIN Addresses as h ON h.AddressID = p.HomeAddressID
A self join would handle this:
select
p.personname,
case when ha.state is null then wa.street else ha.street end as street,
case when ha.state is null then wa.city else ha.city end as city,
case when ha.state is null then wa.state else ha.state end as state
from
Persons p
inner join addresses wa on p.workaddressid = wa.addressid
left join addresses ha on p.homeaddressid = ha.addressid
This syntax would be for MSSQL
Edit: changed the home to a left join because of the criterion Many rows in Persons table do not have HomeAddressID
I hope I can explain this well enough.
Say I have this table:
Owner
+--------+--------+
| Name | Type |
+--------+--------+
| Bob | Cat |
| Bob | Dog |
| Bob | Cow |
| Tim | Dog |
| Tim | Cat |
| Ted | Cat |
| Joe | Dog |
| Joe | Cat |
| Joe | Sheep |
+--------+--------+
I am trying to find everyone who has all the animals tim has (so a cat and a dog). This means Joe and Bob would satisfy this, but not Ted as he only has one type of animal Tim has
How would I go about getting this result?
So I have a table with all the types tim owns:
SELECT Type FROM Owner WHERE Name= 'Tim';
How do I get it so that only those who have both Types tim has get selected from the list of owners?
Any guidance would be appreciated, thanks in advance.
select name
from owner
where type in (select distinct type from owner where name = 'tim')
group by name
having count(distinct type) = (select count(distinct type) from owner where name = 'tim')
I think of this as a join and group by problem. Join Tim's records to all the other owners, but on the type field. Then do an aggregation and keep only the records where all the types match:
select o.name
from owner otim left join
owner o
on o.type = tim.type and o.name <> 'Tim' and otim.name = 'Tim'
group by o.name
having min(case when o.type is null then 0 else 1 end) = 0;
Note that this works even when duplicate type values are allowed in the table.
If you are just trying to get all the persons who have more animals than Tim has then you can do it like
select Name from owners
group by Name
having count(distinct [Type]) > (select count(distinct type) from owners
where Name='tim')