Get results from last WHERE condition - mysql

I have the following DB design.
PRODUCTS
+----+----------------------------------+
| id | name |
+----+----------------------------------+
| 1 | Product 1, Intel, Celeron, N3350 |
| 2 | Product 2, Intel, Celeron, N3350 |
| 3 | Product 3, Intel, i5, 8250U |
| 4 | Product 4, Intel, i9, 8950HK |
| 5 | Product 5, Intel, i9, 8950HK |
+----+----------------------------------+
ATTRIBUTES
+----+--------------+
| id | name |
+----+--------------+
| 11 | Manufacturer |
| 22 | Type |
| 33 | Model |
+----+--------------+
ATTRIBUTE_VALUES
+-----+---------+
| id | value |
+-----+---------+
| 111 | Intel |
| 222 | Celeron |
| 333 | i5 |
| 444 | i9 |
| 555 | N3350 |
| 666 | 8250U |
| 777 | 8950HK |
+-----+---------+
And finally, the table that links all of these:
PRODUCT_ATTRIBUTES_VALUES
+------------+--------------+--------------------+
| product_id | attribute_id | attribute_value_id |
+------------+--------------+--------------------+
| 1 | 11 | 111 |
| 1 | 22 | 222 |
| 1 | 33 | 555 |
| 2 | 11 | 111 |
| 2 | 22 | 222 |
| 2 | 33 | 555 |
| 3 | 11 | 111 |
| 3 | 22 | 333 |
| 3 | 33 | 666 |
| 4 | 11 | 111 |
| 4 | 22 | 444 |
| 4 | 33 | 777 |
| 5 | 11 | 111 |
| 5 | 22 | 444 |
| 5 | 33 | 777 |
+------------+--------------+--------------------+
All of these are about filter products.
I want to return the results for each attribute filter.
If I filter by:
Attribute 'Intel' => it should return all 5 results.
Type 'Celeron' => it should return 2 results (only Celeron)
SQL-FIDDLE
SELECT products.id, products.name FROM `products` LEFT JOIN
`product_attributes_values` ON
`product_attributes_values`.`product_id`=`products`.`id` LEFT JOIN
`attributes` ON
`attributes`.`id`=`product_attributes_values`.`attribute_id` LEFT JOIN
`attribute_values` ON
`attribute_values`.`id`=`product_attributes_values`.`attribute_value_id`
WHERE ((attributes.id = 11 AND `attribute_values`.`id` IN (111)) OR
(`attributes`.`id` = 22 AND `attribute_values`.`id` IN (222)) ) GROUP
BY `products`.`id` LIMIT 10

try this one
SELECT
products.id,
products.name
FROM
`products`
LEFT JOIN `product_attributes_values`
ON `product_attributes_values`.`product_id`=`products`.`id`
LEFT JOIN `attributes`
ON `attributes`.`id`=`product_attributes_values`.`attribute_id`
LEFT JOIN `attribute_values`
ON `attribute_values`.`id`=`product_attributes_values`.`attribute_value_id`
WHERE
attributes.id = 11
OR attributes.id = 22
GROUP BY
products.id,
products.name
HAVING
MAX(CASE WHEN attributes.id = 11 THEN attribute_values.id ELSE NULL END) IN (111)
AND MAX(CASE WHEN attributes.id = 22 THEN attribute_values.id ELSE NULL END) IN (222)
LIMIT
10
if you know all your ids, then you don’t need all these joins
SELECT
p.id,
p.name
FROM
`products` as p
LEFT JOIN `product_attributes_values` as pav1
ON p.id = pav1.product_id
and pav1.attribute_id = 11
LEFT JOIN `product_attributes_values` as pav2
ON p.id = pav2.product_id
and pav2.attribute_id = 22
WHERE
pav1.attribute_value_id = 111
AND pav2.attribute_value_id = 222

It is kind of a relational division problem, here is one solution:
SELECT products.id, products.name
FROM products
WHERE EXISTS (
SELECT 1
FROM product_attributes_values
INNER JOIN attributes ON product_attributes_values.attribute_id = attributes.id
INNER JOIN attribute_values ON product_attributes_values.attribute_value_id = attribute_values.id
WHERE product_attributes_values.product_id = products.id AND (
(attributes.attribute_name = 'Manufacturer' AND attribute_values.value = 'Intel®') OR
(attributes.attribute_name = 'Type' AND attribute_values.value = 'Celeron®')
)
HAVING COUNT(*) = 2 -- this must match the number of conditions
)

