Aggregation giving wrong result in T-SQL - tsql

I am getting wrong sum of values in more than two tables.
Please advise the correct qry for the below
my sample data is as follows.
IF OBJECT_ID('tempdb..#Departamentos') IS NOT NULL
DROP TABLE #Departamentos
GO
CREATE TABLE #Departamentos (ID INT IDENTITY(1,1) PRIMARY KEY,
Nome_Dep VARCHAR(200))
GO
INSERT INTO #Departamentos(Nome_Dep)
VALUES('Vendas'), ('TI'), ('Recursos Humanos')
GO
IF OBJECT_ID('tempdb..#Funcionarios') IS NOT NULL
DROP TABLE #Funcionarios
GO
CREATE TABLE #Funcionarios (ID INT IDENTITY(1,1) PRIMARY KEY,
ID_Dep INT,
Nome VARCHAR(200),
Salario Numeric(18,2))
GO
INSERT INTO #Funcionarios (ID_Dep, Nome, Salario)
VALUES(1, 'Fabiano', 2000), (1, 'Amorim', 2000), (1, 'Diego', 9000),
(2, 'Felipe', 2000), (2, 'Ferreira', 2500), (2, 'Nogare', 11999),
(3, 'Laerte', 5000), (3, 'Luciano', 23500), (3, 'Zavaschi', 13999)
GO
IF OBJECT_ID('tempdb..#deals') IS NOT NULL
DROP TABLE #deals
GO
CREATE TABLE #deals (ID INT IDENTITY(1,1) PRIMARY KEY,
ID_Deal INT,
Nome VARCHAR(200),
Revenue Numeric(18,2))
GO
INSERT INTO #deals (ID_Deal, Nome, Revenue)
VALUES(1, 'Fabiano', 50), (1, 'Amorim', 20), (1, 'Diego', 90),
(2, 'Felipe', 20), (2, 'Ferreira', 25), (2, 'Nogare', 119),
(3, 'Laerte', 50), (3, 'Luciano', 23), (3, 'Zavaschi', 13)
GO
My query is as follows to get the result:
select d.id,d.Nome_Dep,sum(salario)salario,sum(revenue)revenue from #Departamentos d left join #Funcionarios f on d.ID=f.ID_Dep
left join #deals dd on dd.ID_Deal=d.ID
group by d.id,d.Nome_Dep
But my desired result should be:
ID Nome_Dep salario revenue
1 Vendas 13000.00 160.00
2 TI 16499.00 164.00
3 Recursos Humanos 42499.00 86.00
Thanks

There is probably duplication happening because of the double table join. One way around this is to aggregate each table in separate subqueries, and then join to them:
SELECT
d.id,
d.Nome_Dep,
COALESCE(f.sum_salario, 0) AS sum_salario,
COALESCE(dd.sum_revenue, 0) AS sum_revenue
FROM #Departamentos d
LEFT JOIN
(
SELECT ID_Dep, SUM(salario) AS sum_salario
FROM #Funcionarios
GROUP BY ID_Dep
) f
ON d.ID = f.ID_Dep
LEFT JOIN
(
SELECT ID_Deal, SUM(revenue) AS sum_revenue
FROM #deals
GROUP BY ID_Deal
) dd
ON d.ID = dd.ID_Deal;
Demo

Related

Postgresql find by count, joined table

Given 3 tables. I need to build SQL query to find two actors who CAST TOGETHER THE MOST and list the titles of those movies. Sort alphabetically
https://www.db-fiddle.com/f/r2Y9CpH8n7MHTeBaqEHe9S/0
The data for reproducing below:
create table film_actor
(
actor_id integer,
film_id integer
)
;
create table film
(
film_id integer,
title varchar
)
;
create table actor
(
actor_id integer,
first_name varchar,
last_name varchar
)
;
INSERT INTO public.film_actor (actor_id, film_id) VALUES (1, 1);
INSERT INTO public.film_actor (actor_id, film_id) VALUES (1, 2);
INSERT INTO public.film_actor (actor_id, film_id) VALUES (1, 3);
INSERT INTO public.film_actor (actor_id, film_id) VALUES (2, 1);
INSERT INTO public.film_actor (actor_id, film_id) VALUES (2, 2);
INSERT INTO public.film_actor (actor_id, film_id) VALUES (2, 3);
INSERT INTO public.film_actor (actor_id, film_id) VALUES (3, 1);
INSERT INTO public.film (film_id, title) VALUES (1, 'First');
INSERT INTO public.film (film_id, title) VALUES (2, 'Second');
INSERT INTO public.film (film_id, title) VALUES (3, 'Third');
INSERT INTO public.film (film_id, title) VALUES (4, 'Fourth');
INSERT INTO public.actor (actor_id, first_name, last_name) VALUES (1, 'John', 'Snow');
INSERT INTO public.actor (actor_id, first_name, last_name) VALUES (2, 'Spider', 'Man');
INSERT INTO public.actor (actor_id, first_name, last_name) VALUES (3, 'Mike', 'Kameron');
Is this what you are looking for?
with acting_pairs as (
select a1.actor_id as a1_id, a2.actor_id as a2_id
from film_actor a1
join film_actor a2 on a1.film_id = a2.film_id
where a1.actor_id < a2.actor_id
)
select a1_id, a2_id, count(*) as total
from acting_pairs
group by (a1_id, a2_id)
order by total desc
limit 1
Giving us expected output for the example input would be nice.

