I have a MySQL tables like this:
##customer##
+-----------+----+---------+
|customer_id|name|telephone|
+-----------+----+---------+
| 1 |Andi|+62932011|
| 2 |Boby|+62928291|
| 3 |Jane|+62932212|
| 4 |John|+62999021|
| 5 |Beth|+62999021|
| 6 |Noel|+62999021|
+-----------+----+---------+
##plus_membership##
+-----------------+-----------+-------+------------+
|plus_membership_id|customer_id|status |requested_at|
+------------------+-----------+-------+------------+
| 1 | 1 | 1 | 2018-11-01 |
| 2 | 2 | 0 | 2018-11-03 |
| 3 | 4 | 2 | 2018-11-04 |
| 4 | 6 | 1 | 2018-11-05 |
+------------------+-----------+-------+------------+
there are two tables in above structure, the first is the customer with customer_id as the primary key and the second one is the plus_membership which has foreign key customer_id, the plus_membership table is a table to show a request if customer request to become a plus member, status 1 means the customer is apporved to be plus member. I need to select the customer table and add alias column lets say the alias column name is membership , that shows only regular or plus , plus means the customer in plus_membership status is 1, and regular if customer doesnt exist in plus_membership table or status is not 1 in membership table. for example:
SELECT *, .... AS membership FROM customer;
+-----------+----+---------+----------+
|customer_id|name|telephone|membership|
+-----------+----+---------+----------+
| 1 |Andi|+62932011| Plus |
| 2 |Boby|+62928291| Regular |
| 3 |Jane|+62932212| Regular |
| 4 |John|+62999021| Regular |
| 5 |Beth|+62999021| Regular |
| 6 |Noel|+62999021| Plus |
+-----------+----+---------+----------+
You can use Left Join between the two tables, and use Case .. When conditional expressions to evaluate membership accordingly.
Left Join would ensure that all the customer(s) from the customer table are considered, whether they have a corresponding matching row in the plus_membership table or not.
SELECT
c.customer_id,
c.name,
c.telephone,
(CASE WHEN pm.status = 1 THEN 'Plus' ELSE 'Regular' END) AS membership
FROM customer AS c
LEFT JOIN plus_membership AS pm
ON pm.customer_id = c.customer_id
Another approach can be using Correlated Subquery and Exists(). Generally, this would be less efficient than Left Join approach.
SELECT
c.customer_id,
c.name,
c.telephone,
CASE WHEN EXISTS (SELECT 1
FROM plus_membership AS pm
WHERE pm.customer_id = c.customer_id AND
pm.status = 1
)
THEN 'Plus'
ELSE 'Regular'
END AS membership
FROM customer AS c
We use EXISTS or IN to look up data in another table.
select customer_id, name, telephone,
case when customer_id in (select customer_id from plus_membership where status = 1)
then 'Plus' else 'Regular' end as membership
from customer
order by customer_id;
Related
I'm very average with MySQL, but usually I can write all the needed queries after reading documentation and searching for examples. Now, I'm in the situation where I spent 3 days re-searching and re-writing queries, but I can't get it to work the exact way I need. Here's the deal:
1st table (mpt_companies) contains companies:
| company_id | company_title |
------------------------------
| 1 | Company A |
| 2 | Company B |
2nd table (mpt_payment_methods) contains payment methods:
| payment_method_id | payment_method_title |
--------------------------------------------
| 1 | Cash |
| 2 | PayPal |
| 3 | Wire |
3rd table (mpt_payments) contains payments for each company:
| payment_id | company_id | payment_method_id | payment_amount |
----------------------------------------------------------------
| 1 | 1 | 1 | 10.00 |
| 2 | 2 | 3 | 15.00 |
| 3 | 1 | 1 | 20.00 |
| 4 | 1 | 2 | 10.00 |
I need to list each company along with many stats. One of stats is the sum of payments in each payment method. In other words, the result should be:
| company_id | company_title | payment_data |
--------------------------------------------------------
| 1 | Company A | Cash:30.00,PayPal:10.00 |
| 2 | Company B | Wire:15.00 |
Obviously, I need to:
Select all the companies;
Join payments for each company;
Join payment methods for each payment;
Calculate sum of payments in each method;
GROUP_CONCAT payment methods and sums;
Unfortunately, SUM() doesn't work with GROUP_CONCAT. Some solutions I found on this site suggest using CONCAT, but that doesn't produce the list I need. Other solutions suggest using CAST(), but maybe I do something wrong because it doesn't work too. This is the closest query I wrote, which returns each company, and unique list of payment methods used by each company, but doesn't return the sum of payments:
SELECT *,
(some other sub-queries I need...),
(SELECT GROUP_CONCAT(DISTINCT(mpt_payment_methods.payment_method_title))
FROM mpt_payments
JOIN mpt_payment_methods
ON mpt_payments.payment_method_id=mpt_payment_methods.payment_method_id
WHERE mpt_payments.company_id=mpt_companies.company_id
ORDER BY mpt_payment_methods.payment_method_title) AS payment_data
FROM mpt_companies
Then I tried:
SELECT *,
(some other sub-queries I need...),
(SELECT GROUP_CONCAT(DISTINCT(mpt_payment_methods.payment_method_title), ':', CAST(SUM(mpt_payments.payment_amount) AS CHAR))
FROM mpt_payments
JOIN mpt_payment_methods
ON mpt_payments.payment_method_id=mpt_payment_methods.payment_method_id
WHERE mpt_payments.company_id=mpt_companies.company_id
ORDER BY mpt_payment_methods.payment_method_title) AS payment_data
FROM mpt_companies
...and many other variations, but all of them either returned query errors, either didn't return/format data I need.
The closest answer I could find was MySQL one to many relationship: GROUP_CONCAT or JOIN or both? but after spending 2 hours re-writing the provided query to work with my data, I couldn't do it.
Could anyone give me a suggestion, please?
You can do that by aggregating twice. First for the sum of payments per method and company and then to concatenate the sums for each company.
SELECT x.company_id,
x.company_title,
group_concat(payment_amount_and_method) payment_data
FROM (SELECT c.company_id,
c.company_title,
concat(pm.payment_method_title, ':', sum(p.payment_amount)) payment_amount_and_method
FROM mpt_companies c
INNER JOIN mpt_payments p
ON p.company_id = c.company_id
INNER JOIN mpt_payment_methods pm
ON pm.payment_method_id = p.payment_method_id
GROUP BY c.company_id,
c.company_title,
pm.payment_method_id,
pm.payment_method_title) x
GROUP BY x.company_id,
x.company_title;
db<>fiddle
Here you go
SELECT company_id,
company_title,
GROUP_CONCAT(
CONCAT(payment_method_title, ':', payment_amount)
) AS payment_data
FROM (
SELECT c.company_id, c.company_title, pm.payment_method_id, pm.payment_method_title, SUM(p.payment_amount) AS payment_amount
FROM mpt_payments p
JOIN mpt_companies c ON p.company_id = c.company_id
JOIN mpt_payment_methods pm ON pm.payment_method_id = p.payment_method_id
GROUP BY p.company_id, p.payment_method_id
) distinct_company_payments
GROUP BY distinct_company_payments.company_id
;
class_table
+----+-------+--------------+
| id |teac_id| student_id |
+----+-------+--------------+
| 1 | 1 | 1,2,3,4 |
+----+-------+--------------+
student_mark
+----+----------+--------+
| id |student_id| marks |
+----+----------+--------+
| 1 | 1 | 12 |
+----+----------+--------+
| 2 | 2 | 80 |
+----+----------+--------+
| 3 | 3 | 20 |
+----+----------+--------+
I have these two tables and i want to calculate the total marks of student and my sql is:
SELECT SUM(`marks`)
FROM `student_mark`
WHERE `student_id` IN
(SELECT `student_id` FROM `class_table` WHERE `teac_id` = '1')
But this will return null, please help!!
DB fiddle
Firstly, you should never store comma separated data in your column. You should really normalize your data. So basically, you could have a many-to-many table mapping teacher_to_student, which will have teac_id and student_id columns.
In this particular case, you can utilize Find_in_set() function.
From your current query, it seems that you are trying to getting total marks for a teacher (summing up marks of all his/her students).
Try:
SELECT SUM(sm.`marks`)
FROM `student_mark` AS sm
JOIN `class_table` AS ct
ON FIND_IN_SET(sm.`student_id`, ct.`student_id`) > 0
WHERE ct.`teac_id` = '1'
In case, you want to get total marks per student, you would need to add a Group By. The query would look like:
SELECT sm.`student_id`,
SUM(sm.`marks`)
FROM `student_mark` AS sm
JOIN `class_table` AS ct
ON FIND_IN_SET(sm.`student_id`, ct.`student_id`) > 0
WHERE ct.`teac_id` = '1'
GROUP BY sm.`student_id`
Just in case you want to know why, The reason it returned null is because the subquery returned as '1,2,3,4' as a whole. What you need is to make it returned 1,2,3,4 separately.
What your query returned
SELECT SUM(`marks`)
FROM `student_mark`
WHERE `student_id` IN ('1,2,3,4')
What you expect is
SELECT SUM(`marks`)
FROM `student_mark`
WHERE `student_id` IN (1,2,3,4)
The best way is it normalize as #madhur said. In your case you need to make the teacher and student as one to many link
+----+-------+--------------+
| id |teac_id| student_id |
+----+-------+--------------+
| 1 | 1 | 1 |
+----+-------+--------------+
| 2 | 1 | 2 |
+----+-------+--------------+
| 3 | 1 | 3 |
+----+-------+--------------+
| 4 | 1 | 4 |
+----+-------+--------------+
If you want to filter your table based on a comma separated list with ID, my approach is to
append extra commas at the beginning and at the end of a list as well as at the beginning and at the end of an ID, eg.
1 becomes ,1, and list would become ,1,2,3,4,. The reason for that is to avoid ambigious matches like 1 matches 21 or 12 in a list.
Also, EXISTS is well-suited in that situation, which together with INSTR function should work:
SELECT SUM(`marks`)
FROM `student_mark` sm
WHERE EXISTS(SELECT 1 FROM `class_table`
WHERE `teac_id` = '1' AND
INSTR(CONCAT(',', student_id, ','), CONCAT(',', sm.student_id, ',')) > 0)
Demo
BUT you shouldn't store related IDs in one cell as comma separated list - it should be foreign key column to form proper relation. Joins would become trivial then.
I have two tables
Accounts:
+------------+--------+
| accountsid | name |
+------------+--------+
| 1 | Bob |
| 2 | Rachel |
| 3 | Mark |
+------------+--------+
Sales Orders
+--------------+------------+------------+--------+
| salesorderid | accountsid | so_date | amount |
+--------------+------------+------------+--------+
| 1 | 1 | 2015-12-16 | 50 |
| 2 | 1 | 2016-01-13 | 20 |
| 3 | 2 | 2015-12-14 | 10 |
| 4 | 3 | 2016-02-14 | 35 |
+--------------+------------+------------+--------+
As you can see, is a 1-N relation where Accounts has many Salesorders and Salesorder has 1 Account.
I need to retrieve "old" Accounts where are not active anymore. For example, If some Account dont have Salesorder in 2016 is an inactive Account.
So, in this example the result will be ONLY Rachel.
How can i retrieve this? I think its the "opposite" of between but I cant figure how to do it...
Thanks.
PS. Despite the title I can get this without INNER JOIN.
You're looking to effect an anti-join, for which there are three possibilities in MySQL:
Using NOT IN:
SELECT a.*
FROM Accounts a
WHERE a.accountsid NOT IN (
SELECT so.accountsid
FROM `Sales Orders` so
WHERE so.so_date >= '2016-01-01'
)
Using NOT EXISTS:
SELECT a.*
FROM Accounts a
WHERE NOT EXISTS (
SELECT *
FROM `Sales Orders` so
WHERE so.accountsid = a.accountsid
AND so.so_date >= '2016-01-01'
)
Using an outer JOIN:
SELECT a.*
FROM Accounts a LEFT JOIN `Sales Orders` so
ON so.accountsid = a.accountsid
AND so.so_date >= '2016-01-01'
WHERE so.accountsid IS NULL
why do you need to use only inner join? inner join is for cases you have data matching on two tables but in this case you don't you need to be using a subquery with either "not in" or "not exists"
What you want is to get the ids that didn´t make any order, so get the ids that made some order and the rest of them are the ones that didn´t make orders.
It should be something like this SELECT * FROM Accounts WHERE accountsid NOT IN (SELECT accountsid FROM Sales Orders WHERE so_date > your_date)
I have created the following query to use in a view
SELECT
*
FROM
customers c
JOIN
customer_business cb
ON
c.customer_id = cb.customer_id
union
SELECT
*
FROM
customers c
LEFT JOIN
customer_business
ON
business_id=NULL;
It makes his work perfectly. It shows all customers with the business associated, and at the end, shows all customers with the info of the business in null.
customer_id | business_id
--------------------------------
1 | 1
2 | 1
2 | 2
1 | NULL
2 | NULL
3 | NULL
But the problem es that the UNION makes the view has very poor performace.
I tryed to do it with LEFT JOIN but doesnt shows al the customers with business in null, just the ones without any businesses associated
I know that the solution to speed up my view is to remove that UNION, but i cant figure out how.
Can anyone help me?
Thanks
EDIT
Here's an example
Customer Table
customer_id | name
--------------------------------
1 | test1
2 | test2
3 | test3
Customer_business Table
customer_business_id | customer_id | business_id
----------------------------------------------------------
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 2 | 1
5 | 2 | 2
Expected query result:
name | customer_id | business_id
----------------------------------------------------------
test1 | 1 | 1
test1 | 1 | 2
test1 | 1 | 3
test2 | 2 | 1
test2 | 2 | 2
test1 | 1 | NULL
test2 | 2 | NULL
test3 | 3 | NULL
Updating it based on the comments below and the output you want.
Note that I have used UNION ALL which is faster than UNION as UNION uses DISTINCT to get unique records which in your case doesn't apply. Also, make sure customer_id is PK in Customer table and try adding non-unique index on customer_id in Customer_Business table and it should help with performance.
SELECT name,
C.customer_id,
business_id
FROM Customer C
INNER JOIN Customer_Business CB
ON C.customer_id = CB.customer_id
UNION ALL
SELECT name,
C.customer_id,
NULL
FROM Customer C
Excluding the union which we know that is not performant the other thing that slows down you query is the statement in the second query ON idbusiness = NULL.
I propose to edit you query like this and see the performance as a view:
SELECT c.customer_id, idbusiness
FROM customers c
JOIN customer_business cb ON c.customer_id = cb.customer_id
UNION
SELECT customer_id, NULL
FROM customers c
EDIT:
Looking for an alternative you could try this, it should return the same output (i've changed null values with 0) but i don't think it's faster:
SELECT c.customer_id, idbusiness
FROM customers c
INNER JOIN (
SELECT customer_id, idbusiness
FROM customer_business
UNION
SELECT 0 , 0
)b ON ( c.customer_id = b.customer_id )
OR (
b.idbusiness =0
)
Eventually you could try to put into a view only the subquery b or delete the union by putting the values 0,0 as a record in table customer_business.
Ok here's my problem. Assume a customer has access to a number of regions defined in a CustomerRegions table:
CustomerRegionID | CustomerID | RegionID
----------------------------------------
1 | 1 | 1
2 | 1 | 2
Assume that customer 1 has three users 1, 2, and 3. For each user we can specify to which of the CustomerRegions they have access via a table UserRegions:
UserRegionID | UserID | CustomerRegionID
----------------------------------------
1 | 1 | 1
2 | 1 | 2
3 | 2 | 2
So user 1 will have access to both Customerregions and user 2 will only have access to CustomerRegion 2.
If there are UserRegions specified for a given user then only those CustomerRegions are present in the result set, but if no UserRegions are specified for a given user then all CustomerRegions are present in the result. I want to get all accessible regions per user of a given customer. The result I am looking for is something like this:
CustomerID | UserID | RegionID
------------------------------
1 | 1 | 1
1 | 1 | 2
1 | 2 | 2
1 | 3 | 1
1 | 3 | 2
My question is can this be done in a single query and how?
Edit:
I seem to have it working now:
SELECT CustomerID,
UserID,
RegionID
FROM users
LEFT JOIN customerregions ON customerregions.CustomerID = users.CustomerID
LEFT JOIN userregions ON userregions.UserID = users.UserID AND userregions.CustomerRegionID = customerregions.CustomerRegionID
LEFT JOIN regions ON regions.RegionID = customerregions.RegionID
WHERE (userregions.UserID IS NOT NULL
OR (SELECT COUNT(1) FROM userregions WHERE userregions.UserID = users.UserID) = 0)
AND CustomerID = 1
The extra count query in the where seems to do the trick. Thanks #Pablo Martinez for your help. However if someone knows of a better way to do this please let me know.
I'm aggre with #diEcho, the table structure is very confusing
have you try to do a join?
Select CustomerID, UserID, RegionID
from UserRegions join CustomerRegion
on CustomerRegion.CustomerRegionID=UserRegions.CustomerRegionID
where customerID=1