I want to Join to table. the condition is I want to only join those rows which have only one row to match. eg.
books:
id | name | price
1 | book1 | 19
2 | book2 | 19
3 | book3 | 30
price_offer:
id | offer | price
1 | offer1 | 19
2 | offer2 | 30
so now if I do select query on these table:
SELECT * FROM price_offer
JOIN books ON price_offer.price = books.price
I only want to join book with id 3 as it have only one match with price_offer table.
You could use a self join for books table to pick a book with only single match
select po.*, b1.*
from price_offer po
join books b1 on po.price = b1.price
join (
select price,max(id) id
from books
group by price
having count(*) = 1
) b2 on b1.id = b2.id
Demo
Try following query:
Sample data:
create table books(id int, name varchar(10), price int);
insert into books values
(1, 'book1', 19),
(2, 'book2', 19),
(3, 'book3', 30);
create table price_offer(id int, offer varchar(10), price int);
insert into price_offer values
(1, 'offer1', 19),
(2, 'offer2', 30);
Query:
select max(b.id)
from price_offer p
left join books b on b.price = p.price
where p.id is not null
group by b.price
having count(*) = 1;
If you want to avoid nesting queries where you have to use self-joins, you can use window-functions of MySQL 8.0.11, which are exactly for cases like this
Related
I need to get all rows that are in the table A, but joining with the table B (basically a LEFT JOIN), but also, I need to get the A table row itself, for example, with these tables:
Table A:
id
name
1
Random name
2
Random name #2
Table B:
id
parent_id
location
1
2
Location #1
2
2
Location #2
With this query:
SELECT * FROM A
LEFT JOIN B
ON A.id = B.parent_id;
I get something like this:
id
name
id
parent_id
location
1
Random name
NULL
NULL
NULL
2
Random name #2
1
2
Location #1
2
Random name #2
2
2
Location #2
But I want to get something like this:
id
name
id
parent_id
location
1
Random name
NULL
NULL
NULL
2
Random name #2
NULL
NULL
NULL
2
Random name #2
1
2
Location #1
2
Random name #2
2
2
Location #2
As you can see, there is a row by itself of "Random name #2" separated from its joins, how can I do that?
The main idea is that there are an ads table (the table A), but also, there are a subads table (the table B) with little variations of the ads table, and I need to show all ads and subads in a unique query.
Tanks a lot!
Two suggestions:
SELECT * FROM A
INNER JOIN B
ON A.id = B.parent_id
UNION ALL
SELECT *, NULL, NULL, NULL FROM A
or
SELECT A.*,B.*
FROM (SELECT 1 A_ONLY UNION ALL SELECT 0) A_ONLY
CROSS JOIN A
LEFT JOIN B
ON A.id = B.parent_id AND NOT A_ONLY
WHERE A_ONLY OR B.parent_id
The latter is an approach you can use to emulate WITH ROLLUP when that isn't allowed or when you want something slightly different than that produces (here, avoiding a grand total record and avoiding a double record when there are no B rows).
Probably not the best implementation, but until someone comes up with a proper solution...
SELECT A.id, name, B.id, parent_id, location FROM A
LEFT JOIN B
ON A.id = B.parent_id;
UNION ALL
SELECT A.id, name, NULL as id, NULL as parent_id, NULL as location FROM A
WHERE A.id IN (SELECT parent_id FROM B)
Simply UNION ALL with another query taking the values from A that had matches on B, hence no NULL values from the first query.
you need only the NULL added rows from A and the rest of the inner JOIN
CREATE TABLE A
(`id` int, `name` varchar(14))
;
INSERT INTO A
(`id`, `name`)
VALUES
(1, 'Random name'),
(2, 'Random name #2')
;
CREATE TABLE B
(`id` int, `parent_id` int, `location` varchar(11))
;
INSERT INTO B
(`id`, `parent_id`, `location`)
VALUES
(1, 2, 'Location #1'),
(2, 2, 'Location #2')
;
(SELECT A.id as a_id,A.name,B.* FROM A
INNER JOIN B
ON A.id = B.parent_id)
UNION
(SELECT A.*,NULL,NULL,NULL FROM A)
ORDER by a_id,id;
a_id | name | id | parent_id | location
---: | :------------- | ---: | --------: | :----------
1 | Random name | null | null | null
2 | Random name #2 | null | null | null
2 | Random name #2 | 1 | 2 | Location #1
2 | Random name #2 | 2 | 2 | Location #2
db<>fiddle here
You can make INNER JOIN instead of LEFT JOIN and UNION ALL with table A content:
Both queries must return the same number of columns.
SELECT *, NULL, NULL, NULL
FROM A
UNION ALL
SELECT *
FROM A
INNER JOIN B ON A.id = B.parent_id;
I am trying to write a simple query that will display all projects and their total number of team members, sorted alphabetically by project. If a project does not have assigned team members, that project should still be included in the output.
CREATE TABLE Project ( ID INT IDENTITY(1,1), ProjectName VARCHAR(50), DueDate
DATE)
CREATE TABLE Employee ( ID INT IDENTITY(1,1), EmployeeName VARCHAR(50) )
CREATE TABLE ProjectAssignment ( ID INT IDENTITY(1,1), ProjectID INT,
EmployeeID INT)
INSERT INTO Project VALUES ('Alpha', '1/1/2040'), ('Bravo', '3/1/2030'),
('Charlie', '2/1/2017'), ('Delta', '4/1/2017')
INSERT INTO Employee VALUES ('John'), ('Beth'), ('Tom'), ('Kim'), ('Jack')
INSERT INTO ProjectAssignment VALUES (1, 1), (1, 2), (2, 2), (2, 3), (3,
3), (3, 4), (1, 3)
--TABLE Project:
ID ProjectName DueDate
1 Alpha 2040-01-01
2 Bravo 2030-03-01
3 Charlie 2017-02-01
4 Delta 2017-04-01
--TABLE Employee:
ID EmployeeName
1 John
2 Beth
3 Tom
4 Kim
5 Jack
--TABLE ProjectAssignment:
ID ProjectID EmployeeID
1 1 1
2 1 2
3 2 2
4 2 3
5 3 3
6 3 4
7 1 3
Here is my wrong query:
SELECT n.ProjectName, Count(t.ProjectID) as NumMembers
FROM Project p
LEFT JOIN ProjectAssignment t ON p.EmployeeID = t.EmployeeID
LEFT JOIN employee e ON e.ProjectID = t.ProjectID
GROUP BY n.Project
ORDER BY n.Project
Desired Result:
| ProjectName | NumMembers |
+-------------+-------------+
| Alpha | 3 |
| Bravo | 2 |
| Charlie | 2 |
| Delta | null |
Please try this Mysql query. This will resolve your issue. We dont' require employee table join. If you are not taking any data from employee table then don't add employee table in join.
SELECT
p.name AS ProjectName,
Count( t.employeeID ) AS NumMembers
FROM
Project p
LEFT JOIN ProjectAssignment t ON p.id = t.projectID
GROUP BY
p.name
Output:
Project name NumMembers
Alpha 3
Bravo 2
Charlie 2
Delta 0
Few things
LEFT JOIN ProjectAssignment t ON p.EmployeeID = t.EmployeeID
is wrong as you can see p.EmployeeID do not exists on table Project
your join to employee is not needed at all
also finally i do not know how your fields names are. you post Name in sample data and projectName is used in your query and ddl (changed to your DDL instead of your query)
SELECT p.ProjectName, Count(DISTINCT pa.EmployeeID) as NumMembers
FROM Project p
LEFT JOIN ProjectAssignment pa ON p.ID = pa.ProjectID
GROUP BY p.ID, p.ProjectName
ORDER BY p.ProjectName
http://sqlfiddle.com/#!18/36df5/1
SELECT n.ProjectName, Count(t.ProjectID) as NumMembers FROM Project n LEFT JOIN ProjectAssignment t ON n.id = t.ProjectID GROUP BY t.ProjectID ORDER BY n.ProjectName
run this query
Just use this ( it seems join conditions are mixed ):
SELECT p.ProjectName, Count(t.ProjectID) as NumMembers
FROM Project p
LEFT JOIN ProjectAssignment t ON p.ID = t.ProjectID
LEFT JOIN employee e ON t.EmployeeID = e.ID
GROUP BY p.ProjectName
ORDER BY p.ProjectName;
ProjectName NumMembers
Alpha 3
Bravo 2
Charlie 2
Delta 0
SQL Fiddle Demo
Here's the SQLFiddle with schema and data.
I'm trying to sum 2 columns, one at parent level and the other at child level.
The current query I'm using gives me the right sum amount on child level, but doubles up the amount on parent level, due to another 1-many relationship involved on the child level.
Ugh... that's a terrible explanation - here's the English version:
Joe the salesman is involved in 2 sales.
For the 1st sale, he get's 2 sets of commissions, based on 2 different commission types. I'm trying to show Joe's total sale value, alongside the total value of his applicable splits. The split value total is fine, but sale value get's doubled up because I'm obviously, grouping/joining incorrectly (see the last example below).
This is fine:
select sp.person_name, pr.description,
sum(spl.split) as SplitValue
from sale s, product pr, sales_person sp, sales_split spl
where s.product_id = pr.id
and s.id = spl.sale_id
and sp.id = spl.sales_person_id
group by sp.id;
person_name | description | SplitValue
----------- ----------- | ----------
Joe | Widget 1 | 50
Sam | Widget 1 | 10
This is also yields the correct split and sale values, but now 3 rows are displayed for Joe (i.e 2nd row is a duplicate of the 1st one) - I only want to display Joe's "Widget 1" sale once, so not correct:
select sp.person_name, pr.description,
sum(s.sale_value) as SaleValue, sum(spl.split) as SplitValue
from sale s, product pr, sales_person sp, sales_split spl, sales_split_agreement ssa
where s.id = spl.sale_id
and s.product_id = pr.id
and sp.id = spl.sales_person_id
and sp.id = ssa.sales_person_id
and spl.sales_person_id = ssa.sales_person_id
and ssa.id = spl.sales_split_agreement_id
group by sp.id, spl.id;
person_name | description | SplitValue | SaleValue
----------- ----------- ---------- ---------
Joe | Widget 1 | 10 | 20
Joe | Widget 1 | 10 | 20
Joe | Widget 2 | 30 | 30
Sam | Widget 1 | 10 | 20
Now the duplicated row is gone, but Joe's SaleValue is incorrect - it should be 50, not 70:
select sp.person_name, pr.description,
sum(spl.split) as SplitValue, sum(s.sale_value) as SaleValue
from sale s, product pr, sales_person sp, sales_split spl, sales_split_agreement ssa
where s.id = spl.sale_id
and s.product_id = pr.id
and sp.id = spl.sales_person_id
and sp.id = ssa.sales_person_id
and spl.sales_person_id = ssa.sales_person_id
and ssa.id = spl.sales_split_agreement_id
group by sp.id;
person_name | description | SplitValue | SaleValue
----------- ----------- --------- ----------
Joe | Widget 1 | 50 | 70
Sam | Widget 1 | 10 | 20
I.e. I'm after the query that will yield this result (i.e. Joe's correct SaleValue of 50):
person_name | description | SplitValue | SaleValue
----------- ----------- --------- ----------
Joe | Widget 1 | 50 | 50
Sam | Widget 1 | 10 | 20
Any help will be greatly appreciated!
UPDATE 1:
For clarity - here's the schema and test data from the fiddle:
CREATE TABLE product
(`id` int, `description` varchar(12))
;
INSERT INTO product
(`id`, `description`)
VALUES
(1, 'Widget 1'),
(2, 'Widget 2')
;
CREATE TABLE sales_person
(`id` int, `person_name` varchar(7))
;
INSERT INTO sales_person
(`id`, `person_name`)
VALUES
(1, 'Joe'),
(2, 'Sam')
;
CREATE TABLE sale
(`id` int, `product_id` int, `sale_value` int)
;
INSERT INTO sale
(`id`, `product_id`, `sale_value`)
VALUES
(1, 1, 20.00),
(2, 2, 30.00)
;
CREATE TABLE split_type
(`id` int, `description` varchar(6))
;
INSERT INTO split_type
(`id`, `description`)
VALUES
(1, 'Type 1'),
(2, 'Type 2')
;
CREATE TABLE sales_split_agreement
(`id` int, `sales_person_id` int, `split_type_id` int, `percentage` int)
;
INSERT INTO sales_split_agreement
(`id`, `sales_person_id`, `split_type_id`, `percentage`)
VALUES
(1, 1, 1, 50),
(2, 1, 2, 50),
(3, 2, 1, 50),
(4, 1, 1, 100)
;
CREATE TABLE sales_split
(`id` int, `sale_id` int, `sales_split_agreement_id` int, `sales_person_id` int, `split` int )
;
INSERT INTO sales_split
(`id`, `sale_id`, `sales_split_agreement_id`, `sales_person_id`, `split`)
VALUES
(1, 1, 1, 1, 10),
(2, 1, 2, 1, 10),
(3, 1, 3, 2, 10),
(4, 2, 4, 1, 30)
;
I think you were on to the right track, but I decided to restart and approach from the beginning. Getting the SplitValue for each person does not require all those tables. In fact, all you need are sales_split and sales_person, like this:
SELECT sp.person_name, SUM(ss.split) AS SplitValue
FROM sales_person sp
JOIN sales_split ss ON sp.id = ss.sales_person_id
GROUP BY sp.id;
Similarly, you can get the total sale value for each person with a join between sale, sales_split, and sales_person:
SELECT sp.person_name, SUM(s.sale_value) AS SaleValue
FROM sale s
JOIN sales_split ss ON ss.sale_id = s.id
JOIN sales_person sp ON sp.id = ss.sales_person_id
GROUP BY sp.id;
At this point, I realize you have an error in your expected results (for this data set). Joe does in fact have a sale value of 70, because sale id 1 (value 20), 2 (value 20), and 4 (value 30) add up to 70. However, I still think this query will help you out more than the one you have.
At this point, you can get the values for each sales_person_id by joining those two subqueries to the sales_person table. I took out the join to sales_person in the subqueries, as it became irrelevant now. It even makes the subqueries a little cleaner:
SELECT sp.person_name, COALESCE(t1.SplitValue, 0) AS SplitValue, COALESCE(t2.SaleValue, 0) AS SaleValue
FROM sales_person sp
LEFT JOIN(
SELECT ss.sales_person_id, SUM(ss.split) AS SplitValue
FROM sales_split ss
GROUP BY ss.sales_person_id) t1 ON t1.sales_person_id = sp.id
LEFT JOIN(
SELECT ss.sales_person_id, SUM(s.sale_value) AS SaleValue
FROM sale s
JOIN sales_split ss ON ss.sale_id = s.id
GROUP BY ss.sales_person_id) t2 ON t2.sales_person_id = sp.id;
Here is an SQL Fiddle example.
EDIT: I understand now why Joe's actual sale price is 50, because he split twice on sale id 1. To work around this, I first got a list of distinct sales for each sales_person like this:
SELECT DISTINCT sale_id, sales_person_id
FROM sales_split;
This way, there is only one row for sales_person_id = 1 and sale_id = 1. Then, it was easy enough to join that to the sale table and get the proper sales value for each sales_person:
SELECT t.sales_person_id, SUM(s.sale_value) AS SaleValue
FROM(
SELECT DISTINCT sale_id, sales_person_id
FROM sales_split) t
JOIN sale s ON s.id = t.sale_id
GROUP BY t.sales_person_id;
The rest of my answer above still fits. I wrote one query to get SplitValue, and one query to get SaleValue, and I joined them together. So, all I have to do now is replace the subquery I just gave you, with the incorrect subquery from further up:
SELECT sp.person_name, COALESCE(t1.SplitValue, 0) AS SplitValue, COALESCE(t2.SaleValue, 0) AS SaleValue
FROM sales_person sp
LEFT JOIN(
SELECT ss.sales_person_id, SUM(ss.split) AS SplitValue
FROM sales_split ss
GROUP BY ss.sales_person_id) t1 ON t1.sales_person_id = sp.id
LEFT JOIN(
SELECT t.sales_person_id, SUM(s.sale_value) AS SaleValue
FROM(
SELECT DISTINCT sale_id, sales_person_id
FROM sales_split) t
JOIN sale s ON s.id = t.sale_id
GROUP BY t.sales_person_id) t2 ON t2.sales_person_id = sp.id;
Here is the updated SQL Fiddle.
You mentioned in the comments that you shortened your data for brevity, which is fine. I am leaving my joins as they are, and I trust that it gives you enough direction that you can adjust them accordingly to match your proper structure.
I have one table called Employee that contains the following information like
ID Name Skills
1 xyz java,php,dotnet
2 abc ruby,java,python
Skills column saves comma seprated values. it could be one or more.
I want to design a query based on OR operate.When user search java, Database displays two employees likes xyz, abc.
I have tried this query but no result comes out:
SELECT m
FROM Employee m
Where m.Skills LIKE '%JAVA% MS PAINT%'
Any Suggestion?
Ideally you should not store the data in a comma-separated list. You should create a join table between the employees and the skills:
CREATE TABLE employees (`e_id` int, `e_name` varchar(3));
INSERT INTO employees (`e_id`, `e_name`)
VALUES
(1, 'xyz'),
(2, 'abc');
CREATE TABLE skills (`s_id` int, `s_name` varchar(6));
INSERT INTO skills (`s_id`, `s_name`)
VALUES
(1, 'java'),
(2, 'php'),
(3, 'dotnet'),
(4, 'ruby'),
(5, 'python');
CREATE TABLE employees_skills (`e_d` int, `s_id` int);
INSERT INTO employees_skills
(`e_d`, `s_id`)
VALUES
(1, 1),
(1, 2),
(1, 3),
(2, 4),
(2, 1),
(2, 5);
Then when you want to select from the tables you will use:
select *
from employees e
inner join employees_skills es
on e.e_id = es.e_id
inner join skills s
on es.s_is = s.s_id
where s.s_name in ('java', 'ruby')
Or you can use the OR clause:
select *
from employees e
inner join employees_skills es
on e.e_id = es.e_id
inner join skills s
on es.s_is = s.s_id
where s.s_name = 'java'
or s.s_name = 'ruby'
use like not good solution. Full scan and slow query.
Create new table with catalog of skills.
Create table user_skills
You should set up your tables like this:
Employee:
ID | Name
---+------
1 | xyz
2 | abc
Skill:
ID | Name
---+------
1 | java
2 | php
3 | dotnet
4 | ruby
5 | python
EmployeeSkills:
ID | EmployeeID | SkillID
---+------------+----------
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 2 | 4
5 | 2 | 1
6 | 2 | 5
the query to find employees with skills in java would look like this
SELECT
E.Name
FROM
Employee AS E
INNER JOIN
EmployeeSkill AS ES
ON
ES.EmployeeID = E.ID
INNER JOIN
Skill AS S
ON
ES.SkillID = S.ID
WHERE
S.Name = 'java'
select name from table where skill like '%java%' should do
I have two tables in my SQL
For example Table1 - ItemPrice:
DATETIME | ITEM | PRICE
2011-08-28 | ABC 123
2011-09-01 | ABC 125
2011-09-02 | ABC 124
2011-09-03 | ABC 127
2011-09-04 | ABC 126
Table2 - DayScore:
DATETIME | ITEM | SCORE
2011-08-28 | ABC 1
2011-08-29 | ABC 8
2011-09-01 | ABC 4
2011-09-02 | ABC 2
2011-09-03 | ABC 7
2011-09-04 | ABC 3
I want to write a query, which given a item ID (e.g. ABC), will return the price at that date from ItemPrice (of there is no price for that date then the query should not return anything). If a valid price is found for the query date, the query should return (in 9 columns)
the price of the item from ItemPrice for the past three days (i.e. the most recent 3 prices before the date queried).
In the next three columns it should return, from DayScore, the matching score for those 3 dates selected from ItemPrice.
Finally the dates (t-1 to t-3) selected
In otherwords the results for this query looking at just date='2011-09-03' as an example for item='abc' would return:
DATE | ITEM | PRICE | SCR | PRC_t-1 | PRC_t-2 | PRC_t-3 | SCR_t-1 | SCR_t-2 | SCR_t-3 | DATE_t-1 | DATE_t-2 | DATE_t-3
2011-09-03| ABC | 127 | 7 | 124 | 125 | 123 | 2 | 4 | 1 | 2011-09-02| 2011-09-01| 2011-08-28
....
Etc for each date that appears in ItemPrice table.
What is the neatest and most efficient way to run this query (as its something that will be run over many millions of rows)?
Cheers!
Pretty no but it does produce the results. You could probably get rid of some subselects and make it a bit less sql but I tried to build it up in steps so you can deduct what it is doing.
The core part is this select:
SELECT
Sub2.*
, (Select MAX(IP3.DateTime) FROM ItemPrice IP3 where IP3.DateTime < T_2) AS T_3
FROM
(SELECT
Sub1.*
, (Select MAX(IP2.DateTime) FROM ItemPrice IP2 where IP2.DateTime < T_1) AS T_2
FROM
(SELECT
ItemPrice.DateTime
, (Select MAX(IP.DateTime) FROM ItemPrice IP where IP.DateTime < ItemPrice.DateTime) AS T_1
From ItemPrice) Sub1
) Sub2
This returns a table with the dates (now, t-1, t-2, t-3). From there is is simple joining with price and score for each of those dates. The whole things including testdata the becomes this bulk of sql
/*
CREATE TABLE ItemPrice (datetime Date, item varchar(3), price int);
CREATE TABLE DayScore ( datetime Date, item varchar(3), score int);
INSERT INTO ItemPrice VALUES ('20110828', 'ABC', 123);
INSERT INTO ItemPrice VALUES ('20110901', 'ABC', 125);
INSERT INTO ItemPrice VALUES ('20110902', 'ABC', 124);
INSERT INTO ItemPrice VALUES ('20110903', 'ABC', 127);
INSERT INTO ItemPrice VALUES ('20110904', 'ABC', 126);
INSERT INTO DayScore VALUES ('20110828', 'ABC', 1);
INSERT INTO DayScore VALUES ('20110829', 'ABC', 8);
INSERT INTO DayScore VALUES ('20110901', 'ABC', 4);
INSERT INTO DayScore VALUES ('20110902', 'ABC', 2);
INSERT INTO DayScore VALUES ('20110903', 'ABC', 7);
INSERT INTO DayScore VALUES ('20110904', 'ABC', 3);
*/
SELECT Hist.*, Current.Item, Current.Price, Current.Score
, Minus1.Price as PRC_1, Minus1.Score SCR_1
, Minus2.Price as PRC_2, Minus2.Score SCR_2
, Minus3.Price as PRC_3, Minus3.Score SCR_3
FROM
(SELECT Sub2.*, (Select MAX(IP3.DateTime) FROM ItemPrice IP3 where IP3.DateTime < T_2) AS T_3
FROM
(SELECT Sub1.*, (Select MAX(IP2.DateTime) FROM ItemPrice IP2 where IP2.DateTime < T_1) AS T_2
FROM
(SELECT ItemPrice.DateTime, (Select MAX(IP.DateTime) FROM ItemPrice IP where IP.DateTime < ItemPrice.DateTime) AS T_1 From ItemPrice) Sub1) Sub2) Hist
INNER JOIN
(SELECT ItemPrice.DateTime, ItemPrice.Item, ItemPrice.Price, DayScore.Score FROM ItemPrice INNER JOIN DayScore ON (ItemPrice.Item = DayScore.Item AND ItemPrice.Datetime = DayScore.DateTime)) CURRENT
ON (Current.DateTime = Hist.DateTime)
LEFT JOIN
(SELECT ItemPrice.DateTime, ItemPrice.Price, DayScore.Score FROM ItemPrice INNER JOIN DayScore ON (ItemPrice.Item = DayScore.Item AND ItemPrice.Datetime = DayScore.DateTime)) MINUS1
ON (Minus1.DateTime = Hist.T_1)
LEFT JOIN
(SELECT ItemPrice.DateTime, ItemPrice.Price, DayScore.Score FROM ItemPrice INNER JOIN DayScore ON (ItemPrice.Item = DayScore.Item AND ItemPrice.Datetime = DayScore.DateTime)) MINUS2
ON (Minus2.DateTime = Hist.T_2)
LEFT JOIN
(SELECT ItemPrice.DateTime, ItemPrice.Price, DayScore.Score FROM ItemPrice INNER JOIN DayScore ON (ItemPrice.Item = DayScore.Item AND ItemPrice.Datetime = DayScore.DateTime)) MINUS3
ON (Minus3.DateTime = Hist.T_3)
WHERE Current.Item = 'ABC'
;
/*
DROP TABLE ItemPrice;
DROP TABLE DayScore;
*/
I'm curious about your explain plan when you do this on 1M rows :) It might not even be that horrible if you have the right indexes which you probably do.