Related

Using two aggregate values from a SELECT subquery in a single outer SELECT query

For relevant tables and columns (a lot more exist than the following), I have a customer table with cust_id and state
columns and an account table with account_id, cust_id, and avail_balance columns.
Example customer table:
| cust_id | state |
|--------:|-------|
| 1 | MA |
| 2 | MA |
| 3 | MA |
| 4 | MA |
| 5 | NH |
| 6 | MA |
| 7 | MA |
| 8 | NH |
| 9 | MA |
| 10 | NH |
| 11 | MA |
| 12 | NH |
| 13 | MA |
Example account table:
| account_id | cust_id | avail_balance |
|-----------:|--------:|--------------:|
| 1 | 1 | 1057.75 |
| 2 | 1 | 500 |
| 3 | 1 | 3000 |
| 4 | 2 | 2258.02 |
| 5 | 2 | 200 |
| 7 | 3 | 1057.75 |
| 8 | 3 | 2212.5 |
| 10 | 4 | 534.12 |
| 11 | 4 | 767.77 |
| 12 | 4 | 5487.09 |
| 13 | 5 | 2237.97 |
| 14 | 6 | 122.37 |
| 15 | 6 | 10000 |
| 17 | 7 | 5000 |
| 18 | 8 | 3487.19 |
| 19 | 8 | 387.99 |
| 21 | 9 | 125.67 |
| 22 | 9 | 9345.55 |
| 23 | 9 | 1500 |
| 24 | 10 | 23575.12 |
| 25 | 10 | 0 |
| 27 | 11 | 9345.55 |
| 28 | 12 | 38552.05 |
| 29 | 13 | 50000 |
Here is an example of a query that I wish to optimize.
SELECT account.cust_id
FROM account, customer
WHERE
account.cust_id = customer.cust_id
AND customer.state = 'MA'
AND customer.cust_id >
(SELECT MAX(customer.cust_id) AS max_nh_cust_id
FROM account, customer
WHERE
account.cust_id = customer.cust_id
AND state = 'NH')
AND account.avail_balance >
(SELECT MAX(avail_balance) AS max_nh_avail_balance
FROM account, customer
WHERE
account.cust_id = customer.cust_id
AND state = 'NH');
Expected result for the above example data (may not be just 1 result if data is different):
| cust_id |
|--------:|
| 13 |
The problem with the above is the code redundancy and how we have to iterate multiple times through the account table with the two subqueries. I was hoping that it would be possible to use one subquery to get the maximum cust_id and avail_balance from the account table and use those two scalars in the outer query. For example, the shape of the query might look something like this:
SELECT account.cust_id
FROM account, customer
WHERE
account.cust_id = customer.cust_id
AND (customer.cust_id > max_nh_cust_id AND account.avail_balance) > max_nh_avail_balance) IN
(SELECT MAX(customer.cust_id) AS max_nh_cust_id, MAX(avail_balance) AS max_nh_avail_balance
FROM account, customer
WHERE
account.cust_id = customer.cust_id
AND state = 'NH');
Obviously, the above does not work. What is the most efficient way of implementing something like the above with less code redundancy and only in one query (that may contain subqueries)?
You can merge the 2 subqueries into 1:
SELECT MAX(c.cust_id) AS max_nh_cust_id,
MAX(a.avail_balance) AS max_nh_avail_balance
FROM account a INNER JOIN customer c
ON a.cust_id = c.cust_id
WHERE c.state = 'NH'
and join it like this:
SELECT a.cust_id
FROM account a
INNER JOIN customer c ON a.cust_id = c.cust_id
INNER JOIN (
SELECT MAX(c.cust_id) AS max_nh_cust_id,
MAX(a.avail_balance) AS max_nh_avail_balance
FROM account a INNER JOIN customer c
ON a.cust_id = c.cust_id
WHERE c.state = 'NH'
) t ON c.cust_id > t.max_nh_cust_id AND a.avail_balance > t.max_nh_avail_balance
WHERE c.state = 'MA'
See the demo.
Results:
> | cust_id |
> | ------: |
> | 13 |

Multiplying quantity and amount in different tables in same database

