Given a company table companylist and a import-export table trades, I want to find the total exports and imports per country. I want to sort them by country name and print 0s instead of nulls if exports/imports are 0. All countries need to be present in output.
Companylist table =>
name country
abc corp congo
arcus t.g. ghana
bob timbuktu
ddr ltd ghana
none at all nothingland
xyz corp bubbleland
Y zap timbuktu
trades table
id seller buyer value
20120125 bob arcus t.g. 100
20120216 abc corp ddr ltd 30
20120217 abc corp ddr ltd 50
20121107 abc corp bob 10
20123112 arcus t.g. Y zap 30
The tables DDL -
create table if not exists companylist (name varchar(30) not null, country varchar(30) not null, unique(name));
truncate table companylist;
create table if not exists trades (id integer not null, seller varchar(30) not null, buyer varchar(30) not null, value integer
not null, unique(id));
truncate table trades;
insert into companylist(name,country) values ('bob','timbuktu');
insert into companylist(name,country) values ('Y zap','timbuktu');
insert into companylist(name,country) values ('ddr ltd','ghana');
insert into companylist(name,country) values ('arcus t.g.','ghana');
insert into companylist(name,country) values ('abc corp','congo');
insert into companylist(name, country) values ('xyz corp', 'bubbleland');
insert into companylist(name,country) values ('none at all','nothingland');
insert into trades(id,seller,buyer,value) values (20121107,'abc corp','bob',10);
insert into trades(id,seller,buyer,value) values (20123112,'arcus t.g.','Y zap',30);
insert into trades(id,seller,buyer,value) values (20120125,'bob','arcus t.g.',100);
insert into trades(id,seller,buyer,value) values (20120216,'abc corp','ddr ltd',30);
insert into trades(id,seller,buyer,value) values (20120217,'abc corp','ddr ltd',50);
So far i have
select name, country, coalesce(sum(b.value),0) as export, coalesce(sum(c.value),0) as import
from companylist a left join trades b on a.name = b.seller
left join trades c on a.name = c.buyer
group by a.country order by a.country ASC;
I think this works, but does someone have a more elegant / better / different solution? I am learning sql so any feedback helps.
Conditional aggregation is an option:
SELECT country,
SUM(CASE WHEN a.name = b.seller
THEN b.value
ELSE 0
END) as export,
SUM(CASE WHEN a.name = b.buyer
THEN b.value
ELSE 0
END) as import
FROM companylist a
LEFT join trades b ON a.name IN (b.seller, b.buyer)
GROUP BY a.country
ORDER BY a.country ASC;
fiddle
PS. companylist.name makes no sense in output - it was removed.
I would recommend union all and group by:
select c.country, sum(sales) as exports, sum(buys) as imports
from countrylist c left join
((select t.seller as name, value as sales, 0 as buys
from trades t
) union all
(select t.buyer, 0 as sales, value as buys
from trades t
)
) bs
on c.name = bs.name
group by c.country;
This should be much more efficient than Akina's solution which uses a function in the on clause (essentially an or).
Related
I have a table of Friends (Ann, Bob, Carl) and a table of Fruits (Apple, Banana, Cherry, Date, Fig, Grapefruit)
I need to create an intersection table (Friends X Fruit) that associates each Friend with 3 randomly selected fruits.
For example:
Ann might be associated with Cherry, Date, Fig
Bob might be associated with Apple, Fig, Banana
Carl might be associated with Banana, Cherry, Date
I have developed a script that works well for only ONE friend (pasted below), but I am struggling to expand the script to handle all of the friends at once.
(If I remove the WHERE clause, then every friend gets assigned the same set of fruits, which doesn't meet my requirement).
Setup statements and current script pasted below.
Thank you for any guidance!
CREATE TABLE TempFriends ( FirstName VARCHAR(24) );
CREATE TABLE TempFruits ( FruitName VARCHAR(24) );
CREATE TABLE FriendsAndFruits( FirstName VARCHAR(24), FruitName VARCHAR(24) );
INSERT INTO TempFriends VALUES ('Ann'), ('Bob'), ('Carl');
INSERT INTO TempFruits VALUES ('Apple'), ('Banana'), ('Cherry'), ('Date'), ('Fig'), ('Grapefruit');
INSERT INTO FriendsAndFruits( FirstName, FruitName )
SELECT FirstName, FruitName
FROM TempFriends
INNER JOIN ( SELECT FruitName FROM TempFruits ORDER BY RAND() LIMIT 3 ) RandomFruit
WHERE FirstName = 'Bob';
INSERT INTO FriendsAndFruits
SELECT FirstName,
FruitName
FROM (
SELECT FirstName,
FruitName,
ROW_NUMBER() OVER (PARTITION BY FirstName ORDER BY RAND()) rn
FROM TempFriends
CROSS JOIN TempFruits
) subquery
WHERE rn <= 3;
INSERT INTO FriendsAndFruits( FirstName, FruitName )
SELECT FirstName, FruitName
FROM TempFriends
JOIN LATERAL (
SELECT FruitName
FROM TempFruits
ORDER BY TempFriends.FirstName, RAND() LIMIT 3
) RandomFruit;
INSERT INTO FriendsAndFruits( FirstName, FruitName )
SELECT FirstName, FruitName
FROM TempFriends
JOIN LATERAL (
SELECT TempFriends.FirstName tmp, FruitName
FROM TempFruits
ORDER BY RAND() LIMIT 3
) RandomFruit;
fiddle
I have two tables:
TABLE A
Unique_id
id
price
1
1
10.50
2
3
14.70
3
1
12.44
TABLE B
Unique_id
Date
Category
Store
Cost
1
2022/03/12
Shoes
A
13.24
2
2022/04/15
Hats
A
15.24
3
2021/11/03
Shoes
B
22.31
4
2000/12/14
Shoes
A
15.33
I need to filter TABLE A on a known id to get the Unique_id and average price to join to Table B.
Using this information I need to know which stores this item was sold in.
I then need to create a results table displaying the stores and the amount of days sales were recorded in the stores - regardless of whether the sales are associated with the id and the average cost.
To put it more simply I can break down the task into 2 separate commands:
SELECT AVG(price)
FROM table_a
WHERE id = 1
GROUP BY unique_id;
SELECT store, COUNT(date), AVG(cost)
FROM table_b
WHERE category = 'Shoes'
GROUP BY store;
The unique_id should inform the join but when I join the tables it messes up my COUNT function and only counts the days in which the id is connected - not the total store sales days.
The results should look something like this:
Store
AVG price
COUNT days
AVG cost
A
10.50.
3
14.60.
B
12.44
1.
22.31.
I wwas hard to grasp, what you wanted, but after some thinking and your clarification, it can be solved as the code shows
CREATE TABLE TableA
(`Unique_id` int, `id` int, `price` DECIMAL(10,2))
;
INSERT INTO TableA
(`Unique_id`, `id`, `price`)
VALUES
(1, 1, 10.50),
(2, 3, 14.70),
(3, 1, 12.44)
;
CREATE TABLE TableB
(`Unique_id` int, `Date` datetime, `Category` varchar(5), `Store` varchar(1), `Cost` DECIMAL(10,2))
;
INSERT INTO TableB
(`Unique_id`, `Date`, `Category`, `Store`, `Cost`)
VALUES
(1, '2022-03-12 01:00:00', 'Shoes', 'A', 13.24),
(2, '2022-04-15 02:00:00', 'Hats', 'A', 15.24),
(3, '2021-11-03 01:00:00', 'Shoes', 'B', 22.31),
(4, '2000-12-14 01:00:00', 'Shoes', 'A', 15.33)
SELECT
B.`Store`
, AVG(A.`price`) price
, (SELECT COUNT(*) FROM TableB WHERE `Store` = B.`Store` ) count_
, (SELECT AVG(
`cost`) FROM TableB WHERE `Store` = B.`Store` ) price
FROM TableA A
JOIN TableB B ON A.`Unique_id` = B.`Unique_id`
WHERE B.`Category` = 'Shoes'
GROUP BY B.`Store`
Store | price | count_ | price
:---- | --------: | -----: | --------:
A | 10.500000 | 3 | 14.603333
B | 12.440000 | 1 | 22.310000
db<>fiddle here
This should be the query you are after. Mainly you simply join the rows using an outer join, because not every table_b row has a match in table_a.
Then, the only hindrance is that you only want to consider shoes in your average price. For this to happen you use conditional aggregation (a CASE expression inside the aggregation function).
select
b.store,
avg(case when b.category = 'Shoes' then a.price end) as avg_shoe_price,
count(b.unique_id) as count_b_rows,
avg(b.cost) as avg_cost
from table_b b
left outer join table_a a on a.unique_id = b.unique_id
group by b.store
order by b.store;
I must admit, it took me ages to understand what you want and where these numbers result from. The main reason for this is that you have WHERE table_a.id = 1 in your query, but this must not be applied to get the result you are showing. Next time please look to it that your description, queries and sample data match.
(And then, I think that names like table_a, table_b and unique_id don't help understanding this. If table_a were called prices instead and table_b costs and unique_id were called cost_id then, I wouldn't have had to wonder how the tables are related (by id? by unique id?) and wouldn't have had to look again and again which table the cost resides in, which table has a price and which table is the outer joined one while looking at the problem, the requested result and while writing my query.)
I'm new to programming in SQL, and I need help on removing the second 1 through 5 id's on the right from my output (the numbers are highlighted in bold):
ID
Name
Gender
ID
Country
1
Abdur-Rahman
M
1
America
2
Don Madden
M
2
England
3
Dustin Tompkins
M
3
America
4
Nicki Harris
F
4
Germany
5
Samantha Harris
F
5
France
CREATE TABLE test ( id INTEGER PRIMARY KEY, name TEXT);
INSERT INTO test VALUES (1,"Albert Franco");
INSERT INTO test VALUES (2,"Don Madden");
INSERT INTO test VALUES (3,"Dustin Tompkins");
INSERT INTO test VALUES (4,"Nicki Harris");
INSERT INTO test VALUES (5,"Samantha Harris");
ALTER TABLE test
ADD COLUMN gender TEXT;
UPDATE test
SET gender = 'M'
WHERE id = 1;
UPDATE test
SET gender = 'M'
WHERE id = 2;
UPDATE test
SET gender = 'M'
WHERE id = 3;
UPDATE test
SET gender = 'F'
WHERE id = 4;
UPDATE test
SET gender = 'F'
WHERE id = 5;
CREATE TABLE country (
id INTEGER,
nation TEXT
);
INSERT INTO country VALUES (1,"America");
INSERT INTO country VALUES (2,"England");
INSERT INTO country VALUES (3,"America");
INSERT INTO country VALUES (4,"Germany");
INSERT INTO country VALUES (5,"France");
SELECT * FROM test
JOIN country
ON test.id = country.id;
To actually answer your question, you should explicitly state the columns you want, i.e.
SELECT t.id, t.name, t.gender, c.nation
FROM test AS t
JOIN country AS c
ON c.id = t.id;
It is however worth noting that your schema doesn't really make sense, you have to duplicate countries, which is not normalised. You've simply created a 1:1 relationship, you'd be as well just adding a nation column to test.
A better solution though would be to normalise the data, so your country table would become:
CREATE TABLE Country
(
Id INT AUTO_INCREMENT PRIMARY KEY,
Nation VARCHAR(50)
);
INSERT INTO Country(Nation)
VALUES ('America'), ('England'), ('France'), ('Germany');
Then in your Test Table, add CountryId as foreign key to your country table:
CREATE TABLE Test
(
Id INT AUTO_INCREMENT PRIMARY KEY,
Name VARCHAR(500) NOT NULL,
Gender CHAR(1) NOT NULL,
CountryId INT NOT NULL,
CONSTRAINT FK_Test__CountryId FOREIGN KEY (CountryID) REFERENCES Country (Id)
);
INSERT INTO Test (Name, Gender, CountryId)
SELECT data.Name, data.Gender, c.Id
FROM (
SELECT 'Albert Franco' AS Name, 'M' AS Gender, 'America' AS Nation
UNION ALL
SELECT 'Don Madden' AS Name, 'M' AS Gender, 'England' AS Nation
UNION ALL
SELECT 'Dustin Tompkins' AS Name, 'M' AS Gender, 'America' AS Nation
UNION ALL
SELECT 'Nicki Harris' AS Name, 'F' AS Gender, 'Germany' AS Nation
UNION ALL
SELECT 'Samantha Harris' AS Name, 'F' AS Gender, 'France' AS Nation
) AS data
INNER JOIN Country AS c
ON c.Nation = data.Nation;
Your final query is largely similar:
SELECT t.Id, t.Name, t.Gender, c.Nation
FROM Test AS t
INNER JOIN Country AS c
ON c.Id = t.CountryId;
But you have now normalised your countries, so America only appears once. Obviously in a simple example this only saves you one row, but if you have a lot of names, this has saved a lot of duplication, and potential for error. With free type entry (e.g. having a nation column in test) you inevitably end up with multiple variations of everything, e.g. "America", "USA", "U.S.A", "US", "U.S", "United States", this doesn't even consider typos! All of this leads to headaches down the road.
Full Example on SQL Fiddle
*N.B. There's a pretty good argument that the country table should not have a surrogate primary key (AUTO_INCREMENT) and it should instead use the ISO country code. The natural vs surrogate keys debate has been going on for years and years, and is well beyond the scope of this answer
If I understood your question correctly, you want something like this
Then you need to write your query like this
Select test.*, country.nation from test join country on test.id=country.id;
Now, you will only receive the id from your test table
Okay I honestly think my title is incorrect, however that's how i interpreted my problem.
Here are my sample data:
id diagnosis symptom
1 a c
2 a a
3 a b
6 b a
7 b c
My problem is how am I able to retrieve the diagnosis with only the condition inside the in clause?
Here is my query:
SELECT diagnosis
FROM vdoc_b_symptom
WHERE symptom IN ('a','c')
GROUP BY diagnosis
HAVING COUNT(*) = 2;
The output i want to get is only b, however it gave me two outputs. How can i get the a value only from the column "diagnosis" with the conditions for symptom a and c?
I'm sorry for my English it was so bad.
You can try to use condition in HAVING
Schema (MySQL v5.6)
CREATE TABLE vdoc_b_symptom(
diagnosis varchar(5),
symptom varchar(5)
);
insert into vdoc_b_symptom values ('a','a');
insert into vdoc_b_symptom values ('a','b');
insert into vdoc_b_symptom values ('a','c');
insert into vdoc_b_symptom values ('b','a');
insert into vdoc_b_symptom values ('b','c');
Query #1
SELECT
diagnosis
FROM
vdoc_b_symptom
GROUP BY
diagnosis
HAVING
COUNT(distinct symptom) = 2
AND
SUM(symptom = 'a') > 0
AND
SUM(symptom = 'c') > 0;
| diagnosis |
| --------- |
| b |
View on DB Fiddle
Try the query below.
SELECT DISTINCT diagnosis
FROM vdoc_b_symptom v
WHERE NOT EXISTS (
SELECT 1
FROM vdoc_b_symptom
WHERE diagnosis = v.diagnosis AND symptom NOT IN ('a', 'c')
)
Probably some cleverer way to do this, but the way I understand it, you need the results to 1) have both symptoms 'a 'and 'c' as well as 2) not have any other symptoms. So doing a:
SELECT *
FROM vdoc_b_symptom
WHERE diagnosis IN (SELECT diagnosis
FROM vdoc_b_symptom
WHERE symptom IN ( 'a', 'c' )
GROUP BY diagnosis
HAVING Count(*) = 2)
AND diagnosis NOT IN (SELECT diagnosis
FROM vdoc_b_symptom
WHERE symptom NOT IN ( 'a', 'c' ))
should achieve that. May need to name some tables and qualify the columns, though.
table a
no name
2001 jon
2002 jonny
2003 mik
2004 mike
2005 mikey
2006 tom
2007 tomo
2008 tommy
table b
code name credits courseCode
A2 JAVA 25 wer
A3 php 25 wer
A4 oracle 25 wer
B2 p.e 50 oth
B3 sport 50 oth
C2 r.e 25 rst
C3 science 25 rst
C4 networks 25 rst
table c
studentNumber grade coursecode
2003 68 A2
2003 72 A3
2003 53 A4
2005 48 A2
2005 52 A3
2002 20 A2
2002 30 A3
2002 50 A4
2008 90 B2
2007 73 B2
2007 63 B3
SELECT a.num, a.Fname,
b.courseName, b.cMAXscore, b.cCode, c.stuGrade
FROM a
INNER JOIN c
ON a.no = c.no
INNER JOIN b
ON c.moduleCode = b.cCode
INNER JOIN b
ON SUM(b.cMAXscore) / (c.stuGrade)
AND b.cMAXscore = c.stug=Grade
GROUP BY a.Fname, b.cMAXscore, b.cCode, b.courseName,c.stuGrade
"calculate and display every student name(a.Fname) and their ID number(a.num) along with their grade (c.grade) versus the coursse name(b.courseName) and the courses max score(b.cMAXscoure). "
I cant figure out how to divide the MAX by the grade, can someone help?
From the specification, it doesn't look like an aggregate function or a GROUP BY would be necessary. But the specification is ambiguous. There's no table definitions (beyond the unfortunate names and some column references).
Definitions of the tables, along with example data and an example of the desired resultset would go a long ways to removing the ambiguity.
Based on the join predicates in the OP query, I'd suggest something like this query, as a starting point:
SELECT a.Fname
, a.num
, c.grade
, b.courseName
, b.cMAXsource
FROM a
JOIN c
ON c.no = a.no
JOIN b
ON b.cCode = c.moduleCode
ORDER
BY a.Fname
, a.num
, c.grade
, b.courseName
, b.cMAXsource
It seems like that would return the specified result (based on my interpretation of the vague specification.) If that's insufficient i.e. if that doesn't return the desired resultset, then in what way does the desired result differ from the result from this query?
(For more help with your question, I suggest you setup a sqlfiddle example with tables and example data. That will make it easier for someone to help you.)
FOLLOWUP
Based on the additional information provided in the question (table definitions and example data...
To get the maximum (highest) grade for a given course, you could use a query like this:
SELECT MAX(c.grade)
FROM c
WHERE c.coursecode = 'A2'
To get the highest grade for all courses:
SELECT c.coursecode
, MAX(c.grade) AS max_grade
FROM c
GROUP BY c.coursecode
ORDER BY c.coursecode
To match the highest grade for each course to each student grade, use that previous query as an inline view in another query. Something like this:
SELECT g.studentNumber
, g.grade
, g.coursecode
, h.coursecode
, h.highest_grade
FROM c g
JOIN ( SELECT c.coursecode
, MAX(c.grade) AS highest_grade
FROM c
GROUP BY c.coursecode
) h
ON h.coursecode = g.coursecode
To perform a calculation, you can use an expression in the SELECT list of the outer query.
For example, to divide the value of one column by another, you can use the division operator:
SELECT g.studentNumber AS student_number
, g.grade AS student_grade
, g.coursecode AS student_coursecode
, h.coursecode
, h.highest_grade
, g.grade / h.highest_grade AS `student_grade_divided_by_highest_grade`
FROM c g
JOIN ( SELECT c.coursecode
, MAX(c.grade) AS highest_grade
FROM c
GROUP BY c.coursecode
) h
ON h.coursecode = g.coursecode
If you want to also return the name of the student, you can perform a join operation to (the unfortunately named) table a. Assuming that studentnumber is UNIQUE in a :
LEFT
JOIN a
ON a.studentnumber = c.studentnumber
And include a.Fname AS student_first_name in the SELECT list.
If you also need columns from table b, then join that table as well. Assuming that coursecode is UNIQUE in b:
LEFT
JOIN b
ON b.coursecode = g.courscode
Then b.credits can be referenced in an expression in the SELECT list.
Beyond that, you need to be a little more explicit about what result should be returned by the query.
If you are after a "total overall grade" for a student, you'd need to specify how that result should be obtained.
Without knowing table definations it is very hard to provide solution to your problem.
Here is my version of what you are trying to look for:
DECLARE #Student TABLE
(StudentID INT IDENTITY,
FirstName VARCHAR(255),
LastName VARCHAR(255)
);
DECLARE #Course TABLE
(CourseID INT IDENTITY,
CourseCode VARCHAR(25),
CourseName VARCHAR(255),
MaxScore INT
);
DECLARE #Grade TABLE
(ID INT IDENTITY,
CourseID INT,
StudentID INT,
Score INT
);
--Student
insert into #Student(FirstName, LastName)
values ('Test', 'B')
insert into #Student(FirstName, LastName)
values ('Test123', 'K')
--Course
insert into #Course(CourseCode, CourseName, MaxScore)
values ('MAT101', 'MATH',100.00)
insert into #Course(CourseCode, CourseName, MaxScore)
values ('ENG101', 'ENGLISH',100.00)
--Grade
insert into #Grade(CourseID, StudentID, Score)
values (1, 1,93)
insert into #Grade(CourseID, StudentID, Score)
values (1, 1,65)
insert into #Grade(CourseID, StudentID, Score)
values (1, 1,100)
insert into #Grade(CourseID, StudentID, Score)
values (2, 1,100)
insert into #Grade(CourseID, StudentID, Score)
values (2, 1,69)
insert into #Grade(CourseID, StudentID, Score)
values (2, 1,95)
insert into #Grade(CourseID, StudentID, Score)
values (1, 2,100)
insert into #Grade(CourseID, StudentID, Score)
values (1, 2,65)
insert into #Grade(CourseID, StudentID, Score)
values (1, 2,100)
insert into #Grade(CourseID, StudentID, Score)
values (2, 2,100)
insert into #Grade(CourseID, StudentID, Score)
values (2, 2,88)
insert into #Grade(CourseID, StudentID, Score)
values (2, 2,96)
SELECT a.StudentID,
a.FirstName,
a.LastName,
c.CourseCode,
SUM(b.Score) AS 'StudentScore',
SUM(c.MaxScore) AS 'MaxCourseScore',
SUM(CAST(b.Score AS DECIMAL(5, 2))) / SUM(CAST(c.MaxScore AS DECIMAL(5, 2))) AS 'Grade'
FROM #Student a
INNER JOIN #Grade b ON a.StudentID = b.StudentID
INNER JOIN #Course c ON c.CourseID = b.CourseID
GROUP BY a.StudentID,
a.FirstName,
a.LastName,
c.CourseCode;
The problem statement doesn't say anything about dividing by the max, I think you're misunderstanding it.
You need to write a subquery that gets the maximum score for each class, using MAX and GROUP BY. You can then join this with the other tables.
SELECT s.name AS student_name, c.name AS course_name, g.grade, m.max_grade
FROM student AS s
JOIN grade AS g ON s.no = g.studentNumber
JOIN course AS c ON c.code = g.courseCode
JOIN (SELECT courseCode, MAX(grade) AS max_grade
FROM grade
GROUP BY courseCode) AS m
ON m.courseCode = c.courseCode
If you did need to divide the grade by the maximum, you can use g.grade/m.max_grade.