Select persons who do only the project with the same city

So I have 3 tables: Management (…, personID, projectID //personID and projectID can dublicate) Project(id, city, …) and Person (id, name, city, …)
I need to output persons who only do projects with the same city.
Will be greatfull for any help or idea.
Here's a simple query with joins, group by, having, and count(distinct) that gets the desired results.
First, create and populate sample tables (Please save us this step in your future questions):
DECLARE #Project as table
(
id int,
city varchar(20)
);
DECLARE #Person as table
(
id int,
firstName varchar(10),
lastName varchar(10)
);
DECLARE #Managment as table
(
projectId int,
personId int
);
INSERT INTO #Project (id, city) VALUES
(1, 'Abilene'),
(2, 'Akron'),
(3, 'Albuquerque'),
(4, 'Alexandria'),
(5, 'Allentown');
INSERT INTO #Person (Id, firstName, lastName) VALUES
(1, 'Aaron', 'Carter'),
(2, 'Aaron', 'Eckhart'),
(3, 'Abbie', 'Cornish'),
(4, 'Ace', 'Young'),
(5, 'Adam', 'Brody');
INSERT INTO #Managment (projectId, personId) VALUES
(1, 1),
(1, 2),
(3, 3),
(3, 2),
(3, 5),
(4, 4),
(4, 1),
(5, 5);
The query:
SELECT personId, firstName, lastName, max(city) as city
FROM #Person As pe
JOIN #Managment As m
ON pe.Id = m.personId
JOIN #Project As pr
ON pr.Id = m.projectId
GROUP BY personId, firstName, lastName
HAVING COUNT(DISTINCT city) = 1
(I've added the city's name as a bonus if you wanted two cities you could go with max and min, more than that you can't) - note the usage of count(distinct city) in the having clause.
Results:
personId firstName lastName city
3 Abbie Cornish Albuquerque
4 Ace Young Alexandria

CREATE A QUERY WITH GROUP BY

I am trying to count how many "subsidiary companies" work for each "parent company", I came up with something like this...
select cs.contract_number, jd.Job_description, c.Parent_Company,
count(sub.subsidiary_company)
from contracts cs
join Job jd
on jd.ID = cs.ID
join Company c
on cs.Company_ID = c.Company_ID
join Sub_Company sub
on c.Company_ID = sub.Prime
group by cs.contract_number, jd.Job_description, c.Parent_Company;
This is an example of the result I am looking for...
however, I am not getting the results that I need. I am trying to create a query that displays the distinct count of "subsidiary companies" that work under each "parent company" for each "Job description". Can you please assist? Thanks.
For better help you need to provide table structures and sample data in an easily consumable format like this:
-- Table structure and sample data
DECLARE #contracts TABLE (Id int identity, contract_number varchar(100), Company_Id int);
DECLARE #job TABLE (Id int identity, Job_description varchar(100));
DECLARE #Company TABLE (Company_Id int identity, parent_company varchar(100));
DECLARE #Sub_Company TABLE (Company_Id int, Parent_Company_Id int);
INSERT #contracts(contract_number, Company_Id)
VALUES ('001122',1), ('009922',1), ('123ABC',2), ('XXXYYYZZZ',2);
INSERT #job(Job_description)
VALUES ('Fun stuff'), ('Hard Stuff'), ('Dumb stuff');
INSERT #Company(parent_company)
VALUES ('A Co'), ('B Co');
INSERT #Sub_Company(Parent_Company_Id, Company_Id)
VALUES (1,10), (1,11), (1, 20), (1, 30), (2, 400), (2, 500);
-- Select statements to review the data
SELECT * FROM #contracts;
SELECT * FROM #Company;
SELECT * FROM #job;
SELECT * FROM #Sub_Company;
You can just copy/paste the above code and run it locally.
I am trying to create a query that displays the distinct count of
"subsidiary companies" that work under each "parent company" for each
"Job description".
Your GROUP BY statement will look like this:
group by jd.Job_description, c.Parent_Company, cs.contract_number
Your solution will look something like this
-- Table structure and sample data
DECLARE #contracts TABLE (Id int identity, contract_number varchar(100), Company_Id int);
DECLARE #job TABLE (Id int identity, Job_description varchar(100));
DECLARE #Company TABLE (Company_Id int identity, parent_company varchar(100));
DECLARE #Sub_Company TABLE (Company_Id int, Parent_Company_Id int);
INSERT #contracts(contract_number, Company_Id)
VALUES ('001122',1), ('009922',1), ('123ABC',2), ('XXXYYYZZZ',2);
INSERT #job(Job_description)
VALUES ('Fun stuff'), ('Hard Stuff'), ('Dumb stuff');
INSERT #Company(parent_company)
VALUES ('A Co'), ('B Co');
INSERT #Sub_Company(Parent_Company_Id, Company_Id)
VALUES (1,10), (1,11), (1, 20), (1, 30), (2, 400), (2, 500);
-- solution
select jd.Job_description, c.Parent_Company, cs.contract_number, total = count(DISTINCT Sub.Company_Id)
from #contracts cs
join #job jd on jd.ID = cs.ID
join #Company c on cs.Company_ID = c.Company_ID
join #Sub_Company sub on c.Company_ID = sub.Parent_Company_Id
group by jd.Job_description, c.Parent_Company, cs.contract_number ;
Update the sample data I provided to include the correct columns and update the sample data to look more accurate. Next post a screen shot or something that shows what results you want. Do that and you'll get good help fast.

Select value from an enumerated list in PostgreSQL

I want to select from an enumaration that is not in database.
E.g. SELECT id FROM my_table returns values like 1, 2, 3
I want to display 1 -> 'chocolate', 2 -> 'coconut', 3 -> 'pizza' etc. SELECT CASE works but is too complicated and hard to overview for many values. I think of something like
SELECT id, array['chocolate','coconut','pizza'][id] FROM my_table
But I couldn't succeed with arrays. Is there an easy solution? So this is a simple query, not a plpgsql script or something like that.
with food (fid, name) as (
values
(1, 'chocolate'),
(2, 'coconut'),
(3, 'pizza')
)
select t.id, f.name
from my_table t
join food f on f.fid = t.id;
or without a CTE (but using the same idea):
select t.id, f.name
from my_table t
join (
values
(1, 'chocolate'),
(2, 'coconut'),
(3, 'pizza')
) f (fid, name) on f.fid = t.id;
This is the correct syntax:
SELECT id, (array['chocolate','coconut','pizza'])[id] FROM my_table
But you should create a referenced table with those values.
What about creating another table that enumerate all cases, and do join ?
CREATE TABLE table_case
(
case_id bigserial NOT NULL,
case_name character varying,
CONSTRAINT table_case_pkey PRIMARY KEY (case_id)
)
WITH (
OIDS=FALSE
);
and when you select from your table:
SELECT id, case_name FROM my_table
inner join table_case on case_id=my_table_id;

Need help with a SELECT statement

I express the relationship between records and searchtags that can be attached to records like so:
TABLE RECORDS
id
name
TABLE SEARCHTAGS
id
recordid
name
I want to be able to SELECT records based on the searchtags that they have. For example, I want to be able to SELECT all records that have searchtags:
(1 OR 2 OR 5) AND (6 OR 7) AND (10)
Using the above data structure, I am uncertain how to structure the SQL to accomplish this.
Any suggestions?
Thanks!
You may want to try the following:
SELECT r.id, r.name
FROM records r
WHERE EXISTS (SELECT NULL FROM searchtags WHERE recordid = r.id AND id IN (1, 2, 5)) AND
EXISTS (SELECT NULL FROM searchtags WHERE recordid = r.id AND id IN (6, 7)) AND
EXISTS (SELECT NULL FROM searchtags WHERE recordid = r.id AND id IN (10));
Test case: Note that only records 1 and 4 will satisfy the query criteria.
CREATE TABLE records (id int, name varchar(10));
CREATE TABLE searchtags (id int, recordid int);
INSERT INTO records VALUES (1, 'a');
INSERT INTO records VALUES (2, 'b');
INSERT INTO records VALUES (3, 'c');
INSERT INTO records VALUES (4, 'd');
INSERT INTO searchtags VALUES (1, 1);
INSERT INTO searchtags VALUES (2, 1);
INSERT INTO searchtags VALUES (6, 1);
INSERT INTO searchtags VALUES (10, 1);
INSERT INTO searchtags VALUES (1, 2);
INSERT INTO searchtags VALUES (2, 2);
INSERT INTO searchtags VALUES (3, 2);
INSERT INTO searchtags VALUES (1, 3);
INSERT INTO searchtags VALUES (10, 3);
INSERT INTO searchtags VALUES (5, 4);
INSERT INTO searchtags VALUES (7, 4);
INSERT INTO searchtags VALUES (10, 4);
Result:
+------+------+
| id | name |
+------+------+
| 1 | a |
| 4 | d |
+------+------+
2 rows in set (0.01 sec)
SELECT
id, name
FROM
records
WHERE
EXISTS (
SELECT 1 FROM searchtags WHERE recordid = records.id AND id IN (1, 2, 5)
)
AND EXISTS (
SELECT 1 FROM searchtags WHERE recordid = records.id AND id IN (6, 7)
)
AND EXISTS (
SELECT 1 FROM searchtags WHERE recordid = records.id AND id IN (10)
)
not sure how to do it in mysql, but in t-sql, you could do something like:
SELECT id, name FROM RECORDS where id in (SELECT recordid from SEARCHTAGS where id in (1,2,5,6,7,10))
I may not be understanding your question entirely... but I gave it my best.
Try:
SELECT R.*
FROM RECORDS R, SEARCHTAGS S
WHERE R.id == S.recordid
AND S.name in (1,2,5,6,7,10);
Don't know if you need S.name or S.id, but this is an example.
select RECORDS.name
from RECORDS join SEARCHTAGS
on RECORDS.id = SEARCHTAGS.recordid
where RECORDS.id in (1,2,...)
I misread the question first time around, and thought it was asking for
(1 AND 2 AND 5) OR (6 AND 7) OR (10)
instead of the correct
(1 OR 2 OR 5) AND (6 OR 7) AND (10)
All the answers so far have concentrated on answering the specific example, rather than addressing the more general question "and suppose I want a different set of criteria next time".
Actual question
I can't do much better than the selected answer for the actual question (Query 1):
SELECT r.id, r.name
FROM Records AS r
WHERE EXISTS(SELECT * FROM SearchTags AS s
WHERE r.id = s.recordid AND s.id IN (1, 2, 5))
AND EXISTS(SELECT * FROM SearchTags AS s
WHERE r.id = s.recordid AND s.id IN (6, 7))
AND EXISTS(SELECT * FROM SearchTags AS s
WHERE r.id = s.recordid AND s.id IN (10));
This could be written as a join to 3 aliases for the SearchTags table.
Alternative question
There are several ways to answer the alternative - I think this is the most nearly neat and extensible. Clearly, the one item (10) is easy (Query 2):
SELECT r.id, r.name
FROM records AS r JOIN searchtags AS t ON r.id = t.recordid
WHERE t.id IN (10) -- or '= 10' but IN is consistent with what follows
The two items (6 or 7) can be done with (Query 3):
SELECT r.id, r.name
FROM records AS r JOIN searchtags AS t ON r.id = t.recordid
WHERE t.id IN (6, 7)
GROUP BY r.id, r.name
HAVING COUNT(*) = 2
The three items (1, 2, 5) can be done with (Query 4):
SELECT r.id, r.name
FROM records AS r JOIN searchtags AS t ON r.id = t.recordid
WHERE t.id IN (1, 2, 5)
GROUP BY r.id, r.name
HAVING COUNT(*) = 3
And the whole collection can be a UNION of the three terms.
Generalizing the solutions
The downside of this solution is that the SQL must be manually crafted for each set of items.
If you want to automate the 'SQL generation', you need the control data - the sets of interesting search tags - in a table:
CREATE TABLE InterestingTags(GroupID INTEGER, TagID INTEGER);
INSERT INTO InterestingTags(1, 1);
INSERT INTO InterestingTags(1, 2);
INSERT INTO InterestingTags(1, 5);
INSERT INTO InterestingTags(2, 6);
INSERT INTO InterestingTags(2, 7);
INSERT INTO InterestingTags(3, 10);
For the query asking for '(1 OR 2 OR 5) AND (...)' (conjunctive normal form), you can write (Query 5):
SELECT r.id, r.name
FROM records AS r JOIN
searchtags AS s ON r.id = s.recordID JOIN
interestingtags AS t ON s.id = t.tagID
GROUP BY r.id, r.name
HAVING COUNT(DISTINCT t.GroupID) = (SELECT COUNT(DISTINCT GroupID)
FROM InterestingTags);
This checks that the number of distinct 'interesting groups of tags' for a given record is equal to the total number of 'interesting groups of tags'.
For the query asking for '(1 AND 2 AND 5) OR (...)' (disjunctive normal form), you can write a join with InterestingTags and check that the Record has as many entries as the group of tags (Query 6):
SELECT i.id, i.name
FROM (SELECT q.id, q.name, c.GroupSize,
COUNT(DISTINCT t.GroupID) AS GroupCount
FROM records AS q JOIN
searchtags AS s ON q.id = s.recordID JOIN
interestingtags AS t ON s.id = t.tagID JOIN
(SELECT GroupID, COUNT(*) AS GroupSize
FROM InterestingTags
GROUP BY GroupID) AS c ON c.GroupID = t.GroupID
GROUP BY q.id, q.name, c.GroupSize
) AS i
WHERE i.GroupCount = i.GroupSize;
Test Data
I took the test data from Daniel Vassalo's answer and augmented it with some extra values:
CREATE TABLE records (id int, name varchar(10));
CREATE TABLE searchtags (id int, recordid int);
INSERT INTO records VALUES (1, 'a');
INSERT INTO records VALUES (2, 'b');
INSERT INTO records VALUES (3, 'c');
INSERT INTO records VALUES (4, 'd');
INSERT INTO records VALUES (11, 'A11');
INSERT INTO records VALUES (21, 'B12');
INSERT INTO records VALUES (31, 'C13');
INSERT INTO records VALUES (41, 'D14');
INSERT INTO records VALUES (51, 'E15');
INSERT INTO records VALUES (61, 'F16');
INSERT INTO searchtags VALUES (1, 1);
INSERT INTO searchtags VALUES (2, 1);
INSERT INTO searchtags VALUES (6, 1);
INSERT INTO searchtags VALUES (10, 1);
INSERT INTO searchtags VALUES (1, 2);
INSERT INTO searchtags VALUES (2, 2);
INSERT INTO searchtags VALUES (3, 2);
INSERT INTO searchtags VALUES (1, 3);
INSERT INTO searchtags VALUES (10, 3);
INSERT INTO searchtags VALUES (5, 4);
INSERT INTO searchtags VALUES (7, 4);
INSERT INTO searchtags VALUES (10, 4);
INSERT INTO searchtags VALUES (1, 11);
INSERT INTO searchtags VALUES (2, 11);
INSERT INTO searchtags VALUES (5, 11);
INSERT INTO searchtags VALUES (6, 21);
INSERT INTO searchtags VALUES (7, 21);
INSERT INTO searchtags VALUES (10, 31);
INSERT INTO searchtags VALUES (1, 41);
INSERT INTO searchtags VALUES (6, 41);
INSERT INTO searchtags VALUES (10, 41);
INSERT INTO searchtags VALUES (2, 51);
INSERT INTO searchtags VALUES (5, 51);
INSERT INTO searchtags VALUES (10, 51);
INSERT INTO searchtags VALUES (7, 61);
INSERT INTO searchtags VALUES (2, 61);
INSERT INTO searchtags VALUES (1, 61);
CREATE TABLE InterestingTags(GroupID INTEGER, TagID INTEGER);
INSERT INTO InterestingTags VALUES(1, 1);
INSERT INTO InterestingTags VALUES(1, 2);
INSERT INTO InterestingTags VALUES(1, 5);
INSERT INTO InterestingTags VALUES(2, 6);
INSERT INTO InterestingTags VALUES(2, 7);
INSERT INTO InterestingTags VALUES(3, 10);
Test results
The outputs that I got were:
Query 1
1 a
4 d
41 D14
Query 2
1 a
3 c
4 d
31 C13
41 D14
51 E15
Query 3
21 B12
Query 4
11 A11
Query 5
1 a
41 D14
4 d
Query 6
4 d
31 C13
3 c
1 a
41 D14
51 E15
Clearly, if I wanted the output in a specific order, I would add an ORDER BY clause to the queries.