This query:
SELECT contacts.name, accounts.account
FROM contacts
LEFT JOIN deals
ON contacts.id = deals.contact_id
LEFT JOIN
accounts ON accounts.deal_id = deals.id;
returns:
+------+-------------------+
| name | account |
+------+-------------------+
| Bob | fun deal account |
| Bob | NULL |
| John | NULL |
+------+-------------------+
But I expected:
+------+-------------------+
| name | account |
+------+-------------------+
| Bob | fun deal account |
| Bob | fun deal account |
| John | NULL |
+------+-------------------+
The first LEFT JOIN behaves correctly. Since there are two deals for Bob, Bob correctly shows up twice in result set. But the second LEFT JOIN does not behave right, because the account should have been carried over twice for both Bob records, but instead there is a NULL for the second bob.
The schema:
CREATE TABLE contacts(
id int AUTO_INCREMENT,
name VARCHAR(50),
Primary Key(id)
)
INSERT INTO contacts VALUES('Bob');
INSERT INTO contacts(name) VALUES('John');
CREATE TABLE deals(
id int AUTO_INCREMENT,
name VARCHAR(20),
contact_id int,
FOREIGN KEY(contact_id) REFERENCES contacts(id),
Primary Key(id)
);
INSERT INTO deals(name, contact_id) VALUES('cool deal',1);
INSERT INTO deals(name, contact_id) VALUES('another cool deal',1);
CREATE TABLE accounts(
id int AUTO_INCREMENT,
account VARCHAR(50),
deal_id int,
FOREIGN KEY(deal_id) REFERENCES deals(id),
PRIMARY KEY (id)
)
INSERT INTO accounts(account, deal_id) VALUES('fun deal account', 1);
Why doesn't the second LEFT JOIN give desired behavior and how can I get the 'fun deal account' account to show up for both Bobs?
Bob have two deals but deals.id is auto_increment so fun deal account only match the first row in deals table, the cool deal.
You need to add INSERT INTO accounts(account, deal_id) VALUES('fun deal account', 2); too
In case of doubts, decompose your query.
The first LEFT JOIN could be this:
SELECT contacts.id as contact_id, contacts.name, deals.id as deals_id, deals.name
FROM contacts
LEFT JOIN deals ON contacts.id = deals.contact_id
Which results in :
contact_id name deals_id name
1 Bob 1 cool deal
1 Bob 2 another cool deal
2 John NULL NULL
The second LEFT JOIN is:
LEFT JOIN accounts ON accounts.deal_id = deals.id
So the result given is logical given your data, you have only one account with deal_id=1 so it matches the first row where deals.id=1 : "cool deal" .
I think your mistake is on the last part of your query, the query you wanted is :
SELECT contacts.name, accounts.account FROM contacts LEFT JOIN deals ON contacts.id = deals.contact_id LEFT JOIN accounts ON accounts.deal_id = deals.contact_id
"accounts.deal_id = deals.contact_id" instead of "accounts.deal_id = deals.id" is the deal (pun intended) to have your expected result.
Related
I have a database with two tables one table (shops) has an admin user column and the other a user with less privileges. I plan to LEFT JOIN the table of the user with less privileges. When I retrieve the data, the records for the admin user must be on a separate row and must have NULL values for the left joined table followed by records of users with less privileges (records of the left joined table) if any. I am using MySQL.
I have looked into the UNION commands but I don't think it can help. Please see the results bellow of what I need.
Thank you.
SELECT *
FROM shops LEFT JOIN users USING(shop_id)
WHERE shop_id = 1 AND (admin_id = 1 OR user_id = 1);
+---------+----------+---------+
| shop_id | admin_id | user_id |
+---------+----------+---------+
| 1 | 1 | NULL | <-- Need this one extra record
| 1 | 1 | 1 |
| 1 | 1 | 2 |
| 1 | 1 | 3 |
+---------+----------+---------+
Here is an example structure of the databases and some sample data:
CREATE SCHEMA test DEFAULT CHARACTER SET utf8 ;
USE test;
CREATE TABLE admin(
admin_id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY(admin_id)
);
CREATE TABLE shops(
shop_id INT NOT NULL AUTO_INCREMENT,
admin_id INT NOT NULL,
PRIMARY KEY(shop_id),
CONSTRAINT fk_shop_admin FOREIGN KEY(admin_id) REFERENCES admin (admin_id)
);
CREATE TABLE users(
user_id INT NOT NULL AUTO_INCREMENT,
shop_id INT NOT NULL,
CONSTRAINT fk_user_shop FOREIGN KEY(shop_id) REFERENCES admin (shop_id)
);
-- Sample data
INSERT INTO admin() VALUES ();
INSERT INTO shops(admin_id) VALUES (1);
INSERT INTO users(shop_id) VALUES (1),(1),(1);
I think you need union all:
select s.shop_id, s.admin_id, null as user_id
from shops s
where s.shop_id = 1
union all
select s.shop_id, s.admin_id, u.user_id
from shops s join
users u
on s.shop_id = u.shop_id
where shop_id = 1;
Put your where condition in On clause
SELECT *
FROM shops LEFT JOIN users on shops.shop_id=users.shop_id and (admin_id = 1 OR user_id = 1)
WHERE shops.shop_id = 1
I've these 3 tables:
___Invoices:
|--------|---------------|
| INV_Id | IVC_BookingId |
|--------|---------------|
| 10 | 31 |
|--------|---------------|
___Bookings:
|--------|-------------|---------------|---------------|
| BOO_Id | BOO_GuestId | BOO_CompanyId | BOO_BillingId |
|--------|-------------|---------------|---------------|
| 10 | 89 90 | 0 |
|--------|-------------|---------------|---------------|
___Kardex:
|--------|----------|-------------|-------------|
| KDX_Id | KDX_Type | KDX_Name | KDX_Company |
|--------|----------|-------------|-------------|
| 89 | guest | Frank | |
| 90 | company | | Google |
|--------|----------|-------------|-------------|
I would like to find for an Invoice the linked card user.
For example, for INV_Id = 10, it should return me:
|--------|-------|---------|---------|
| INV_Id | guest | company | billing |
|--------|-------|---------|---------|
| 10 | Frank | Google | 0 |
|--------|-------|---------|---------|
billing is actually empty because I do not have any existing like between my invoice and booking and kardex.
So my try is the following:
SELECT IVC_Id, IVC_BookingId, IFNULL(BOO_GuestId, 0) AS BOO_GuestId, IFNULL(BOO_CompanyId, 0) AS BOO_CompanyId, IFNULL(BOO_BillingId, 0) AS BOO_BillingId, KDX_Name, KDX_Company
FROM ___Invoices
JOIN ___Bookings
ON ___Bookings.BOO_Id = ___Invoices.IVC_BookingId
LEFT JOIN ___Kardex
ON ___Kardex.KDX_Id = ___Bookings.BOO_BillingId
WHERE IVC_Id='10'
I've no error but I can get the name or company from the ___Kardex table.
Do you know why please ?
Here the SQL Fiddle: http://sqlfiddle.com/#!9/211d01/1
Thanks.
I'm not too sure about your relationships or foreign keys. But from what you have given, I think this would do. Might need slight modifications, but I guess you will get the idea.
SELECT ___Invoices.IVC_Id, k1.KDX_Name as guest, k2.KDX_Company as company
FROM ___Invoices
JOIN ___Bookings
ON ___Bookings.BOO_Id = ___Invoices.IVC_BookingId
LEFT JOIN ___Kardex k1 ON ___Bookings.BOO_GuestId = k1.KDX_Id
LEFT JOIN ___Kardex k2 ON ___Bookings.BOO_CompanyId = k2.KDX_Id
WHERE ___Invoices.IVC_Id='10'
Please check this and let me know if it's what you wanted.
http://sqlfiddle.com/#!9/211d01/31
Feel free to ask if you have any doubts. Hope it helps :)
Please check your second JOIN query:
LEFT JOIN ___Kardex
ON ___Kardex.KDX_Id = ___Bookings.BOO_BillingId
It should be
LEFT JOIN ___Kardex
ON ___Kardex.KDX_Id = ___Bookings.BOO_CompanyId
SQL Fiddle: http://sqlfiddle.com/#!9/211d01/7
You are joining to __Kardex on a BillingID of 0.
0 Does not equal 89 or 90
Your code
LEFT JOIN ___Kardex
ON ___Kardex.KDX_Id = ___Bookings.BOO_BillingId
Here is how I solved: I joined to the same table twice with a different join criteria each time. And then created a new alias for that join:
CREATE TABLE `inv` (`INV_ID` VARCHAR(255) NOT NULL, `IVC_BookingID`
VARCHAR(255) NOT NULL);
CREATE TABLE `bookings` (`BOO_id` VARCHAR(255) NOT NULL, `BOO_guestid` VARCHAR(255) NOT NULL, `BOO_CompanyID` VARCHAR(255) NOT NULL, `BOO_BillingID` VARCHAR(255) NOT NULL);
CREATE TABLE `kardex` (`KDX_ID` VARCHAR(255) NOT NULL, `KDX_TYPE` VARCHAR(255) NOT NULL, `KDX_NAME` VARCHAR(255) NOT NULL, `KDX_COMPANY` VARCHAR(255) NOT NULL);
INSERT INTO `inv` (INV_ID,IVC_BookingID) SELECT '10','31';
INSERT INTO `bookings` (BOO_id,BOO_guestid,BOO_CompanyID,BOO_BillingID) SELECT '10','89','90','0';
INSERT INTO `kardex` (KDX_ID,KDX_TYPE,KDX_NAME,KDX_COMPANY) SELECT '89','guest','Frank','';
INSERT INTO `kardex` (KDX_ID,KDX_TYPE,KDX_NAME,KDX_COMPANY) SELECT '90','company','','Google';
SELECT a.INV_ID, c.kdx_name, d.kdx_company FROM inv a
INNER JOIN bookings b ON b.boo_id=a.INV_ID
INNER JOIN kardex c ON c.kdx_id=b.boo_guestid
INNER JOIN kardex d ON d.kdx_id=b.BOO_CompanyID
WHERE INV_ID='10';
RESULT
INV_ID kdx_name kdx_company
10 Frank Google
You can try
SELECT IVC_Id,
(SELECT KDX_Name FROM ___Kardex WHERE KDX_Id= ___Bookings.BOO_GuestId) AS guest,
(SELECT KDX_Company FROM ___Kardex WHERE KDX_Id= ___Bookings.BOO_CompanyId) AS company,
___Bookings.BOO_BillingId as billing
FROM ___Invoices
JOIN ___Bookings
ON ___Bookings.BOO_Id = ___Invoices.IVC_BookingId
WHERE IVC_Id='10'
It probably not the most optimal solution if your concern is performance, but in you schema design you should not have one table handling more then one type of information, this makes it difficult to write any legit joins.
I have a database of people and projects. How can I find the names of people who collaborated with a given person, and on how many projects?
For example, I want to find the collaborators of Jimmy from the database:
+----------+--------+
| project | person |
+----------+--------+
| datamax | Jimmy |
| datamax | Ashley |
| datamax | Martin |
| cocoplus | Jimmy |
| cocoplus | Ashley |
| glassbox | Jimmy |
| glassbox | Martin |
| powerbin | Jimmy |
| powerbin | Ashley |
+----------+--------+
The result would look something like this:
Jimmy's collaborations:
+--------+----------------+
| person | collaborations |
+--------+----------------+
| Ashley | 3 |
| Martin | 2 |
+--------+----------------+
Join the table with itself, group by the person field:
SELECT u2.person, COUNT(u1.project) AS collaborations
FROM users u1
JOIN users u2 ON u2.project = u1.project
WHERE u1.person != u2.person AND u1.person = 'Jimmy'
GROUP BY u2.person;
The query selects the projects in which Jimmy participated from u1. The rows from u2 are filtered by the rows from u1. Duplicate entries, where the users from both tables match, are filtered with WHERE clause. Finally, the result set is grouped by person, and the COUNT function calculates the number of rows per group.
Performance
Note, an index for person and project columns (or two separate indexes) will significantly improve performance of the query above. Specific index configuration depends on the table structure. Although, I think the following is quite enough for a table with two varchar fields for person and project, for instance:
ALTER TABLE users ADD INDEX `project` (`project`(10));
ALTER TABLE users ADD INDEX `person` (`person`(10));
Normalization
However, I would rather store persons and projects in separate tables with their numeric IDs. A third table could play the role of connector: person_id - project_id. In other words, I recommend normalization. With normalized tables, you will not need to build bloated indexes for the text fields.
Normalized tables may look as follows:
CREATE TABLE users (
id int unsigned NOT NULL AUTO_INCREMENT,
name varchar(200) NOT NULL DEFAULT '',
PRIMARY KEY(`id`),
-- This index is needed, if you want to fetch users by names
INDEX name (name(8))
);
CREATE TABLE projects (
id int unsigned NOT NULL AUTO_INCREMENT,
name varchar(100) NOT NULL DEFAULT '',
PRIMARY KEY(`id`)
);
CREATE TABLE collaborations (
project_id int unsigned NOT NULL DEFAULT 0,
user_id int unsigned NOT NULL DEFAULT 0,
PRIMARY KEY(`project_id`, `user_id`)
);
The query for the normalized structures will look a little bit more complex:
-- In practice, the user ID is retrieved from the calling process
-- (such as POST/GET HTTP requests, for instance).
SET #user_id := (SELECT id FROM users WHERE name LIKE 'Jimmy');
SELECT u.name person, COUNT(p.id) collaborations
FROM collaborations c
JOIN collaborations c2 USING(project_id)
JOIN users u ON u.id = c2.user_id
JOIN projects p ON p.id = c2.project_id
WHERE c.user_id = #user_id AND c.user_id != c2.user_id
GROUP BY c2.user_id;
But it will be fast, and the space required for the indexes will be significantly smaller, especially for large data sets.
Original answer
To fetch the total number of projects for each person, use COUNT function with GROUP BY clause:
SELECT person, COUNT(*) AS collaborations
FROM users
GROUP BY person;
I have a question. This is my database structure
**company**
id | name
---------
1, Test
2, demo
**address**
id | name
---------
1, test1
2, test2
3, bla6
**address_company**
id | address_id | company_id
1, 1, 1
2, 2, 1
3, 3, 2
My query is this:
SELECT company.name, address.name FROM company
INNER JOIN address_company on address_company.company_id = company.id
INNER JOIN address on address.id = address_company.address_id
This works. But I need to filter results.
So when people click address (frontend): test1, it only needs to show company: Test
I can do this:
WHERE address.name = "test1"
This also works but I need to filter further so what I need is
WHERE address.name = "test1" AND address.name = "test2"
But this doesn't work, it doesn't show results. I can only filter on 1 address and I need to filter on more addresses.
Hope you guys can understand me and can help me.
THANKS!
The below strategy leans on the unique key(address_id,company_id) making sure there are no duplicates at that combo-level
Schema
create table company
( id int auto_increment primary key,
name varchar(100) not null
);
insert company(name) values ('Test'),('demo');
create table address
( id int auto_increment primary key,
name varchar(100) not null
);
insert address(name) values ('test1'),('test2'),('bla6');
create table address_company
( id int auto_increment primary key,
address_id int not null,
company_id int not null,
unique key(address_id,company_id) -- no dupes allowed ! I am banking on this below
);
insert address_company(address_id,company_id) values (1,1),(2,1),(3,2);
The queries
select company_id,count(*) theCount from address_company
where address_id in (1,2)
group by company_id having theCount>1;
+------------+----------+
| company_id | theCount |
+------------+----------+
| 1 | 2 |
+------------+----------+
select company_id,count(*) theCount from address_company
where address_id in (select id from address where name in ('test1','test2'))
group by company_id having theCount>1;
+------------+----------+
| company_id | theCount |
+------------+----------+
| 1 | 2 |
+------------+----------+
So if the group by / having returns greater than 1 for the count, where I literally went after name1 and name2, then I know that row qualifies. And that row of course then has name1 and name2.
Back to the unique key part: This assures we aren't tricked in having a company with the same address twice. Which first off doesn't make sense, and also that would mess up this strategy.
Obviously the schema needs some index help, and FK's wouldn't break anyone's heart. But this is just a strawman.
Use OR instead of and, or use the in() structure:
WHERE address.name = 'test1' OR address.name = 'test2'
WHERE address.name IN('test1', 'test2' )
Note: I hope that the below join condition was just typed incorrectly in the question:
INNER JOIN address on address.id = address_company.id
I have the following tables
1. tblJobs
JobID int primary key
JobTypeID int
JobClientID int
JobStaffID int
....
2. tblContacts
ContactID int primary key
ContactName varchar
....
3. tblJobTypes
TypeID int primary key
TypeName varchar
I can SELECT data from these table with this SQL...
SELECT tblContacts.ContactName, tblContacts.ContactID,
tblJobs.JobID, tblJobs.JobTypeID, tblJobs.JobClientID, tblJobs.JobStaffID,
tblJobTypes.* FROM (tblJobs LEFT JOIN tblJobTypes ON tblJobs.JobTypeID = tblJobTypes.TypeID) LEFT JOIN tblContacts ON tblJobs.JobClientID = tblContacts.ContactID;
An example row from the SQL...
| ContactName| ContactID | JobID | JobTypeID | JobClientID | JobStaffID |TypeID | TypeName |
| Mr Contact | 290 | 341 | 3 | 290 | 202 | 3 | Enquiry |
As you can see this SQL joins the tblJobs.JobClientID to tblContacts.ContactID(290).
This is how I get tblContacts.JobStaffID (202).
How can I modify the SQL to get tblContacts.ContactName?
I've tried joining tables twice but with no success.
Assuming that JobStaffID is a foreign key relating to tblContacts.ContactID you're correct in assuming that you need to join the tblContacts table twice - but you have to give it different aliases in each join like this:
SELECT
c1.ContactName as ClientName, c1.ContactID as ClientID,
c2.ContactName as StaffName, c2.ContactID as StaffID,
j.JobID, j.JobTypeID, j.JobClientID, j.JobStaffID,
jt.TypeID, jt.TypeName
FROM tblJobs j
LEFT JOIN tblJobTypes jt ON j.JobTypeID = jt.TypeID
LEFT JOIN tblContacts c1 ON j.JobClientID = c1.ContactID
LEFT JOIN tblContacts c2 ON j.JobStaffID = c2.ContactID;
And while at it you might want to use aliases for all tables to reduce the query text.