these are my tables
ORDER_ITEMS
+----------+--------+-----------------+------------+
| order_id |item_id | item_name | quantity |
+----------+--------+-----------------+------------+
| 1 | 1 | coffee | 2 |
| 2 | 2 | shake | 2 |
| 2 | 3 | icecream | 3 |
+----------+--------+-----------------+------------+
PRODUCT_INGREDIENT:
+--------+-----------------+--------+
|item_id | ingredient_id | amount |
+--------+-----------------+--------+
| 1 | 123 | 10 |
| 1 | 124 | 15 |
| 2 | 125 | 10 |
| 2 | 124 | 15 |
| 2 | 123 | 10 |
| 2 | 126 | 15 |
| 3 | 124 | 15 |
| 3 | 123 | 10 |
| 3 | 126 | 15 |
+--------+-----------------+--------+
INVENTORY:
+--------+-----------------+--------+
| id | ingredient_id | amount |
+--------+-----------------+--------+
| 1 | 123 | 80 |
| 2 | 124 | 70 |
| 3 | 125 | 100 |
| 4 | 126 | 100 |
+--------+-----------------+--------+
the sql I have is working but I need to multiply the (amount)column in PRODUCT_INGREDIENT table to the column(quantity) in ORDER_ITEMS table
before
this is the sql statement
UPDATE inventory i
INNER JOIN (
SELECT p.ingredient_id, sum(p.amount) amount
FROM product_ingredient p
INNER JOIN order_items o on o.item_id = p.item_id
WHERE o.order_id = 1
GROUP BY p.ingredient_id
) p ON i.ingredient_id = p.ingredient_id
SET i.amount = i.amount - p.amount
I want my inventory to look like this after query
INVENTORY:
+--------+-----------------+--------+
| id | ingredient_id | amount |
+--------+-----------------+--------+
| 1 | 123 | 100 |
| 2 | 124 | 100 |
| 3 | 125 | 100 |
| 4 | 126 | 100 |
+--------+-----------------+--------+
Looking to your data sample seems you need add the result of the subquery
UPDATE inventory i
INNER JOIN (
select p.ingredient_id, sum(p.amount*o.quantity) amount
from product_ingredient p
INNER JOIN order_items o on o.item_id = p.item_id
WHERE o.order_id = 1
GROUP BY p.ingredient_id
) p ON i.ingredient_id = p.ingredient_id
SET i.amount = i.amount + p.amount
You can use such an UPDATE statement :
UPDATE inventory i
INNER JOIN (
SELECT p.ingredient_id, sum(p.amount*o.quantity) amount
FROM product_ingredient p
INNER JOIN order_items o on o.item_id = p.item_id
WHERE o.order_id = 1
GROUP BY p.ingredient_id
) p ON i.ingredient_id = p.ingredient_id
SET i.amount = i.amount + p.amount
Demo
Only ingredient_id 123 and 124 are matched for the whole query. So, only they're updated.

How to combine table records with some condition in SQL table?

