I'm trying to modify and existing query to work with a conditional JOIN.
I have three tables: invoices, companies and clients.
The application logic is this: first there's a bill of quantities which gets created for each order. Then, I create an invoice for that bill of quantities. Sometimes the client that makes the order is different from the client that gets billed (example: client A from company A+ is making an order, but client B from company B+ is getting billed for that order). For this scenario I have the invoice_as_company_id and invoice_as_client_id columns.
Right now I have a query that gets all the invoices and it looks like this:
SELECT i.*, co.name AS company, cl.name AS client
FROM invoices i
LEFT JOIN companies co ON i.invoice_company_id = co.company_id
LEFT JOIN clients cl ON i.invoice_client_id = cl.client_id
ORDER BY i.invoice_date DESC
LIMIT 10
So I would like to modify this query like this:
if the invoice_as_company_id is null, the use the
invoice_company_id field in the companies table join
if the invoice_as_client_id is null, the use the invoice_client_id field
in the clients table join
The database tables are bellow.
Invoices
+-----------------------+--------------+
| invoice_id | int(10) |
| invoice_date | date |
| invoice_number | int(11) |
| invoice_amount | decimal(5,2) |
| invoice_company_id | int(11) |
| invoice_client_id | int(11) |
| invoice_as_company_id | int(11) |
| invoice_as_client_id | int(11) |
| date_added | int(11) |
+-----------------------+--------------+
Companies
+--------------+--------------+
| company_id | int(10) |
| company_name | varchar(255) |
| date_added | int(11) |
+--------------+--------------+
Clients
+-------------+--------------+
| client_id | int(10) |
| client_name | varchar(255) |
| date_added | int(11) |
+-------------+--------------+
LEFT JOIN companies co
ON co.company_id=IFNULL(i.invoice_company_as_id, i.invoice_company_id)
And if you have more specific test cases:
LEFT JOIN companies co
ON co.company_id=IF(i.invoice_company_as_id IS NULL OR i.invoice_company_as_id = 0, i.invoice_company_as_id, i.invoice_company_id)
Same for your 2nd case. Performances may go away for heavy tables...
Related
I want to delete the rows with null values in the column
How can i delete it?
SELECT employee.Name,
`department`.NUM,
SALARY
FROM employee
LEFT JOIN `department` ON employee.ID = `department`.ID
ORDER BY NUM;
+--------------------+-------+----------+
| Name | NUM | SALARY |
+--------------------+-------+----------+
| Gallegos | NULL | NULL |
| Lara | NULL | NULL |
| Kent | NULL | NULL |
| Lena | NULL | NULL |
| Flores | NULL | NULL |
| Alexandra | NULL | NULL |
| Hodge | 8001 | 973.45 |
+--------------------+-------+----------+
Should be like this
+--------------------+-------+----------+
| Name | NUM | SALARY |
+--------------------+-------+----------+
| | | |
| Hodge | 8001 | 973.45 |
+--------------------+-------+----------+
You are asking to delete, but to me it seems more like removing nulls from the result of select statement, if so use:
SELECT employee.Name,
`department`.NUM,
SALARY
FROM employee
LEFT JOIN `department` ON employee.ID = `department`.ID
WHERE (`department`.NUM IS NOT NULL AND SALARY IS NOT NULL)
ORDER BY NUM;
Note: The parentheses are not required but it’s good practice to enclose grouped comparators for better readability.
The above query will exclude the even if the NUM column is not null and the SALARY column is null and vice versa
If by deleting you mean that you don't want to see rows with null values in your table, you can use INNER JOIN instead of LEFT JOIN.
You use INNER JOIN when you want to return only records having pair on both sides, and you'll use LEFT JOIN when you need all records from the “left” table, no matter if they have pair in the “right” table or not.
You can learn more here.
My query looks like:
SELECT SUM(ct_product_store_quantity.quantity) as quantity, `ct_product`.*
FROM `ct_product`
LEFT JOIN `ct_productLang` ON `ct_product`.`id` = `ct_productLang`.`product_id`
LEFT JOIN `ct_product_store_quantity` ON `ct_product`.`id` = `ct_product_store_quantity`.`product_id`
LEFT JOIN `ct_product_attribute` as cpa ON ct_product.id=cpa.product_id
WHERE cpa.attribute_id=10
AND cpa.attribute_value_id=36
AND cpa.attribute_id=2
AND cpa.attribute_value_id=5
AND cpa.attribute_id=7
AND cpa.attribute_value_id=31
AND cpa.attribute_id=9
AND cpa.attribute_value_id=28
AND cpa.attribute_id=8
AND cpa.attribute_value_id=25
GROUP BY `ct_product`.`id`
HAVING quantity > 0
ORDER BY `id` DESC
In simple words - each of the AND condtitions evaluate to true. If I execute them one by one it is OK. But when I try to execute it like what I posted above - no results are returned. I am sure am not doing right the multiple AND conditions part. The ct_product_attribute table:
+--------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| product_id | int(11) | YES | MUL | NULL | |
| attribute_set_id | int(11) | YES | MUL | NULL | |
| attribute_id | int(11) | YES | MUL | NULL | |
| attribute_value_id | int(11) | YES | MUL | NULL | |
| value | varchar(255) | YES | | NULL | |
+--------------------+--------------+------+-----+---------+----------------+
Will post the other tables if needed. Just trying to not flood the post. Thank you!
EDIT
In ct_product I got products like ( just for example ):
id
1
2
3
In ct_product_attribute each product can have more than one attribute-attr.value pairs. Some of the pairs are same.( will show only the columns that I need )
id product_id attribute_id attribute_value_id
1 1 1 1
2 2 1 1
3 1 2 1
4 2 3 1
5 3 1 1
6 3 2 1
The values that I get from the request are:
attribute_id=1
attribute_value_id=1
attribute_id=2
attribute_value_id=1
And now I have to retrieve only the product with id=1. If I use OR it is retrieving both products id=1 and id=2. Not sure if it gets more clear now.
I'm pretty sure those are supposed to be ORs because you can't have all those IDs at the same time. With that in mind, you should be able to use IN.
WHERE cpa.attribute_id IN (10,2,7,9,8)
AND cpa.attribute_value_id IN (36,5,31,28,25)
I really don't know what you are trying to accomplish but you should/could use WHERE IN, as everyone pointed in the comments you are looking for a field with multiple values...
But, as for the AND question, you could/should use IN, as in;
SELECT SUM(ct_product_store_quantity.quantity) as quantity, `ct_product`.*
FROM `ct_product`
LEFT JOIN `ct_productLang` ON `ct_product`.`id` = `ct_productLang`.`product_id`
LEFT JOIN `ct_product_store_quantity` ON `ct_product`.`id` = `ct_product_store_quantity`.`product_id`
LEFT JOIN `ct_product_attribute` as cpa ON ct_product.id=cpa.product_id
WHERE cpa.attribute_id IN (10, 2, 7, 9, 8)
AND cpa.attribute_value_id IN (36, 5, 31, 28, 25)
GROUP BY `ct_product`.`id`
HAVING quantity > 0
ORDER BY `id` DESC
You can try using (cpa.attribute_id,cpa.attribute_value_id) in ((10,36),(2,5),(7,31),(9,28),(8,25))
SELECT SUM(ct_product_store_quantity.quantity) as quantity, `ct_product`.*
FROM `ct_product`
LEFT JOIN `ct_productLang` ON `ct_product`.`id` = `ct_productLang`.`product_id`
LEFT JOIN `ct_product_store_quantity` ON `ct_product`.`id` = `ct_product_store_quantity`.`product_id`
LEFT JOIN `ct_product_attribute` as cpa ON ct_product.id=cpa.product_id
WHERE (cpa.attribute_id,cpa.attribute_value_id) in ((10,36),(2,5),(7,31),(9,28),(8,25)) and `ct_product`.`id`=1
GROUP BY `ct_product`.`id`
HAVING quantity > 0
ORDER BY `id` DESC
Listings table
+------------+---------+
| name | id |
+------------+---------+
| Example 1 | 1 |
| Example 2 | 2 |
| Example 3 | 3 |
| Example 4 | 4 |
| Example 5 | 5 |
| Example 6 | 6 |
+------------+---------+
Categories table
+------------+---------+
| name | id |
+------------+---------+
| Catname 1 | 1 |
| Catname 2 | 2 |
| Catname 3 | 3 |
+------------+---------+
ListingCats table
+--------+---------+
| cat_id | list_id |
+--------+---------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 1 |
| 3 | 1 |
| 3 | 3 |
| 2 | 2 |
| 1 | 5 |
| 2 | 6 |
+--------+---------+
I am trying to build 2 queries which should be simple.
The first thing needed is to get a count of how many listings in the listings table corelate to a given category ID in the listingcats table.
The second part is getting all of the data (*) in the rows from the listings table that corelate to the given category id in the listingcats table.
I have tried a number of joins and for some reason none want to work properly. Can anyone help based on the example tables given above please. The 'given' category ID in this case would be '1'.
For the first query, you can use a simple join, and return a count
SELECT COUNT(Name)
FROM Listings l
JOIN ListingCats lc ON l.id = lc.cat_id
WHERE lc.cat_id = 1
This will return all rows from the listings table such that the listings id has a corresponding cat_id in the listingcats table, but exclusive to those that have a cat_id of 1. Then, the count aggregate function returns the number of rows.
For the second one, you can just use the same subquery above, but without the aggregate function, and select all values.
SELECT * FROM Listings l
JOIN ListingCats lc ON l.id = lc.cat_id
WHERE lc.cat_id = 1
Try those, please let me know if they work or not and I will try to work through them more with you.
EDIT
After looking back at the question, if you are given a specific cat_id you don't even need to use a join, you can simply query the listings table for one that has that id. If the given id is one:
SELECT COUNT(Name)
FROM Listings l
WHERE l.id = 1
And then again, even more broad for the second one:
SELECT * FROM Listings l WHERE l.id = 1
CREATE TABLE `listings` (
`id` int(10) unsigned NOT NULL auto_increment,
`name` varchar(10) NOT NULL default '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE `categories` (
`id` int(10) unsigned NOT NULL auto_increment,
`name` varchar(10) NOT NULL default '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE `listings_cats` (
`cat_id` int(10) unsigned NOT NULL,
`list_id` int(10) unsigned NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
SELECT c.id, c.name, COUNT(lc.list_id) as the_count
FROM categories c
JOIN listings_cats lc ON (lc.cat_id = c.id)
GROUP BY c.id;
SELECT l.id, l.name, c.name AS category_name
FROM listings l JOIN listings_cats lc ON (lc.list_id = l.id)
JOIN categories c ON (lc.cat_id = c.id);
Suppose, we have a table:
SELECT * FROM users_to_courses;
+---------+-----------+------------+---------+
| user_id | course_id | pass_date | file_id |
+---------+-----------+------------+---------+
| 1 | 1 | 2014-01-01 | 1 |
| 1 | 1 | 2014-01-01 | 2 |
| 1 | 1 | 2014-02-01 | 3 |
| 1 | 1 | 2014-02-01 | 4 |
+---------+-----------+------------+---------+
Schema:
CREATE TABLE `users_to_courses` (
`user_id` int(10) unsigned NOT NULL,
`course_id` int(10) unsigned NOT NULL,
`pass_date` date NOT NULL,
`file_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`user_id`, `course_id`, `pass_date`, `file_id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
One user can pass a certain course multiple times, and every time he passes multiple certificates can be generated. user_id and course_id store the links to users and courses tables. file_id - to files table, where info about certificate files is stored.
In our example user #1 has passed course #1 twice and every time 2 certificates were issued: 4 records totally.
How can I get this data: for user_id=1 for every course get MAX(pass_date) and all the files, attached to this date. So far I could only get this:
SELECT
users_to_courses.course_id,
MAX(users_to_courses.pass_date) AS max_passed_date,
GROUP_CONCAT(users_to_courses.file_id SEPARATOR ',') AS files
FROM
users_to_courses
WHERE
users_to_courses.user_id=1
GROUP BY
users_to_courses.course_id;
+-----------+-----------------+---------+
| course_id | max_passed_date | files |
+-----------+-----------------+---------+
| 1 | 2014-02-01 | 1,2,3,4 |
+-----------+-----------------+---------+
I need this:
+-----------+-----------------+---------+
| course_id | max_passed_date | files |
+-----------+-----------------+---------+
| 1 | 2014-02-01 | 3,4 |
+-----------+-----------------+---------+
I think, this requires a compound GROUP BY.
fiddle
Try the below query it first gets max date for all the records and then we can join only those record in the outer query. You can use the same query for more than one user by adding group by utc.user_id
SELECT
utc.course_id,
mdt.maxDate AS max_passed_date,
GROUP_CONCAT(utc.file_id SEPARATOR ',') AS files
FROM
users_to_courses utc
join
(SELECT MAX(pass_date) AS maxDate, course_id cId, user_id uId
FROM users_to_courses GROUP BY user_id, course_id) AS mdt
ON
mdt.uId = utc.user_id
AND
mdt.cId = utc.course_id
AND
mdt.maxDate = utc.pass_date
WHERE
utc.user_id=1
GROUP BY
utc.course_id;
For a homework assignment, I have to write a MySQL query to calculate the GPA of every student in the database table. I broke the problem down into 3 parts: (1) calculating the number of grade points earned by each student, (2) calculating the number of credits taken, and then (3) dividing grade points by credits. Here are the queries I've written for steps 1 and 2:
Calculate grade points earned:
SELECT ID, SUM( credits ) AS credits_taken
FROM takes
NATURAL JOIN course
GROUP BY ID
2 Find grade points earned:
SELECT ID, SUM( credits * ( SELECT points FROM gradepoint WHERE letter = grade ) ) AS tot_grade_points
FROM takes NATURAL JOIN course
GROUP BY ID
I manually evaluated each query and they return the correct results. But I can't figure out how to return (credits_taken / tot_grade_points) for each student. Here is what I have tried:
SELECT ID, GPA
FROM student AS S NATURAL JOIN
(SELECT ID,( 'credits_taken' / SUM( credits * ( SELECT points FROM gradepoint WHERE letter = grade ) )) AS GPA
FROM takes AS T1 NATURAL JOIN course
WHERE S.ID = T1.ID
AND EXISTS (
SELECT ID, SUM( credits ) AS 'credits_taken'
FROM takes AS T2 NATURAL JOIN course
WHERE S.ID = T2.ID
GROUP BY ID
)
GROUP BY ID) Z
GROUP BY ID
But this gives me the error " Unknown column 'S.ID' in 'where clause'". From what I've read, you can't reference the alias of a table from a subquery in a join operation. Does anyone have another way of doing the calculation of these two subqueries and returning them bound to the student ID?
The 'takes' table maps student IDs to information about the courses they've taken, most importantly the course_id and grade. The 'course' table contains the 'credits' field, the number of credits the course is worth.
EDIT
Here are the relevant table structures:
takes:
Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| ID | varchar(5) | NO | PRI | | |
| course_id | varchar(8) | NO | PRI | | |
| sec_id | varchar(8) | NO | PRI | | |
| semester | varchar(6) | NO | PRI | | |
| year | decimal(4,0) | NO | PRI | 0 | |
| grade | varchar(2) | YES | | NULL | |
+-----------+--------------+------+-----+---------+-------+
course:
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| course_id | varchar(8) | NO | PRI | | |
| title | varchar(50) | YES | | NULL | |
| dept_name | varchar(20) | YES | MUL | NULL | |
| credits | decimal(2,0) | YES | | NULL | |
+-----------+--------------+------+-----+---------+-------+
I would try:
SELECT takes.sec_id,
SUM( course.credits * gradepoint.points ) / SUM( course.credits ) AS GPA
FROM takes
JOIN gradepoint ON takes.grade = gradepoint.letter
JOIN course ON takes.course_id = course.course_id
GROUP BY takes.sec_id
Since your table structure description is incomplete I had to guess gradepoint schema and I assumed sec_id identifies a student in takes table, if there is another column for that just replace it in the query in both SELECT and GROUP BY parts. Maybe it is ID, but a column name like that is usually used for primary keys. Or maybe there are no primary keys defined at all, which is a bad practise anyway. Also you would need to join student table if you wanted any student info other than id, like name and so on.
I would also recommend using JOIN ... ON ... syntax instead of NATURAL JOIN, not only it is more readable, it also gives you more flexibility, for example see how gradepoint is joined instead of using costly dependent subquery.