Count how many times a value appears in tables SQL - sql

Here's the situation:
So, in my database, a person is "responsible" for job X and "linked" to job Y. What I want is a query that returns: name of person, his ID and he number of jobs it's linked/responsible. So far I got this:
select id_job, count(id_job) number_jobs
from
(
select responsible.id
from responsible
union all
select linked.id
from linked
GROUP BY id
) id_job
GROUP BY id_job
And it returns a table with id in the first column and number of occurrences in the second. Now, what I can't do is associate the name of person to the table. When i put that in the "select" from beginning it gives me all the possible combinations... How can I solve this? Thanks in advance!
Example data and desirable output:
| Person |
id | name
1 | John
2 | Francis
3 | Chuck
4 | Anthony
| Responsible |
process_no | id
100 | 2
200 | 2
300 | 1
400 | 4
| Linked |
process_no | id
101 | 4
201 | 1
301 | 1
401 | 2
OUTPUT:
| OUTPUT |
id | name | number_jobs
1 | John | 3
2 | Francis | 3
3 | Chuck | 0
4 | Anthony | 2

Try this way
select prs.id, prs.name, count(*) from Person prs
join(select process_no, id
from Responsible res
Union all
select process_no, id
from Linked lin ) a on a.id=prs.id
group by prs.id, prs.name

I would recommend aggregating each of the tables by the person and then joining the results back to the person table:
select p.*, coalesce(r.cnt, 0) + coalesce(l.cnt, 0) as numjobs
from person p left join
(select id, count(*) as cnt
from responsible
group by id
) r
on r.id = p.id left join
(select id, count(*) as cnt
from linked
group by id
) l
on l.id = p.id;

select id, name, count(process_no) FROM (
select pr.id, pr.name, res.process_no from Person pr
LEFT JOIN Responsible res on pr.id = res.id
UNION
select pr.id, pr.name, lin.process_no from Person pr
LEFT JOIN Linked lin on pr.id = lin.id) src
group by id, name
order by id
Query ain't tested, give it a shot, but this is the way you want to go

Related

Need help for MS Access Select Request using 2 tables

For a "products reservation system", I have 2 tables :
"RD", for global reservations data (fieds: ID, CustomerID, Date, ...)
"RP", for reserved products data per reservation (fields: ID, RD_ID, ProductID, Status, ...). RD_ID fits with the ID in RD table (field for joining). Status field can have these values: O, C, S.
I need to extract (with 2 Select instructions) the list of reservations and the number of reservations for which all products have status 'O' .
Data example for RP:
ID | RD_ID | ProdID | Status
----------------------------
1 | 1 | 100 | O
2 | 1 | 101 | O
3 | 1 | 102 | O
4 | 2 | 105 | O
5 | 2 | 100 | S
6 | 3 | 101 | C
7 | 3 | 102 | O
In this example, Select statement should return only RD_ID 1
For the number of ID, the following request does not work because it also includes reservations with products having different status:
SELECT COUNT(rd.ID) FROM rd INNER JOIN rp ON rp.RD_ID = rd.ID WHERE rp.Status = 'O';
Could you help me for the right Select statement?
Thank you.
SELECT rd.ID, COUNT(rd.ID) CountOfRD, status
FROM rd INNER JOIN rp ON rp.RD_ID
GROUP BY rd.ID, status
Use not exists as follows:
Select t.* from your_table t
Where t.status = 'O'
And not exists (select 1 from your_table tt
Where t.rd_id = tt.rd_id
And t.status != tt.status)
You can also use group by and having as follows:
Select rd_id
From your_table t
Group by rd_id
Having sum(case when status <> 'O' then 1 end) > 0

A better way to aggregate into a default value

