MySQL Query with reference parameter from another table - mysql

I apologise in advance if this might seem simple as my assignment needs to be passed in 3 hours time and I don't have enough time to do some further research as I have another pending assignment to be submitted tonight. I only know the basic MYSQL commands and not these types. Please help.
Say that I have two tables:
________________ _________________
| customers | | agents |
|________________| |_________________|
|(pk)customer id | |(pk) agent_id |
|(fk) agent_id | | first_name |
| first_name | | last_name |
| last_name | | address |
|________________| |_________________|
Basically I would just like to know how to query something like: (in incorrect terms)
SELECT * FROM customers WHERE agent_id = '(agent_id of Michael Smith from the AGENTS table)'
obviously I only have the agent_id of the agent and i can directly call it if i know what the agents name is based on the id like:
SELECT * FROM customers WHERE agent_id = '4'
but how can i query it by submitting the agent first name and last name as parameter?
(first name and last name because agents can have the same names, or even same last names)

Remember your foreign key does not help you building the query, you have to tell the database what you want in the query (however, a foreign key can help data spread across tables more consistent).
You can use a JOIN here.
You can implement it like this:
SELECT *
FROM customers C
INNER JOIN agents A ON C.agent_id = A.agent_id
WHERE A.last_name = 'Smith'
AND A.first_name = 'Michael';

You can do it without a join as well.
select *
from customers
where customers.agent_id in (
select agents.agent_id
from agents
where agents.first_name = 'Michael' and agents.last_name = 'Smith'
);

Related

SQL, table join wont display proper output [duplicate]

