T-SQL - calculate a boolean field on the fly - sql-server-2008

I'm using SQL Server 2008.
Let's say I have two hypothetical tables like below:
CREATE TABLE [Department](
[Id] int IDENTITY(1,1),
[ManagerId] int NULL, -- << Foreign key to the Person table
-- other fields
)
CREATE TABLE [Person](
[Id] int IDENTITY(1,1),
[DepartmentId] int NOT NULL, -- << Foreign key to the Department table
-- other fields
)
Now, I want to return a list of rows from the [Person] table (i.e. list of staff for a given department). Only one (or zero) of these rows will match the [ManagerId] field in the [Department] table. And I want to flag the matched row with a boolean field on the fly... the resultant rowset will resemble the following schema:
[Id] INT,
[IsManager] BIT NOT NULL DEFAULT 0,
-- other fields
The [IsManager] field will be TRUE when [Department].[ManagerId] matches [Person].[Id].
This is fairly trivial to do with two (or more) queries. But how can I achieve this using a single SQL statement?

Add an expression to your SELECT clause where you compare actual persons Id with ManagerId from persons department
SELECT
Person.Id,
Department.Id,
CAST(CASE WHEN Person.Id=Department.ManagerId THEN 1 ELSE 0 END AS BIT) AS IsManager
FROM Person
INNER JOIN Department ON Person.DepartmentId=Department.Id
WHERE Person.DepartmentId=<CONDITION>

A left join from the Person table to the department table on ManagerId will do the trick for you:
SELECT p.Id AS PersonId, d.Id AS DepartmentId,
CAST(CASE WHEN d.Id IS NULL THEN 0 ELSE 1 END) AS IsManager
FROM Person p LEFT JOIN Department d ON p.Id = d.ManagerId
How it works: All rows from Person are return, regardless of the existence of a corresponding Department matching on ManagerId. For those Person records without a matching department, all of the Department fields in the resultset are NULL, so we can use that to determine whether or not there is a match.
Note that this query may return duplicate Person records, if a person is a manager for multiple departments. To this end, I have added the DepartmentId to the list. If you require a unique list of persons and their IsManager flag, drop d.DepartmentId from the select clause and insert DISTINCT after the select:
SELECT DISTINCT p.Id AS PersonId,
CAST(CASE WHEN d.DepartmentId IS NULL THEN 0 ELSE 1 END) AS IsManager
FROM Person p LEFT JOIN Department d ON p.Id = d.ManagerId

Related

How to create a query with JOIN and WHERE or how to make them friends?