For this example I have three tables (individual, business, and ind_to_business). Individual has information on people. Business has information on businesses. And ind_to_business has information on which people are linked to which business. Here are their DDL:
CREATE TABLE individual
(
ID INTEGER PRIMARY KEY,
NAME VARCHAR2(100) NOT NULL,
ENTERPRISE_ID VARCHAR2(25) NOT NULL UNIQUE
);
CREATE TABLE business
(
ID INTEGER PRIMARY KEY,
NAME VARCHAR2(100) NOT NULL,
ENTERPRISE_ID VARCHAR2(25) NOT NULL UNIQUE
);
CREATE TABLE ind_to_business
(
ID INTEGER PRIMARY KEY,
IND_ID REFERENCES individual(id),
BUS_ID REFERENCES business(id),
START_DT DATE NOT NULL,
END_DT DATE
);
I'm looking for the best way to display one row for each person. If they are linked to one business, I want to display the the business's ENTERPRISE_ID. If they are linked to more than one business, I want to display the default value 'Multiple'. They will always be linked to a business, so there is no LEFT JOIN necessary. They can also be linked to a business more than once (Leaving and coming back). Multiple records for the same business would be aggregated.
So for the following sample data:
Individual:
+----+------------+---------------+
| ID | NAME | ENTERPRISE_ID |
+----+------------+---------------+
| 1 | John Smith | 53a23B7 |
| 2 | Jane Doe | 63f2a35 |
+----+------------+---------------+
Business:
+----+----------+---------------+
| ID | NAME | ENTERPRISE_ID |
+----+----------+---------------+
| 3 | ABC Corp | 2a34d9b |
| 4 | XYZ Inc | 34bf21e |
+----+----------+---------------+
ind_to_business
+----+--------+--------+-------------+-------------+
| ID | IND_ID | BUS_ID | START_DT | END_DT |
+----+--------+--------+-------------+-------------+
| 5 | 1 | 3 | 01-JAN-2000 | 31-DEC-2002 |
| 6 | 1 | 3 | 01-JAN-2015 | |
| 7 | 2 | 3 | 01-JAN-2000 | |
| 8 | 2 | 4 | 01-MAR-2006 | 05-JUN-2010 |
| 9 | 2 | 4 | 15-DEC-2019 | |
+----+--------+--------+-------------+-------------+
I would expect the following output:
+---------+------------+------------+
| IND_ID | NAME | LINKED_BUS |
+---------+------------+------------+
| 53a23B7 | John Smith | 2a34d9b |
| 63f2a35 | Jane Doe | Multiple |
+---------+------------+------------+
Here is my current query:
SELECT DISTINCT
sub.ind_id,
sub.name,
DECODE(sub.bus_count, 1, sub.bus_id, 'Multiple') AS LINKED_BUS
FROM (SELECT i.enterprise_id AS IND_ID,
i.name,
b.enterprise_id AS BUS_ID,
COUNT(DISTINCT b.enterprise_id) OVER (PARTITION BY i.id) AS BUS_COUNT
FROM individual i
INNER JOIN ind_to_business i2b ON i.id = i2b.ind_id
INNER JOIN business b ON i2b.bus_id = b.id) sub;
My query works, but this is running on a large dataset and taking a long time to run. I'm wondering if anyone has any ideas on how improve this so that there isn't so much wasted processing (i.e Needing to do a DISTINCT on the final result or doing COUNT(DISTINCT) in the inline view only to use that value in the DECODE above).
I've also created a DBFiddle for this question. (Link)
Thanks in advance for any input.
You could try and use a correlated subquery. This removes the need for outer distinct:
SELECT
i.enterprise_id ind_id,
i.name,
(
SELECT DECODE(COUNT(DISTINCT b.enterprise_id), 1, MIN(bus_id), 'Multiple')
FROM ind_to_business i2b
INNER JOIN business b ON i2b.bus_id = b.id
WHERE i2b.ind_id = i.id
) linked_bus
FROM individual i
You can join with the aggregated ind_to_business per individual. One way to do this:
select i.id, i.name, coalesce(b.enterprise_id, 'Multiple')
from individual i
join
(
select
ind_id,
case when min(bus_id) = max(bus_id) then min(bus_id) else null end as bus_id
from ind_to_business
group by ind_id
) ib on ib.ind_id = i.id
left join business b on b.id = ib.bus_id
order by i.id;
First you should sub-query to get all needed dimensions and then do all your final aggregation using CASE statement.
select
ind_id,
name,
case
when count(*) > 1 then 'Multiple'
else ind_id
end as linked_bus
from
(
select
distinct i.enterprise_id as ind_id,
i.name,
b.enterprise_id as bus_id
from individual i
join ind_to_business i2b
on i.id = i2b.ind_id
join business b
on i2b.bus_id = b.id
) vals
group by
ind_id,
name
order by
ind_id
No need of using DISTINCT twice. You could use subquery factoring and put the in-line view in WITH clause, and make the data set DISTINCT in the subquery itself.
WITH data AS
(
SELECT distinct
i.enterprise_id AS IND_ID,
i.name,
b.enterprise_id AS BUS_ID
FROM individual i
JOIN ind_to_business i2b ON i.id = i2b.ind_id
JOIN business b ON i2b.bus_id = b.id
)
SELECT ind_id,
name,
case
when count(*) = 1 then MIN(bus_id)
else 'Multiple'
end AS LINKED_BUS
FROM data
GROUP BY ind_id, name;
IND_ID NAME LINKED_BUS
---------- ---------- -------------------------
53a23B7 John Smith 2a34d9b
63f2a35 Jane Doe Multiple