I've got the following two tables (in MySQL):
Phone_book
+----+------+--------------+
| id | name | phone_number |
+----+------+--------------+
| 1 | John | 111111111111 |
+----+------+--------------+
| 2 | Jane | 222222222222 |
+----+------+--------------+
Call
+----+------+--------------+
| id | date | phone_number |
+----+------+--------------+
| 1 | 0945 | 111111111111 |
+----+------+--------------+
| 2 | 0950 | 222222222222 |
+----+------+--------------+
| 3 | 1045 | 333333333333 |
+----+------+--------------+
How do I find out which calls were made by people whose phone_number is not in the Phone_book? The desired output would be:
Call
+----+------+--------------+
| id | date | phone_number |
+----+------+--------------+
| 3 | 1045 | 333333333333 |
+----+------+--------------+
There's several different ways of doing this, with varying efficiency, depending on how good your query optimiser is, and the relative size of your two tables:
This is the shortest statement, and may be quickest if your phone book is very short:
SELECT *
FROM Call
WHERE phone_number NOT IN (SELECT phone_number FROM Phone_book)
alternatively (thanks to Alterlife)
SELECT *
FROM Call
WHERE NOT EXISTS
(SELECT *
FROM Phone_book
WHERE Phone_book.phone_number = Call.phone_number)
or (thanks to WOPR)
SELECT *
FROM Call
LEFT OUTER JOIN Phone_Book
ON (Call.phone_number = Phone_book.phone_number)
WHERE Phone_book.phone_number IS NULL
(ignoring that, as others have said, it's normally best to select just the columns you want, not '*')
SELECT Call.ID, Call.date, Call.phone_number
FROM Call
LEFT OUTER JOIN Phone_Book
ON (Call.phone_number=Phone_book.phone_number)
WHERE Phone_book.phone_number IS NULL
Should remove the subquery, allowing the query optimiser to work its magic.
Also, avoid "SELECT *" because it can break your code if someone alters the underlying tables or views (and it's inefficient).
The code below would be a bit more efficient than the answers presented above when dealing with larger datasets.
SELECT *
FROM Call
WHERE NOT EXISTS (
SELECT 'x'
FROM Phone_book
WHERE Phone_book.phone_number = Call.phone_number
);
SELECT DISTINCT Call.id
FROM Call
LEFT OUTER JOIN Phone_book USING (id)
WHERE Phone_book.id IS NULL
This will return the extra id-s that are missing in your Phone_book table.
I think
SELECT CALL.* FROM CALL LEFT JOIN Phone_book ON
CALL.id = Phone_book.id WHERE Phone_book.name IS NULL
SELECT t1.ColumnID,
CASE
WHEN NOT EXISTS( SELECT t2.FieldText
FROM Table t2
WHERE t2.ColumnID = t1.ColumnID)
THEN t1.FieldText
ELSE t2.FieldText
END FieldText
FROM Table1 t1, Table2 t2
SELECT name, phone_number FROM Call a
WHERE a.phone_number NOT IN (SELECT b.phone_number FROM Phone_book b)
Alternatively,
select id from call
minus
select id from phone_number
Don't forget to check your indexes!
If your tables are quite large you'll need to make sure the phone book has an index on the phone_number field. With large tables the database will most likely choose to scan both tables.
SELECT *
FROM Call
WHERE NOT EXISTS
(SELECT *
FROM Phone_book
WHERE Phone_book.phone_number = Call.phone_number)
You should create indexes both Phone_Book and Call containing the phone_number. If performance is becoming an issue try an lean index like this, with only the phone number:
The fewer fields the better since it will have to load it entirely. You'll need an index for both tables.
ALTER TABLE [dbo].Phone_Book ADD CONSTRAINT [IX_Unique_PhoneNumber] UNIQUE NONCLUSTERED
(
Phone_Number
)
WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ONLINE = ON) ON [PRIMARY]
GO
If you look at the query plan it will look something like this and you can confirm your new index is actually being used. Note this is for SQL Server but should be similar for MySQL.
With the query I showed there's literally no other way for the database to produce a result other than scanning every record in both tables.

Complex SQL query with where and where not in

I have 3 tables (candidates, candidate_notes and candidate_events)
CANDIDATES | CANDIDATE_NOTES | CANDIDATE_EVENTS
id | id | id
name | candidate_event_id | type
surname | candidate_id
city | note
...
One candidate can have many notes.
One note has one kind of event.
I would like to obtain all the candidates that have notes of kind of event 1 but only that.
For example, if the candidate 1 have 5 notes, one of type 1, other 3 of type 3 and another one of type 6, I don't want to see it in the results.
Can someone help me with this query?
Thanks
One method uses group by and having:
select cn.candidate_id
from candidate_notes cn join
candidate_events e
on cn.candidate_event_id = e.candidate_event_id
group by cn.candidate_id
having min(type) = 1 and min(type) = max(type);

export phpList subscribers via sql in mysql database

For some reason, I am unable to export a table of subscribers from my phpList (ver. 3.0.6) admin pages. I've searched on the web, and several others have had this problem but no workarounds have been posted. As a workaround, I would like to query the mySQL database directly to retrieve a similar table of subscribers. But I need help with the SQL command. Note that I don't want to export or backup the mySQL database, I want to query it in the same way that the "export subscribers" button is supposed to do in the phpList admin pages.
In brief, I have two tables to query. The first table, user contains an ID and email for every subscriber. For example:
id | email
1 | e1#gmail.com
2 | e2#gmail.com
The second table, user_attribute contains a userid, attributeid, and value. Note in the example below that userid 1 has values for all three possible attributes, while userid's 2 and 3 are either missing one or more of the three attributeid's, or have blank values for some.
userid | attributeid | value
1 | 1 | 1
1 | 2 | 4
1 | 3 | 6
2 | 1 | 3
2 | 3 |
3 | 1 | 4
I would like to execute a SQL statement that would produce a row of output for each id/email that would look like this (using id 3 as an example):
id | email | attribute1 | attribute2 | attribute3
3 | e3#gmail.com | 4 | "" | "" |
Can someone suggest SQL query language that could accomplish this task?
A related query I would like to run is to find all id/email that do not have a value for attribute3. In the example above, this would be id's 2 and 3. Note that id 3 does not even have a blank value for attributeid3, it is simply missing.
Any help would be appreciated.
John
I know this is a very old post, but I just had to do the same thing. Here's the query I used. Note that you'll need to modify the query based on the custom attributes you have setup. You can see I had name, city and state as shown in the AS clauses below. You'll need to map those to the attribute id. Also, the state has a table of state names that I linked to. I excluded blacklisted (unsubscribed), more than 2 bounces and unconfirmed users.
SELECT
users.email,
(SELECT value
FROM `phplist_user_user_attribute` attrs
WHERE
attrs.userid = users.id and
attributeid=1
) AS name,
(SELECT value
FROM `phplist_user_user_attribute` attrs
WHERE
attrs.userid = users.id and
attributeid=3
) AS city,
(SELECT st.name
FROM `phplist_user_user_attribute` attrs
LEFT JOIN `phplist_listattr_state` st
ON attrs.value = st.id
WHERE
attrs.userid = users.id and
attributeid=4
) AS state
FROM
`phplist_user_user` users
WHERE
users.blacklisted=0 and
users.bouncecount<3 and
users.confirmed=1
;
I hope someone finds this helpful.

MySql Best Way To Do Query? Virtual Tables? Duplicate row returned because of unique column needed

I have a database that I made for a website I am making that will have a list of employees. It will be a grid like list with each employee pic, name, phone number, etc.
This is my first database design and I'm really proud of it... but I'm a newbie so I am sure it is inefficient in some ways.
I am having trouble figuring out the best way to query it for the website to make the elements.
1) Should I use a virtual table? Or should I just use one query and loop it for each employee?
2) Some employees will have more than one jurisdiction. Right now I have:
select employees.firstName, employees.lastName, employees.phone, employees.position,
employees.picture, jurisdictions.jurisdiction
from employees
inner join(jurisdictions cross join departments cross join user_jurisdictions)
on (employees.deptID = departments.deptID
AND user_jurisdictions.userID = employees.userID
AND user_jurisdictions.jurID = jurisdictions.jurID)
Which would return:
+-----------+----------+--------------+-----------------+---------+--------------+
| firstName | lastName | phone | position | picture | jurisdiction |
+-----------+----------+--------------+-----------------+---------+--------------+
| John | Smith | 210-226-3232 | Senior Manager |/pics/jpeg1|South America|
| John | Smith | 210-226-3232 | Senior Manager |/pics/jpeg1|London |
+-----------+----------+--------------+-----------------+---------+--------------+
How is there a way to create the query where it would remove the duplicates rows because of the additional jurisdiction the employee would have? something like?
+-----------+----------+--------------+---------+---------+--------------+------------+
| firstName | lastName | phone | position| picture | jurisdiction |Jurisdiction|
+-----------+----------+--------------+---------+---------+--------------+------------+
| John | Smith | 210-226-3232 | Manager |/pics/jpeg1| South America|London |
+-----------+----------+--------------+-----------------+---------+-------------------+
Here is my mysql schema:
employees
--------------------
userID (primary key)
firstName
lastName
phone
fax
position
picture
deptID (foreign key references departments(deptID))
departments
--------------------
deptID (primary key)
department
jurisdictions
-----------------
jurID (primary key)
jurisdiction
user_jurisdictions
--------------------------
userID(foreign key references employees(userID))
jurID(foreign key references jurisdictions(jurID))
triger:
after_employees_insert | INSERT | employees | begin
insert into user_jurisdictions(userID) values (new.userID);
You can group your results by employee and use MySQL's GROUP_CONCAT() function to aggregate the employee's jurisdictions:
SELECT employees.firstName,
employees.lastName,
employees.phone,
employees.position,
employees.picture,
GROUP_CONCAT(jurisdictions.jurisdiction)
FROM employees
JOIN departments USING (deptID)
JOIN user_jurisdictions USING (userID)
JOIN jurisdictions USING ( jurID)
GROUP BY employees.userID
However, this aggregates jurisdictions into a delimited string (which makes it difficult to differentiate between multiple jurisdictions and a single jurisdiction containing the delimiter: one can choose a delimiter that is unlikely to be used in a jurisdiction name, but it's still subject to problems such as limitations in string length and, in my view, is fundamentally lazy).
A better way might be to leave such aggregation to a higher-level of your application code. For example, using PDO:
$dbh = new PDO("mysql:dbname=$dbname", $username, $password);
$qry = $dbh->query('
SELECT employees.userID
employees.firstName,
employees.lastName,
employees.phone,
employees.position,
employees.picture,
jurisdictions.jurisdiction
FROM employees
JOIN departments USING (deptID)
JOIN user_jurisdictions USING (userID)
JOIN jurisdictions USING ( jurID)
ORDER BY employees.userID
');
$row = $qry->fetch();
while ($row) {
$current_user = $row['userID'];
// handle $current_user initialisation
do {
// handle user $row
} while ($row = $qry->fetch() and $row['userID'] == $current_user);
// handle $current_user termination
}
Of course, this has the disadvantage of producing a much larger resultset that must be generated by the RDBMS and transferred to / processed by your higher-level code.

Summary of MySQL detail records matching by IP address ranges - mySQL Jedi Knight required

So, I have to draw upon all the powers of the greatest mySQL minds that SO has to offer. I have to summarize detail records based on the IP address in each record. Here's the scenario:
In short, we have consortiums that want to know: "Which schools within my consortium watched which videos how many times"? In SQL terms, it amounts to COUNTing the detail records, grouped by which IP range it might fall into.
We have several university Consortiums - each with a handful of different schools that are members.
Each school within a consortium uses various IP ranges to access the videos that we serve to these schools.
The IP Ranges are specified with wild cards, so each school specifies something like '100.200.35.x, 100.201.x.x, 100.202.39.50, etc.', with the average number of ranges per school being 10 or 15.
The raw text log files to summarize are already in a database (one row for each log entry), and has the actual IP address that accessed the video file.
There are 100's of millions of detail records, so I fully expect this to be a long slow process that runs for a considerable period.
PHP scripts exist that can "explode" the wildcards into the individual IPs that are represented, but I fear this will be the final answer and could take weeks to run.
(For simplicity sake, I'm only going to refer to the video filename that was accessed and COUNT the log entries for it, but in fact all the details such as start/stop/duration,etc. are there and will ultimately be part of this solution.)
With Consortium records something like this: (All table designs except log details open to suggestion):
| id|consortium |
| 10|Ivy League |
| 20|California |
And School/IP records something like this:
| id|school |consortium_id|
| 101|Harvard |10 |
| 102|Yale |10 |
| 103|UCLA |20 |
| 104|Berkeley |20 |
| id|school_id|ip_range |
| 1| 101 |100.200.x.x |
| 2| 101 |100.201.65.x |
| 3| 101 |100.202.39.50 |
| 4| 101 |100.202.39.51 |
| 5| 101 |100.200.x.x |
| 6| 101 |100.201.65.x |
| 7| 101 |100.202.39.50 |
And detail records something like this:
|session |ip_address |filename |
|560554790925|100.202.390.500|history101.mp4 |
|406417611526|43.22.90.5 |newsreel.mp4 |
|650423700223|100.202.39.50 |history101.mp4 |
|650423700223|100.202.50.12 |science101.mp4 |
|513057324209|100.202.39.56 |history101.mp4 |
I like to think I'm pretty handy with mySQL, but this one is stretching it, and am hoping that there's a spectacular function or set of steps that someone might offer.
With your existing data structure, you could do string matching as follows (but it's not very efficient):
SELECT schools.school, detail.filename, COUNT(*)
FROM schools
JOIN ipranges ON schools.id = ipranges.school_id
JOIN detail ON detail.ip_address LIKE REPLACE(ipranges.ip_range, 'x', '%')
WHERE schools.consortium_id = ?
GROUP BY schools.school, detail.filename
A better way would be to store your IP ranges as network address and prefix length:
ALTER TABLE ipranges
ADD COLUMN network INT UNSIGNED,
ADD COLUMN prefix TINYINT;
UPDATE ipranges SET
network = INET_ATON(REPLACE(ip_range, 'x', 0)),
prefix = 32 - 8*(CHAR_LENGTH(ip_range) - CHAR_LENGTH(REPLACE(ip_range,'x',''));
ALTER TABLE ipranges
DROP COLUMN ip_range;
ALTER TABLE detail
ADD COLUMN ip_address_new INT UNSIGNED;
UPDATE detail SET
ip_address_new = INET_ATON(ip_address);
ALTER TABLE detail
DROP COLUMN ip_address,
CHANGE ip_address_new ip_address INT UNSIGNED;
Then it would merely be a case of performing some bit comparisons:
SELECT schools.school, detail.filename, COUNT(*)
FROM schools
JOIN ipranges ON schools.id = ipranges.school_id
JOIN detail ON detail.ip_address & ~((1 << 32 - ipranges.prefix) - 1)
= ipranges.network
WHERE schools.consortium_id = ?
GROUP BY schools.school, detail.filename
SELECT D.filename, S.school, COUNT(D.*)
FROM detail_records AS D
INNER JOIN ip_map AS I ON D.ip_address LIKE CONCAT(SUBSTRING(I.ip_range, 1, LOCATE('x', I.ip_range)-1), '%')
INNER JOIN school AS S ON S.id = I.school_id
INNER JOIN consortium AS C ON C.id = S.consortium_id
WHERE S.consortium_id = <consortium identifier>
GROUP BY D.filename, S.school