I need to make a query, where there are columns of client's names and their orders per month.
Some clients don't have orders at some months and there fields must have 0.
The problem is, when i use WHERE and OUTER JOIN (no matter which one) at one query*, nessesary zero`s cutteed by WHERE. So how can i solve that?
Descripton of tables are pinned.
SELECT name
, ordering.id_client
, COUNT(order_date)
FROM ordering
RIGHT
OUTER
JOIN client
ON client.id_client = ordering.id_client
WHERE month(order_date) = 1
GROUP
BY name;
**Descripton**: (https://i.imgur.com/TrUGOLW.png)
**Example of my query** (there are 6 notes about clients at my db, showed only 4 of 6):
(https://i.imgur.com/ABP6pP0.png)
**MRE stuff**
Client: create table client(id_client int primary key auto_increment, name var char(50), passport_code int, addr varchar(70));
insert into client values(null, 'Penny Anderson', 6485, 'New Orlean');
Ordering: create table ordering(id_order int primary key auto_increment, id_client int, order_date date, foreign key(id_client) references client(id_client));
insert into ordering values(null, 1, date('2020-05-01'));
Try a simple left join starting from client's table
SELECT client.name
, client.id_client
, COUNT(order_date)
FROM client
LEFT JOIN ordering ON client.id_client = ordering.id_client
AND month(ordering.order_date) = 1
GROUP BY client.id_client;
If the condition for join is related to the left joined table then add this condition in the related ON clause and not in where otherwise this work as an inner join

How do I write an SQL query that gives me the amount of times a value is repeated in a table?

So I have two tables which I have created:
CREATE TABLE IF NOT EXISTS `Advertising_Campaign` (
`CampaignID` VARCHAR(10) NOT NULL,
`AdvertName` varchar(45) NOT NULL,
`ProjectLead` VARCHAR(10) NULL,
`CostEstimate` decimal NULL,
`CampaignCost` decimal NULL,
`EndDateEst` date NULL,
`StartDate` date NULL,
`EndDate` date NULL,
`Theme` VARCHAR(45) NOT NULL,
`AdvertType` VARCHAR(45) NOT NULL,
PRIMARY KEY (`CampaignID`))
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS `staff_works_campaign` (
`CampaignID` VARCHAR(10) NOT NULL,
`StaffID` VARCHAR(10) NOT NULL,
`SalaryGrade` Integer NOT NULL,
`isSup` VARCHAR(3) NOT NULL,
PRIMARY KEY (`StaffID`, `CampaignID`),
CONSTRAINT `FK_StaffID3` FOREIGN KEY (`StaffID`) REFERENCES `Staff` (`StaffID`),
CONSTRAINT `FK_CampaignID2` FOREIGN KEY (`CampaignID`) REFERENCES `Advertising_Campaign` (`CampaignID`))
ENGINE = InnoDB;
which gives the tables:
Basically, I want to write a query that will return me a list of the advertising_campaign.AdvertName with more than 2 staff members working on them and a count of the number of staff members whose staff_works_campaign.SalaryGrade is greater than 2.
I have tried:
select a.advertname, count(*) as 'Greater Than 2'
from advertising_campaign a inner join staff_works_campaign
where staff_works_campaign.SalaryGrade > 2;
Which isn't exactly what I want, it returns:
I am a bit unsure at what this is returning exactly because I would thought it would have returned a count of 2(because of the fact that there are 2 entries with a SalaryGrade of 4 in the table), might be because of the way inner join works?
I am also a bit confused as to how to filter for 'more than 2 staff members', My idea is to see the amount of times the staff_works_campaign.CampaignID has appeared in the staff_works_campaign table to see how many staff members are apart of the same campaign.
I'm not sure how to structure it to count the amount of times campaignID is repeated and to return the names of the adverts that have a campaignID that has 2 or more staff members working on it.
So in this case I would want it to return a table with AdvertName of only those campaigns with two or more people working on them and a count of those staff members who have a salary grade greater than 2.
SELECT
a.CampaignID
,a.AdvertName
,COUNT(DISTINCT s.StaffID) AS [Count of staff]
,SUM(
--Use this to get a total of the staff who are
--in a SalaryGrade greater than 2
CASE WHEN s.SalaryGrade > 2
THEN 1
ELSE 0 --anyone who is under this level will be a 0 and not count
END
) as [Count of staff above salary grade]
FROM
advertising_campaign AS a
INNER JOIN staff_works_campaign AS s
--dont forget the join condition
ON a.CampaignID = s.CampaignID
--Dont want a where here, we want to include ALL staff.
--WHERE
-- staff_works_campaign.SalaryGrade > 2
GROUP BY
a.CampaignID
,a.AdvertName
HAVING
--more than two members of staff working on the same campaign.
COUNT(DISTINCT s.StaffID) > 2
Firstly you need a condition to join the two tables on. Secondly you can use a Group By and Having clause to put the filter on aggregation. Finally you need to count the number of staff with a salary grade > 2, which you can SUM a conditional for. Something like this:
select a.advertname, Sum(CASE WHEN c.SalaryGrade > 2 THEN 1 ELSE 0 END) as 'Greater Than 2'
from advertising_campaign a inner join staff_works_campaign c
on a.CampaignId = c.CampaignId
Group By a.advertname Having count(*) >= 2;
You can do something like following if you want both conditions together,
2 people working on camp, whose salary_grade >2
SELECT AdvertName
FROM Advertising_Campaign
WHERE CampaignID IN
(
SELECT
CampaignID
FROM
staff_works_campaign
WHERE
SalaryGrade > 2
GROUP BY
CampaignID
HAVING
COUNT(DISTINCT StaffID) >= 2
)
What you have received as a result from your query is the count of all staff members across all campaigns that have a salary grade greater than 2. It returns "Star Wars 3" as the advert name simply because it's the first name it came across in all the results that the COUNT operates over. (Some other SQL technologies such as Microsoft SQL Server actually won't allow you to do this kind of query to avoid this confusion.)
In order to get the results to be split by the campaign, you have to use the GROUP BY clause as suggested in the other answers. This will tell SQL to calculate any aggregate functions (i.e. COUNT) over groups of records that all match for one or more fields. In your case, you want to group by the campaignID, since you want the COUNT to be calculated for each campaign individually. You could do this on the advert name as well, but better to do it on the ID in case you have two with the same name. Modifying your query to do that, we get:
select a.campaignID, count(*) as 'Greater Than 2'
from advertising_campaign a inner join staff_works_campaign
where staff_works_campaign.SalaryGrade > 2
group by a.campaignID;
This still isn't quite going to work though, because the salary grade condition is applied before the COUNT. We need to move that part out into a new query that wraps around this one. We also need to limit the campaigns down to those with two staff - thankfully, we don't need yet another outer query for that. The HAVING keyword allows a condition to be applied after a GROUP BY, so we can do:
select a.campaignID, count(*) as 'staff_amount'
from advertising_campaign a inner join staff_works_campaign
group by a.campaignID
having staff_amount > 2;
Now, adding the staff salary condition and another select from advertising_campaign to get the advert name in an outer query, we finally get:
select advertising_campaign.advertname
from advertising_campaign
inner join staff_works_campaign on advertising_campaign.campaignid = staff_works_campaign.campaignid
inner join
(
select a.campaignID, count(*) as 'staff_amount'
from advertising_campaign a inner join staff_works_campaign
group by a.campaignID
having staff_amount > 2
) large_campaigns on advertising_campaign.campaignid = large_campaigns.campaignid
where staff_works_campaign.salarygrade > 2

MySQL query using different tables and filters

I have one table called 'vacancies' which has a 'vacancy_id' PK. It looks like this:
create table vacancies
(
vacancy_id int not null auto_increment,
org_id int not null,
name varchar(255) not null comment 'title',
vacancy_visibility_start_date datetime comment 'vacancy visibility date, when it needs to be active on the website',
vacancy_visibility_end_date datetime,
primary key (vacancy_id)
);
Following this I have a couple of other tables which are linked to this one.
create table vacancy_calendar
(
vacancy_calendar_id int not null auto_increment,
vacancy_id int,
date_from datetime not null,
date_to datetime not null,
primary key (vacancy_calendar_id)
);
create table vacancy_interests
(
vacancy_id int,
interest_id int
);
create table vacancy_skills
(
vacancy_id int,
skill_id int
);
All of these tables can contain multiple rows for the same vacancy_id.
My page has different filters which I want to process via AJAX.
I want to have one line per vacancy containing all data I need + it has to match my filtering criteria. However I am not sure how my query has to look like in order to retrieve the result I am looking for.
It is possible to filter on 'interest_id' , 'skill_id', 'date_from' and 'date_to'.
I started with the following query but I am stuck very fast:
SELECT v.*, vi.interest_id
FROM `vacancies` as v
INNER JOIN `vacancy_interests` as vi on v.vacancy_id = vi.vacancy_id
GROUP BY v.vacancy_id
This query will only return me 1 interest_id for a vacancy, even if the vacancy has 3 interest_id rows in the vacancy_interest table. If I remove the GROUP BY statement I will get 3 rows for the same vacancy which is not what I want either.
Ideally I would want the interest_id's to be each in a separate column or in the same field separated by comma's. Or if there are any other possibilities/suggestions feel free to share!
You can use group_concat for get interest_id separated by comma
SELECT v.*, group_concat(vi.interest_id)
FROM `vacancies` as v
INNER JOIN `vacancy_interests` as vi on v.vacancy_id = vi.vacancy_id
GROUP BY v.vacancy_id
Referring to you comment about add where eg:
You can add where condition
SELECT v.*, group_concat(vi.interest_id)
FROM `vacancies` as v
INNER JOIN `vacancy_interests` as vi on v.vacancy_id = vi.vacancy_id
INNER JOIN `vacancy_skills` as vs ON vs.vacancy_id = v.vacancy_id
WHERE vs.skill_id IN (4) AND vi.interest_id IN (1,3)
GROUP BY v.vacancy_id
In this case the gorup_concat is applied on the resulting rows .. because group by perform the related action on the selected resulting rows .

Return information about oldest employee in department, if department has more than 20 employees

Don't judge me, I'm new to SQL querying. I got scheme, like the one shown on picture below. So, there are 2 tables, first one Employees contains EmployeeID, FirstName, LastName, DateOfBirth and DepartmentID. The second one is called Department and contains DepartmentID and DepartmentName .
I want to return FirstName, LastName and DepartmentName for the oldest employee from each department containing more than 20 employees.
My solution is the following query :
SELECT FirstName, LastName, DepartmentName
FROM employees
LEFT JOIN department
ON employees.DepartmentID = department.DepartmentID
WHERE (employees.DateOfBirth =
(SELECT MIN(employees.DateOfBirth ) FROM (
SELECT *FROM employees WHERE employees.DepartmentID IN (
SELECT employees.DepartmentID FROM employees GROUP BY DepartmentID HAVING COUNT(*) > 20)));
I think that logic is fine, because inner SELECT statement will return ID's of every department with more than 20 employees, and the outer should return to oldest employee.
The problem that I have is when I try to execute this query, it is returning SQL error every derived table must have it's own alias.
I've tried putting alias on each derived table, but outcome is still the same.
Please, help me with this one.
Also, if someone has different solution, please share it.
Thank You.
Addition which strawberry asked for, Create queries
CREATE TABLE Employees
(
EmployeeID int,
FirstName varchar(10),
LastName varchar(15),
DateOfBirth date,
DeparmentID int
)
CREATE TABLE Department
(
DepartmentID int,
DepartmentName varchar(15)
)
Your query is tricky to read due to inconsistent formatting. So I'll clean it up as follows:
SELECT FirstName, LastName, DepartmentName
FROM employees
LEFT JOIN department
ON employees.DepartmentID = department.DepartmentID
WHERE (employees.DateOfBirth =
(
SELECT MIN(employees.DateOfBirth)
FROM (
SELECT *
FROM employees
WHERE employees.DepartmentID IN (
--Departments with more than 20 employees
SELECT employees.DepartmentID
FROM employees
GROUP BY DepartmentID
HAVING COUNT(*) > 20)
) -- You need an alias here.
-- Also from this point you were missing closing brackets.
Problems with your query:
Obviously the missing alias and closing brackets meant you couldn't even test your query.
Also SELECT MIN(employees.DateOfBirth) returns only a single value. Not a value per department.
So your overall result includes only the oldest employee across all the 'big' departments. (Unless the oldest employee in each department happened to have the same birth date.)
It could also include results from a smaller department if any employee happened to have the same birth date the oldest from the big departments. And that employee needn't even be the oldest in their department!
You also have some inefficiencies by using more sub-queries than necessary.
CTEs (common table expressions) are great at simplifying complex queries. But I don't know if mysql supports them. So this solution still uses sub-queries.
SELECT e.FirstName, e.LastName, d.DepartmentName
FROM employees e -- I prefer short aliases
INNER JOIN (
-- This sub-query returns the earliest birth date within each
-- big department. This needs to be an aliased query so you
-- can join to other tables for your desired columns.
SELECT DepartmentID, MIN(DateOfBirth) AS MinDOB -- Must alias column
FROM employees
WHERE DepartmentID IN (
-- Big departments
SELECT DepartmentID
FROM employees
GROUP BY DepartmentID
HAVING COUNT(*) > 20
)
GROUP BY DepartmentID
) ddob -- Alias Department Date of Birth
-- As a result of inner joining to ddob your employees
-- will be filtered to only those that match the relevant
-- ones identified in the query.
ON e.DepartmentID = ddob.DepartmentID
AND e.DateOfBirth = ddob.MinDOB
INNER JOIN Department d
ON d.DepartmentID = e.DepartmentID
Something to note in the above solution, if 2 employees are tied for being oldest in a department, both will be returned.
This approach is structurally similar to yours, but you could also approach the problem from another direction.
Start out getting oldest employees in ALL departments.
And only at the end filter the result according to department size.
I'll leave that to you to try. I suspect the query would be a little simpler.
The following SQL script correspond to your Class Diagram:
CREATE TABLE Departments (
DepartmentID int AUTO_INCREMENT PRIMARY KEY,
DepartmentName varchar(15)
);
CREATE TABLE Employees (
EmployeeID int AUTO_INCREMENT PRIMARY KEY,
FirstName varchar(10),
LastName varchar(15),
DateOfBirth date,
DepartmentID int,
FOREIGN KEY (DepartmentID) REFERENCES Departments(DepartmentID)
);
Following your classes diagram, there is a department for each employee. So, is the reason of using INNER JOIN. I think the following query do what you want:
SELECT ee.FirstName, ee.LastName, ee.DateOfBirth, t.DepartmentName
FROM
(
SELECT e.DepartmentID, d.DepartmentName, MIN(e.DateOfBirth) AS DateOfBirth
FROM Employees AS e
INNER JOIN Departments AS d ON e.DepartmentID = d.DepartmentID
WHERE e.DepartmentID IN (
SELECT DepartmentID
FROM Employees
GROUP BY DepartmentID HAVING COUNT(DepartmentID) > 20
)
GROUP BY e.DepartmentID
) AS t
INNER JOIN Employees AS ee
WHERE ee.DepartmentID = t.DepartmentID AND ee.DateOfBirth = t.DateOfBirth
Example of output:
FirstName LastName DateOfBirth DepartmentName
fisrt14 last14 02/01/2000 SI
fisrt31 last31 12/01/2003 Finance
You improve its performance!

SQL COUNT() function and LEFT OUTER JOIN

I have two tables, users and departments. I want to have table, where are two columns: first is department name, second is count - how many users are assigned to this department.
And I have this piece of code:
SELECT department_name as 'deptName',
COUNT(users.department_id) as 'userCount'
FROM departments
LEFT OUTER JOIN users
ON departments.id = users.department_id
GROUP BY 'deptName'
Department's table columns are:
integer id PK
varchar(20) department_name
User's table columns are:
integer id PK
varchar(20) name
varchar(20) surname
int department_id FK
But it does not work.
Now I have 2 departments, and output should be 2 rows, first with count 8 and second with count 1. But I see only one row, with all count (9).
I use MySQL installed with XAMPP.
SELECT department_name as 'deptName',
COUNT(users.department_id) as 'userCount'
FROM departments
LEFT OUTER JOIN users
ON departments.id = users.department_id
GROUP BY `deptName`
Notice the tick marks vs. your single quotes in the GROUP BY (this is the key to the left of the 1 on your keyboard). Refer to: http://dev.mysql.com/doc/refman/5.0/en/problems-with-alias.html
You could also just group by department_name (the field itself, rather than the alias)
Currently you are grouping on the literal value 'deptName', not the field that you've given an alias of deptName, which is why you only have 1 row returned. You're not actually doing any grouping.