T-SQL Select Join 3 Tables

I'm currently working on a select query in T-SQL on SQL Server 2012. It's a complex query, I want to query a list from 3 tables. The result should look something like this:
Desired Output:
ProjectId | Title | Manager | Contact | StatusId
----------+-------------+-----------+-----------+-----------
1 | projectX | 1123 | 4453 | 1
2 | projectY | 2245 | 5567 | 1
3 | projectZ | 3335 | 8899 | 1
My 3 Tables:
1) Project: ProjectId, ProjectDataId, MemberVersionId
2) ProjectData: ProjectDataId, Title, StatusId
3) Members: MemberId, MemberVersionId, MemberTypeId, EmployeeId
The tricky part is, to implement versioning. Thus, over time the project Members can change, and it should always be possible to return to a previous version, that's why I use MemberVersionId as a foreign key inbetween Project and Members. The tables Project and ProjectData a linked with ProjectDataId.
Hence, 1 Project has 1 OfferData and 1 Project has N Members.
Some sample data:
Project
ProjectId | ProjectDataId | MemberVersionId |
----------+---------------+-----------------+
1 | 2 | 1 |
2 | 3 | 1 |
3 | 4 | 1 |
ProjectData
ProjectDataId | Title | StatusId
--------------+-------------+-----------
2 | projectX | 1
3 | projectY | 1
4 | projectZ | 1
Members: MemberTypeId 1 = Manager, MemberTypeId 2 = Contact, 3 = Other
MemberId | MemberVersionId | MemberTypeId | EmployeeId |
---------+-----------------+--------------+------------+
1 | 1 | 1 | 1123 |
2 | 1 | 2 | 4453 |
3 | 1 | 3 | 9999 |
4 | 2 | 1 | 2245 |
5 | 2 | 2 | 5567 |
6 | 2 | 3 | 9999 |
7 | 3 | 1 | 3335 |
8 | 3 | 2 | 8899 |
9 | 3 | 3 | 9999 |
My current query looks like this:
SELECT ProjectId, Title, EmployeeId AS Manager, EmployeeId AS Contact, StatusId
FROM [MySchema].[Project] a,
[MySchema].[ProjectData] b,
[MySchema].[Members] c
WHERE a.ProjectDataId = b.ProjectDataId
AND a.MemberVersionId = c.MemberVersionId
Unfortunately this doesn't work yet. Do you know how to solve this issue?
Thanks
Something like this?
SELECT
p.ProjectId,
pd.Title,
mm.EmployeeId AS Manager,
mc.EmployeeId AS Contact,
pd.StatusId
FROM
[MySchema].[Project] p
INNER JOIN [MySchema].[ProjectData] pd ON pd.ProjectDataId = p.ProjectDataId
INNER JOIN [MySchema].[Members] mm ON mm.MemberVersionId = p.MemberVersionId AND mm.MemberTypeId = 1
INNER JOIN [MySchema].[Members] mc ON mc.MemberVersionId = p.MemberVersionId AND mc.MemberTypeId = 2;
You can try this:
SELECT ProjectId, Title, C.EmployeeId AS Manager, d.EmployeeId AS Contact, StatusId
FROM [MySchema].[Project] a
INNER JOIN [MySchema].[ProjectData] b ON A.ProjectDataId=B.ProjectDataId
LEFT JOIN (SELECT * FROM [MySchema].[Members] WHERE MemberTypeID=1) c ON a.MemberVersionId=c.MemberVersionId
LEFT JOIN (SELECT * FROM [MySchema].[Members] WHERE MemberTypeID=2) d ON a.MemberVersionId=d.MemberVersionId
You must select members two times, one for the manager and another for contact:
SELECT ProjectId, Title, m.EmployeeId AS Manager, c.EmployeeId AS
Contact, StatusId
FROM [MySchema].[Project] a,
[MySchema].[ProjectData] b,
[MySchema].[Members] m
[MySchema].[Members] c
WHERE a.ProjectDataId = b.ProjectDataId
AND a.MemberVersionId = m.MemberVersionId and m.MemberTypeId = 1
AND a.MemberVersionId = c.MemberVersionId and c.MemberTypeId = 2
try this,
SELECT ProjectId, Title, cmanager.EmployeeId AS Manager, ccon.EmployeeId AS
Contact, StatusId
from [MySchema].[ProjectData] b
inner join [MySchema].[Project] a on b.ProjectDataId=a.ProjectDataId
left join [MySchema].[Members] cmanager on cmanager.MemberVersionId =
a.MemberVersionId and cmanager.MemberTypeId=1
left join [MySchema].[Members] ccon on ccon.MemberVersionId =
a.MemberVersionId and ccon.MemberTypeId=2
The simplest solution to your problem would be introducing additional field to Project table. You'd either call it LatestMemberVersion (int, holds the currently highest MemberVersionId), which would by the most up to date version of the relationship, your you can add even simpler IsLatestMemberVersion (bit, holds 1 if the record is the latest/active). You can compute both of them using ROW_NUMBER() OVER statement.
Then, the query would change to:
SELECT ProjectId, Title, EmployeeId AS Manager, EmployeeId AS Contact, StatusId
FROM [MySchema].[Project] a,
[MySchema].[ProjectData] b ON a.ProjectDataId = b.ProjectDataId
[MySchema].[Members] c ON a.MemberVersionId = c.MemberVersionId
WHERE
a.[IsLatestMemberVersion] = 1 -- alternative is a.[LatestMemberVersion] = a.[MemberVersionId]
Additionally, there are two more things you can try:
you might want to borrow ideas from data warehousing, namely you will want to have combination of Slowly Changing Dimension Type 1 and 2
you can try to use SQL Server features, such as Change Data Tracking. But I have no experience with that, so it's possible it'll lead to nowhere.
And one last piece of advice, if you can, never write join conditions into the WHERE clause. It is not readable and can lead to problems when you suddenly change JOIN to LEFT JOIN. Microsoft itself recommends using ON instead of WHERE when applicable.