I have six tables in the database, all tables are relative to each other and want to show records in one table.
Following are my tables:
1) mls_stores
*----------------------------*
| store_id | store_title |
*----------------------------*
| 1001 | ajmar-jaipur |
| 1002 | dwarka-delhi |
*----------------------------*
2) mls_category
*-------------------------------------------*
| cat_no | store_id | cat_value | cat_type |
*-------------------------------------------*
| 20 | 1001 | 1 | running |
| 21 | 1001 | 4 | cycling |
| 22 | 1002 | 1 | running |
| 23 | 1002 | 2 | swmining |
*-------------------------------------------*
3) mls_points_matrix
*----------------------------------------*
| store_id | value_per_point | maxpoint |
*----------------------------------------*
| 1001 | 1 | 10 |
| 1001 | 2 | 20 |
| 1002 | 1 | 20 |
| 1002 | 4 | 30 |
*----------------------------------------*
4) mls_user
*--------------------------*
| id | store_id | name |
*--------------------------*
| 1 | 1001 | sandeep |
| 2 | 1001 | jagveer |
| 3 | 1002 | gagan |
*--------------------------*
5) bonus_points
*---------------------------------------------------*
| user_id | store_id | bonus_points | bonus_type |
*---------------------------------------------------*
| 1 | 1001 | 10 | fixed |
| 3 | 1002 | 2 | % |
*---------------------------------------------------*
6) mls_entry
*-------------------------------------------------------*
| user_id | store_id | category | distance | status |
*-------------------------------------------------------*
| 1 | 1001 | 20 | 10 | approved |
| 1 | 1001 | 21 | 40 | approved |
| 1 | 1001 | 20 | 5 | reject |
| 2 | 1001 | 21 | 40 | approved |
| 3 | 1002 | 22 | 10 | approved |
| 3 | 1002 | 23 | 20 | approved |
*-------------------------------------------------------*
Now I want output as below:
*-----------------------------------------------------------------------------------*
| Name | Entries | Points Earned | Bonus Points | Total Points | Total Amount |
*-----------------------------------------------------------------------------------*
| Sandeep | running(1) | 20 | 10 | 30 | 60 |
| | cycling(1) | | | | |
*-----------------------------------------------------------------------------------*
| Jagveer | cycling(1) | 10 | 0 | 10 | 10 |
*-----------------------------------------------------------------------------------*
I am using following code:
SELECT
u.name,
ROUND(COALESCE(t1.points, 0)) AS points,
ROUND(COALESCE(b.bonus_points, 0)) AS bonus_points,
ROUND(COALESCE(t1.points, 0) + COALESCE(b.bonus_points, 0)) AS total_points
FROM mls_user u
LEFT JOIN
(
SELECT e.user_id, e.status, SUM(e.distance / c.cat_value) AS points
FROM mls_entry e
INNER JOIN mls_category c
ON e.store_id = c.store_id AND e.category = c.cat_no
GROUP BY e.user_id
HAVING e.status='approved'
) t1
ON u.id = t1.user_id
LEFT JOIN bonus_points b
ON u.id = b.user_id
WHERE u.store_id = '1001'
ORDER BY
total_points DESC
This SQL query giving me Point earned, bonus points and total points, But I am not able to find Entries And Total Amount and it is giving me wrong Point calculation for Sandeep, As per data one entry is rejected. so it should be 20, not 25.
My total amount will be for Sandeep 30X2(it is coming from point matrix) = 60
same like for jagveer, the total amount for jagveer 10X1 = 10.
I have created tables in DEMO
Try below :
SELECT
u.name,
ROUND(COALESCE(t1.points, 0)) AS points,
ROUND(COALESCE(b.bonus_points, 0)) AS bonus_points,
ROUND(COALESCE(t1.points, 0) + COALESCE(b.bonus_points, 0)) AS total_points,
ROUND(COALESCE(t1.points, 0) + COALESCE(b.bonus_points, 0)) * t1.countId as total_amount,
group_concat(t1.EntriesConcat) as Entries
FROM mls_user u
LEFT JOIN
(
SELECT e.user_id, e.status, SUM(e.distance / c.cat_value) AS points,
concat(c.cat_type, '(',count(e.user_id), ')' ) as EntriesConcat,
count(e.user_id) as countId -- it returns count of records according to group by part
FROM mls_entry e
INNER JOIN mls_category c
ON e.store_id = c.store_id AND e.category = c.cat_no
-- remove HAVING and use WHERE clause
WHERE e.status='approved'
GROUP BY e.user_id
) t1 ON u.id = t1.user_id
LEFT JOIN bonus_points b ON u.id = b.user_id
WHERE u.store_id = '1001'
ORDER BY total_points DESC
group_concat of mysql is useful to concatinating values on Group By

SQL Query: 3 tables highest Value plus rest of row plus

