SQL statement select columns with specific value - sql

I need some help making an sql statement; I don't really know how to aproach the situation. I have two tables, Departments and Employees
from which I want to select the Dpt_num and the Dpt_name of the departments that have at least one employee and that all their employees are from Barcelona
Case 1
== Departments =======
| Dpt_num | Dpt_name |
| 1 | A |
| 2 | B |
== Employees ===================
| E_num | Dpt_num | City |
| 1 | 1 | Barcelona |
| 2 | 1 | Barcelona |
The result in this case should be
Dpt_num Dpt_name
------------------
1 A
Case 2
== Departments =======
| Dpt_num | Dpt_name |
| 1 | A |
| 2 | B |
== Employees ==================
| E_num | Dpt_num | City |
| 1 | 1 | Barcelona |
| 2 | 1 | Madrid |
The result in this case should be empty.
I tried this for example but it seems very inefficient and it does not work in all the cases
select
num_dpt, nom_dpt
from
departements
where
1 = (select count(distinct e.ciutat_empl)
from empleats e
where e.num_dpt = num_dpt)
and not exists (select * from empleats e
where e.ciutat_empl != 'BARCELONA' and e.num_dpt = num_dpt);
I really appreciate any help. Thanks!

You want to go down the path of doing the filtering in the where clause. Then, use exists and not exists:
select d.num_dpt, d.nom_dpt
from departaments d
where exists (select 1
from empleats e
where e.num_dpt = d.num_dpt and e.ciutat_empl = 'BARCELONA'
) and
not exists (select 1
from empleats e
where e.num_dpt = d.num_dpt and e.ciutat_empl <> 'BARCELONA'
);
The first condition checks that at least one employee is from Barcelona. The second checks that no employees are from any other city.
One major problem in your version is your correlation clause:
e.num_dpt = num_dpt
You think this is doing:
e.num_dpt = departaments.num_dpt
But it is really doing:
e.num_dpt = e.num_dpt
Always qualify your column names. This is especially important when you have more than one table reference in the query.

Join the tables, group by the department and check if the count of employees in Barcelona is equal to the count of all employess of the department.
SELECT d.dpt_num,
d.dpt_name
FROM departments d
INNER JOIN employees e
ON e.dpt_num = d.dpt_num
GROUP BY d.dpt_num,
d.dpt_name
HAVING count(CASE
WHEN e.city = 'Barcelona' THEN
1
END) = count(*);

I believe this should work:
select d.dpt_num, d.dpt_name
from departments d
inner join employees e on
d.dpt_num = e.dpt_num
group by d.dpt_num, d.dpt_name
having count(*) = sum(case when e.city = 'Barcelona' then 1 else 0 end)
INNER JOIN makes sure there's at least 1 employee
HAVING count(*) = sum(case when e.city = 'Barcelona' then 1 else 0 end) makes sure that all employees are from Barcelona

demo: db<>fiddle
SELECT dpt_num, dpt_name
FROM (
SELECT d.dpt_num, d.dpt_name, array_agg(city) as cities
FROM dept d
JOIN empl e
ON d.dpt_num = e.dpt_num
GROUP BY d.dpt_num, d.dpt_name
) s
WHERE 'Barcelona' = ALL(cities)
Aggregate the cities and then you can filter with the ALL operator which checks if all array elements fit the condition.

Generally speaking, you compare COUNT(*) with COUNT(some condition) for such problems:
SELECT *
FROM Departments
WHERE EXISTS (
SELECT 1
FROM Employees
WHERE Employees.Dpt_num = Departments.Dpt_num
HAVING COUNT(*) > 0 -- it is possible to get a 0 if where did not match
AND COUNT(*) = COUNT(CASE WHEN Employees.City = 'Barcelona' THEN 1 END)
)
DB Fiddle

Pl try query below
select a.dpt_number,a.dpt_name from yy_department a
where exists (select 'x' from yy_employees y where y.dpt_number = a.dpt_number and y.city = 'Barcelona')
and not exists (select 'x' from yy_employees y where y.dpt_number = a.dpt_number and nvl(y.city,'x') <> nvl('Barcelona','y'))

Related

Marking duplicates records in a table