SQL server matching two table on a column

I have two tables one storing user skills another storing skills required for a job. I want to match how many skills a of each user matches with a job.
The table structure is
Table1: User_Skills
| ID | User_ID | Skill |
---------------------------
| 1 | 1 | .Net |
---------------------------
| 2 | 1 | Software|
---------------------------
| 3 | 1 | Engineer|
---------------------------
| 4 | 2 | .Net |
---------------------------
| 5 | 2 | Software|
---------------------------
Table2: Job_Skills_Requirement
| ID | Job_ID | Skill |
--------------------------
| 1 | 1 | .Net |
---------------------------
| 2 | 1 | Engineer|
---------------------------
| 3 | 1 | HTML |
---------------------------
| 4 | 2 | Software|
---------------------------
| 5 | 2 | HTML |
---------------------------
I was trying to have comma separated skills and compare but these can be in different order.
Edit
All the answers here are excellent. The result I am looking for is matching all jobs with all users as later on I will match other properties as well.
You could join the tables by the skill columns and count the matches:
SELECT user_id, job_id, COUNT(*) AS matching_skills
FROM user_skills u
JOIN job_skills_requirement j ON u.skill = j.skill
GROUP BY user_id, job_id
EDIT:
IF you want to also show users and jobs that have no matching skills, you can use a full outer join instead.
SELECT user_id, job_id, COUNT(*) AS matching_skills
FROM user_skills u
FULL OUTER JOIN job_skills_requirement j ON u.skill = j.skill
GROUP BY user_id, job_id
EDIT 2:
As Jiri Tousek commented, the above query will produce nulls where there's no match between a user and a job. If you want a full Cartesian products between them, you could use (abuse?) the cross join syntax and count how many skills actually match between each user and each job:
SELECT user_id,
job_id,
COUNT(CASE WHEN u.skill = j.skill THEN 1 END) AS matching_skills
FROM user_skills u
CROSS JOIN job_skills_requirement j
GROUP BY user_id, job_id
If you want to match all users and all jobs, then Mureinik's otherwise excellent answer is not correct.
You need to generate all the rows first, which I would do using a cross join and then count the matching ones:
select u.user_id, j.job_id, count(jsr.job_id) as skills_in_common
from users u cross join
jobs j left join
user_skills us
on us.user_id = u.user_id left join
Job_Skills_Requirement jsr
on jsr.job_id = j.job_id and
jsr.skill = us.skill
group by u.user_id, j.job_id;
Note: This assumes the existence of a users and a jobs table. You can of course generate these using subqueries.
WITH User_Skills(ID,User_ID,Skill)AS(
SELECT 1,1,'.Net' UNION ALL
SELECT 2,1,'Software' UNION ALL
SELECT 3,1,'Engineer' UNION ALL
SELECT 4,2,'.Net' UNION ALL
SELECT 5,2 ,'Software'
),Job_Skills_Requirement(ID,Job_ID,Skill)AS(
SELECT 1,1,'.Net' UNION ALL
SELECT 2,1,'Engineer' UNION ALL
SELECT 3,1,'HTML' UNION ALL
SELECT 4,2,'Software' UNION ALL
SELECT 5,2 ,'HTML'
),Job_User_Skill AS (
SELECT j.Job_ID,u.User_ID,u.Skill
FROM Job_Skills_Requirement AS j INNER JOIN User_Skills AS u ON u.Skill=j.Skill
)
SELECT jus.Job_ID,jus.User_ID,COUNT(jus.Skill),STUFF(c.Skills,1,1,'') AS Skill
FROM Job_User_Skill AS jus
CROSS APPLY(SELECT ','+j.Skill FROM Job_User_Skill AS j WHERE j.Job_ID=jus.Job_ID AND j.User_ID=jus.User_ID FOR XML PATH('')) c(Skills)
GROUP BY jus.Job_ID,jus.User_ID,c.Skills
ORDER BY jus.Job_ID
Job_ID User_ID Skill
----------- ----------- ----------- -------------
1 1 2 .Net,Engineer
1 2 1 .Net
2 1 1 Software
2 2 1 Software