i'm currently stuck on a sql-query, trying to find a solution, but making me headache for 2 stays now. i've got 3 tables
user-table:
+-----+----------+-----------+
| pid | username | role |
+-----+----------+-----------+
| 1 | user1 | patient |
| 2 | user2 | patient |
| 3 | user3 | doc |
| 4 | user4 | assistant |
| 5 | user5 | patient |
+-----+----------+-----------+
base-dat:
+-----+---------+-------+------------+
| pid | surname | name | birthdate |
+-----+---------+-------+------------+
| 1 | smith | john | 1950-07-31 |
| 2 | jackson | sarah | 1948-08-15 |
+-----+---------+-------+------------+
med-dat:
+-----+-----+---------------+--------+--------+
| mid | pid | dateLastEntry | weight | pulse |
+-----+-----+---------------+--------+--------+
| 1 | 1 | 2017-12-01 | 86 | 65 |
| 2 | 1 | 2017-12-02 | 84 | 70 |
| 3 | 1 | 2017-12-03 | 80 | 67 |
| 4 | 2 | 2017-11-15 | 66 | 60 |
| 5 | 2 | 2017-11-17 | 60 | 64 |
+-----+-----+---------------+--------+--------+
I'm trying to get the max(dateLastEntry) for each user with role patient, showing their pid, name, surname, weight, pulse in a single row - , even if there is no med-data entry for the patient: something like this:
+-----+---------+-------+------------+--------+-------+
| pid | surname | name | lastEntry | weight | pulse |
+-----+---------+-------+------------+--------+-------+
| 1 | smith | john | 2017-12-02 | 84 | 70 |
| 2 | jackson | sarah | 2017-11-17 | 60 | 64 |
| 5 | NONE | NONE | NONE | NONE | NONE |
+-----+---------+-------+------------+--------+-------+
Atm my statement looks like this, but can't get the proper result:
select b.pid, s.surname, s.name, max(m.date) as lastEntry, m.weight, m.pulse
from users b
left join med-dat m on b.pid = m.pid
left join base-dat s on m.pid = s.pid
where b.role = 'Patient'
group by b.pid, s.surname, s.name, m.weight;
You can rewrite your query as below to get the desired output
select b.pid, s.surname, s.name, m.dateLastEntry as lastEntry, m.weight, m.pulse
from users b
left join med_dat m on b.pid = m.pid
left join base_dat s on m.pid = s.pid
left join med_dat m1 on m.pid = m1.pid
and m.dateLastEntry < m1.dateLastEntry
where m1.pid is null
and b.role = 'Patient'
DEMO
Edit from comment, join base_dat using pid from user table
select b.pid, s.surname, s.name, m.dateLastEntry as lastEntry, m.weight, m.pulse
from users b
left join med_dat m on b.pid = m.pid
left join base_dat s on b.pid = s.pid
left join med_dat m1 on m.pid = m1.pid and m.dateLastEntry < m1.dateLastEntry
where m1.pid is null
and b.role = 'Patient'
group by b.pid, s.surname, s.name, m.weight;
Demo

MySQL, SELECT column value when using GROUP BY and HAVING

I want to SELECT the asset_id, category_name, and Fixture ID for assets with 2 or less properties (2 or less rows in asset_property table). So far all I have is the asset_id and category_name. The Fixture ID may not exist for all assets. If it does, it would be the property_value from a row with property_id 1
EXAMPLE OF DESIRED RESULTS
asset_property table:
| asset_id | property_id | property_value |
|:--------:|:-----------:|:---------------|
| 100 | 1 | A-6 |*
| 100 | 6 | Blue |
| 104 | 1 | CC-7 |*
| 104 | 6 | Blue |
| 104 | 4 | 12" |
| 105 | 1 | B-1 |*
| 108 | 1 | HR-1 |*
| 109 | 3 | 500 |
| 109 | 4 | 1" |
*property_id 1 is the Fixture ID
Desired results:
| asset_id | category_name | property_value*|
|:--------:|:-------------:|:---------------|
| 100 | Flooring | A-6 |
| 104 | Flooring | CC-7 |
| 105 | Kitchen Equip | B-1 |
| 108 | Plumbing | HR-1 |
| 109 | Plumbing | |
*property_value is the Fixture ID if the property_id is 1
MY MYSQL SO FAR
MySQL:
SELECT asset_property.asset_id, category_name
FROM asset_property
LEFT JOIN asset
ON asset.asset_id = asset_property.asset_id
LEFT JOIN category
ON category.category_id = asset.category_id
GROUP BY asset_property.asset_id
HAVING COUNT(asset_property.asset_id) <= 2
Results from MySQL:
| asset_property.asset_id | category_name |
|:-----------------------:|:--------------|
| 3048 | Parking Lot |
| 3519 | Kitchen |
| 3522 | Kitchen |
| 3597 | Flooring |
(etc...)
So I need to also SELECT the property_value WHERE the property_id = 1. I hope this makes sense, Thanks!
Maybe switch/case would be appropriate for this query
SELECT asset_property.asset_id, category_name,
( CASE property_id
WHEN 1 THEN property_value
ELSE 0
END
)
FROM asset_property
LEFT JOIN asset
ON asset.asset_id = asset_property.asset_id
LEFT JOIN category
ON category.category_id = asset.category_id
GROUP BY asset_property.asset_id
HAVING COUNT(asset_property.asset_id) <= 2
Hope that helps..