I am trying to mark duplicate records, however I get wrong reassignment on few on them and I don't know why.
Data:
=FirstName | LastName | Company | Group | Status | ID
x | x | x | NULL | NULL | 1
x | x | x | NULL | NULL | 2
Then I run this query to find matches on FirstName, LastName, Company
and join it back to the main table to mark the records:
with d as (
select ID, FirstName, LAstName, Company, row_number() over (partition by FirstName,LastName, Company order by FirstName,LastName, Company) as nr
from [dbo].xx)
Update b
set Status = 'S'
, Group = d.DQ_ID
from xx as b inner join d on
b.FirstName = d.FirstName and
b.LastNAme = d.LastName and
b.Company = d.Company
where d.nr = 1
And then Update the Main Record with P
Update b
set Status = 'P'
from xx as b
where b.ID = b.Group
GO
What I expect:
=FirstName | LastName | Company | Group | Status | ID
x | x | x | 1 | P | 1
x | x | x | 1 | S | 2
What I get:
=FirstName | LastName | Company | Group | Status | ID
x | x | x | 2 | S | 1
x | x | x | 1 | S | 2
I am working on about 1M records - and it only happen to some of them!
Try this :
;with d as (
select
ID,
FirstName,
LAstName,
Company,
row_number() over (
partition by FirstName,LastName, Company
order by Id asc -- this was done to keep ordering as per ID
) as nr
from [dbo].xx
) ,
e as
(select * from d where nr=1)
-- e was created to only take the nr=1 rows which will be joined to all similar records
Update b
set Status = case when e.DQ_ID = b.DQ_ID then 'P' else 'S' end
-- the set case logic ensures that matching ids get P else S
, Group = e.DQ_ID
from xx as b
inner join e on
b.FirstName = e.FirstName and
b.LastNAme = e.LastName and
b.Company = e.Company
Can try with the following:
;WITH RankedData AS
(
SELECT
T.ID,
T.[Group],
T.Status,
T.FirstName,
T.LastName,
T.Company,
GroupRanking = ROW_NUMBER() OVER (PARTITION BY T.FirstName, T.LastName, T.Company ORDER BY T.ID ASC)
FROM
dbo.xx AS T
)
UPDATE T SET
[Group] = N.ID,
Status = CASE WHEN T.GroupRanking = 1 THEN 'P' ELSE 'S' END
FROM
RankedData AS T
INNER JOIN RankedData AS N ON
T.FirstName = N.FirstName AND
T.LastName = N.LastName AND
T.Company = N.Company AND
N.GroupRanking = 1
Keep in mind that the INNER JOIN will join on not null names and companies, will have to keep in mind if you have nulls on those columns.

In SQL Query a one-to-many relationship with condition