INNER JOIN and COUNT in the same query

I am having trouble with putting together INNER JOIN and COUNT in the same query.
Tables are:
TABLE STREETS
ID | STREET_NAME
------------------------
1 | Elm street
2 | Some other street
3 | Unknown street
4 | Killer street
5 | Dead-end street
TABLE ACCIDENTS_STREETS
STREET_ID | ACCIDENT_ID
-----------------------
2 | 4
2 | 7
2 | 2
2 | 1
5 | 3
I would like to get the street name where most accidents have occured.
This is for COUNT:
SELECT TOP 1 COUNT(STREET_ID) AS dangerous_street FROM ACCIDENTS_STREETS GROUP BY STREET_ID ORDER BY dangerous_street DESC
How to add INNER JOIN there to get only the name of the street?
Any advice is appreciated!
The Following should work
SELECT TOP 1 S.STREET_NAME,COUNT(a.*) AS dangerous_street
FROM ACCIDENTS_STREETS A
inner Join STREET S on S.ID = A.STREET_ID
GROUP BY S.STREET_NAME ORDER BY dangerous_street DESC
try this...
After joining the streets table, you would have to use an aggregation function to get the name in the resultset
SELECT
TOP 1 COUNT(STREET_ID) AS dangerous_street
, min( STREET_NAME) dangerous_STREET_NAME
FROM ACCIDENTS_STREETS acc
inner join STREETS str
on acc.STREET_ID=str.id
GROUP BY STREET_ID
ORDER BY dangerous_street DESC