I have the following tables:
event_tbl
| event_id (PK) | event_date | event_location |
|---------------|------------|----------------|
| 1 | 01/01/2018 | Miami |
| 2 | 02/04/2018 | Tampa |
performer_tbl
| performer_id (PK) | event_id (FK) | genre |
|-------------------|---------------|-------|
| 1 | 1 | A |
| 2 | 1 | B |
| 3 | 2 | A |
| 4 | 2 | C |
I want to find events that have both genre A and genre B (should just return event 1), and I'm lost on writing the query. Maybe I just haven't had enough coffee, but all I can come up with is doing two derived columns with a case statement that count either genre and group by the event_id, then filtering both to >0. It just doesn't seem very elegant.
This should do the job (in MySQL, for other DBMS the syntax can be varied easily):
SELECT
e.event_id
FROM
event_tbl e
JOIN performer_tbl p USING(event_id)
GROUP BY e.event_id
HAVING SUM(IF(p.genre = 'A', 1, 0)) >= 1 AND SUM(IF(p.genre = 'B', 1, 0)) >= 1;
if you are using sql server, check below:
Select * From
event_tbl
where event_id
IN
(
select event_id
from performer_tbl as A
where exists (select 1
from perfoermer_tbl as B
where B.event_id = A.event_id and B.genre = 'A')
and
exists (select 1
from perfoermer_tbl as B
where B.event_id = A.event_id and B.genre = 'B')
)
This should work in any SQL database (at least in mysql, sql server, postgres or oracle)
select event_tbl.* FROM (
select event_id
from performer_tbl
where genre = 'A'
GROUP BY event_id) a_t
INNER JOIN (select event_id
from performer_tbl
where genre = 'B'
GROUP BY event_id) b_t
ON a_t.event_id = b_t.event_id
INNER JOIN event_tbl
ON event_tbl.event_id = a_t.event_id
This also works using left joins: (Since there are no function calls or sub-selects, it is fast. Also, it's usable in most SQL engines.)
SELECT DISTINCT
p1.event_id
,e.event_date
,e.event_location
FROM
performer_tbl as p1
inner join event_tbl as e on
p1.event_id = e.event_id
left outer join performer_tbl as p2 on
p1.event_id = p2.event_id
AND p2.genre = 'A'
left outer join performer_tbl as p3 on
p1.event_id = p3.event_id
AND p3.genre = 'B'
WHERE
p2.genre IS NOT NULL
AND p3.genre IS NOT NULL;
If I correctly understand what you need, you can try this:
Select *
from event_tbl e
where exists (select *
from performer_tbl p
where p.event_id = e.event_id
and p.genre in ('A', 'B'))

How to combine group by, where and logical operators?

I have the following data structure
| Name | Contract Type |
|:------|--------------:|
| Frank | Student |
| Frank | Staff |
| Frank | Staff |
| John | Internship |
| John | Student |
| John | Staff |
| Tim | Internship |
| Tim | Staff |
I want to get the Names of employees who have had a student contract and another contract in theire history. My approach was
Select Name, count(Name) from table
where ContractType = ('Student') AND (ContractType = ('Staff') OR ContractType = ('Internship') )
group by Name having count (Name) > 1
The problem here is that I still get all employees. Anyone who can help me get this accomplished?
Group by the Name and take only those groups having at least once the Student contract and in total more than 1 contract
Select Name
from your_table
group by Name
having sum(case when ContractType = 'Student' then 1 else 0 end) > 0
and count(distinct ContractType) > 1
Using GROUP BY:
SELECT Name
FROM dbo.YourTable
GROUP BY Name
HAVING SUM(CASE WHEN [Contract Type] = 'Student' THEN 1 ELSE 0 END) >= 1
AND SUM(CASE WHEN [Contract Type] <> 'Student' THEN 1 ELSE 0 END) >= 1;
But you can also use EXISTS:
SELECT DISTINCT A.Name
FROM dbo.YourTable A
WHERE EXISTS(SELECT 1 FROM dbo.YourTable
WHERE Name = A.Name
AND [Contract Type] = 'Student')
AND EXISTS( SELECT 1 FROM dbo.YourTable
WHERE Name = A.Name
AND [Contract Type] <> 'Student');
You can also use a GROUP BY and then IN:
SELECT name
FROM table
GROUP BY name
HAVING COUNT(*) > 1
AND name IN
(SELECT name
FROM table
WHERE contracttype = 'student')
Tested here: http://sqlfiddle.com/#!9/38fa36/1

SQL SELECT multiple keys/values

I've got a table PERSON_PROPERTIES that resembles the following :
| ID | KEY | VALUE | PERSON_ID |
| 1 | fname | robert | 1 |
| 2 | lname | redford | 1 |
| 3 | fname | robert | 2 |
| 4 | lname | de niro | 2 |
| 5 | fname | shawn | 3 |
| 6 | nname | redford | 3 |
I would like to SELECT (in JPQL or in PSQL) the PERSON_ID that matches the given fname and lname.
I've tried
`SELECT DISTINCT *
FROM PERSON_PROPERTIES t0
WHERE ((((t0.key = 'fname')
AND (t0.value = 'robert'))
AND ((t0.key = 'lname')
AND (t0.value = 'redford'))))`
but it returns me no value.
I've also tried
`SELECT DISTINCT *
FROM PERSON_PROPERTIES t0
WHERE ((((t0.key = 'fname')
AND (t0.value = 'robert'))
OR ((t0.key = 'lname')
AND (t0.value = 'redford'))))`
but this way it returns me all values. I don't know how to turn the query properly for it to give me only value 1.
SELECT PERSON_ID
FROM PERSON_PROPERTIES
group by PERSON_ID
having sum(case when key = 'fname' and value = 'robert' then 1 else 0 end) > 0
and sum(case when key = 'lname' and value = 'redford' then 1 else 0 end) > 0
Groupy by the person and select only those having both values.
Another approach would be with subselect (caution, it's MS SQL 2012)
SELECT PERSON_ID
FROM PERSON_PROPERTIES
WHERE [Key] = 'fname' AND value = 'robert'
AND PERSON_ID in
(SELECT PERSON_ID FROM PERSON_PROPERTIES WHERE [Key] = 'lname' AND value = 'redford')
Fiddle Demo
Along with some colleagues we came to this answer :
SELECT p.PERSON_ID
FROM PERSON_PROPERTIES p
WHERE (p.key = 'fname' AND p.value = 'robert')
OR (p.key = 'lname' AND p.value = 'redford')
GROUP BY p.PERSON_ID
HAVING count(*) = 2
What do you think about it?
SELF JOIN also does the trick. DISTINCT for duplicate person_id:
SELECT DISTINCT a.PERSON_ID
FROM PERSON_PROPERTIES a JOIN PERSON_PROPERTIES b ON a.PERSON_ID = b.PERSON_ID
WHERE a.the_key = 'fname' AND a.value = 'robert'
AND b.the_key = 'lname' AND b.value = 'redford';
Demo
OK I will be marking this as the correct answer. The only thing I did was modified it a bit
SELECT Y.*, M.* FROM wp_postmeta as Y JOIN wp_postmeta AS M USING (`post_id`)
WHERE (Y.meta_key = 'agam_post_options_year' AND Y.meta_value = 2013)
AND (M.meta_key = 'agam_post_options_month' AND M.meta_value BETWEEN 0 AND 12 )
GROUP BY Y.meta_value, M.meta_value ORDER BY M.meta_value+0 DESC
So I get that DESC order.. however.. I noticed that it does not duplicates results... I had two posts with the same year and same month... now I don't see it... is there anything there that's preventing this ?

Select from other table if value exist

I created a fiddle for this, at this link:
http://www.sqlfiddle.com/#!2/7e007
I could'nt find SQL compact / CE so it's in MySQL.
The tables looks like this
Records Clients
ID | NAME | AGE ID | NAME
------------------ ----------------
1 | John | 20 1 | John
2 | Steven | 30 2 | Daniel
3 | Abraham | 30 3 |
4 | Donald | 25 5 | Lisa
6 | | 35 6 | Michael
7 | | 42 7 |
I would like to select from both tables, and if the id is in both tables and both have names I would like the the name from "Clients" as the default. If the name in Records is blank, use the client name (if any) and if the Clients.Name is blank; use the records.Name.
From tables above, i would like this:
ID | NAME | AGE
------------------
1 | John | 20
2 | Daniel | 30
3 | Abraham | 30
4 | Donald | 25
5 | Lisa |
6 | Michael | 35
7 | | 42
How do i do this in SQL Compact?
EDIT:
Thanks to great answers below i've managed to come up with this query, which ALMOST works:
SELECT t.id, t.name, t.age FROM
(
(
SELECT r.id,
CASE WHEN r.name = NULL THEN c.name ELSE r.name END as name,
r.age
FROM Records r
LEFT JOIN Clients c ON c.id = r.id
)
UNION
(
SELECT c.id, c.name, null as age FROM Clients c where c.id NOT IN (select id from Records)
)
) as t ORDER BY t.id
This gives me this output:
ID | NAME | AGE
------------------
1 | John | 20
2 | Daniel | 30
3 | Abraham | 30
4 | Donald | 25
5 | Lisa |
6 | | 35
7 | | 42
"Michael" (should be on #6) is missing in this case. Why?!
select r.id,
IF(c.name != '',c.name,r.name) as name,
r.age
FROM Records r
LEFT JOIN Clients c ON c.id = r.id
GROUP BY c.id
Use above query.
EDITED:
SELECT t.id, t.name, t.age FROM
(
(
SELECT r.id,
CASE WHEN c.name <> '' THEN c.name ELSE r.name END as name,
r.age
FROM Records r
LEFT JOIN Clients c ON c.id = r.id
)
UNION
(
SELECT c.id, c.name, null as age FROM Clients c where c.id NOT IN (select id from Records)
)
) as t ORDER BY t.id
Use this query.
please try,hope this will work..
select c.id,
IF(NAME='',(select name from Records where id = c.id),'')
If(NAME=NULL,(select name from Records where id = c.id),NULL)
Else c.NAME
from client c;
cheers!!!
select case when a.id <> '' then a.id else b.id end as id ,
case when a.name <> '' then a.name else b.name end as name,a.age
from records a
full outer join clients b on a.Id = b.id
order by a.id
Use COALECSE to get the first not-null value:
select id, coalesce(clients.name, records.name) as correct_name, records.age
from records
join clients using (id);
EDIT: In case not existing names are stored as '' instead of NULL use:
select id, case when clients.name = '' then records.name else clients.name end as correct_name, records.age
from records
join clients using (id);
Of course you can also react on both '' and NULL by asking
when clients.name = '' or clients.name is null then
See http://www.sqlfiddle.com/#!2/7e007/36.