Joining multiple columns into one with union, exclude results with same id - mysql

I want to join columns from multiple tables to one column, in my case column 'battery_value' and 'technical_value' into column 'value'. I want to fetch data for only given category_ids, but because of UNION, I get data from other tables as well.
I have 4 tables:
Table: car
car_id model_name
1 e6
Table: battery
battery_category_id car_id battery_value
1 1 125 kW
Table: technical_data
technical_category_id car_id technical_value
1 1 5
3 1 2008
Table: categories
category_id category_name category_type
1 engine power battery
1 seats technical
3 release year technical
From searching, people are suggesting that I use union to join these columns. My query now looks like this:
SELECT CARS.car_id
category_id,
CATEGORIES.category_name,
value,
FROM CARS
left join (SELECT BATTERY.battery_category_id AS category_id,
BATTERY.car_id AS car_id,
BATTERY.value AS value
FROM BATTERY
WHERE `BATTERY`.`battery_category_id` IN (1)
UNION
SELECT TECHNICAL_DATA.technical_category_id AS category_id,
TECHNICAL_DATA.car_id AS car_id,
TECHNICAL_DATA.value AS value
FROM TECHNICAL_DATA
WHERE `TECHNICAL_DATA`.`technical_category_id` IN (3))
tt
ON CARS.car_id = tt.car_id
left join CATEGORIES
ON category_id = CATEGORIES.id
So the result I want is this, because I only want to get the data where category_id 1 is in battery table:
car_id category_id category_name technical_value
1 1 engine power 125 kW
1 3 release year 2008
but with the query above I get this, category_id 1 from technical table is included which is not something I want:
car_id category_id category_name value
1 1 engine power 125 kW
1 1 seats 125 kW
1 3 release year 2008
How can get exclude the 'seats' row?

For the results you want, I don't see why the cars table is needed. Then, you seem to need an additional key for the join to categories based on which table it is referring to.
So, I suggest:
SELECT tt.*, c.category_name
FROM ((SELECT b.battery_category_id AS category_id,
b.car_id AS car_id, b.value AS value,
'battery' as which
FROM BATTERY b
WHERE b.battery_category_id IN (1)
) UNION ALL
(SELECT td.technical_category_id AS category_id,
td.car_id AS car_id, td.value AS value,
'technical' as which
FROM TECHNICAL_DATA td
WHERE td.technical_category_id IN (3)
)
) tt LEFT JOIN
CATEGORIES c
ON c.id = tt.category_id AND
c.category_type = tt.which;
That said, you seem to have a problem with your data model, if the join to categories requires "hidden" data such as the type. However, that is outside the scope of the question.

Related

Select three records in a single query of a cross table that contains records of two tables - MYSQL

I have a main table called "product" that is linked with three tables:
"product_type",
"feature",
"type_feature"
and a cross table called "product_feature" that contains several features of the same product.
Example one record:
I have something similar like this:
product_type
id_product_type name
1 Phone
feature
id_feature name
1 Memory
2 Color
3 Memory Ram
feature_type
id_feature_type id_feature value
1 1 16GB
2 1 32GB
3 2 Blue
4 2 Black
5 3 2GB
6 3 3GB
product
id_product id_product_type price quantity model
1 1 100$ 5 Moto-G7
Cross table "product_feature" (linked to "product", "feature" and "feature_type"):
id_feature id_product id_feature_type
1 1 1
2 1 3
3 1 6
I want query show this:
id_product type_name price quantity model feature_name value
1 Phone 100$ 5 Moto-G7 Memory 16GB
feature_name2 value2 feature_name3 value3
Color Blue Memory Ram 3GB
I tried this, but only I have only one feature_name and value, and I need three:
SELECT p.id_product, pt.name, model, price, quantity, f.name AS feature, ft.value
FROM product p
LEFT JOIN product_type pt ON pt.id_product_type = p.id_product_type
LEFT JOIN product_feature pf ON pf.id_product = p.id_product
LEFT JOIN feature f ON f.id_feature = pf.id_feature
LEFT JOIN feature_type ft ON ft.id_feature_type = pf.id_feature_type
GROUP BY p.id_product;
Try this query, It will give you the results:
SELECT product.*, product_type.name as TypeName, (SELECT CONCAT( GROUP_CONCAT(feature_type.value), "|", GROUP_CONCAT(feature.name) ) FROM `product_feature` INNER JOIN feature_type on product_feature.id_feature_type = feature_type.id_feature_type INNER JOIN feature on feature_type.id_feature = feature.id_feature WHERE product_feature.id_product=1 GROUP BY product_feature.id_product) as options FROM `product` LEFT JOIN product_type on product.id_product_type = product_type.id_product_type
You will get options for your product and you have to explode() those options by "|" and you can count how many options you have by using $count= count(explode("|", $options))
By this way you can get all options .

MySQL SELECT WHERE OR - pick one line only if second is empty

I'm joining 2 tables, for example
PRODUCTS (PRODUCT_ID, NAME)
and
PRICE_LEVELS (PRODUCT_ID, PRICE, PRICE_LVL_NAME )
(ofc. it's simplified, there is several joins ).
in the PRICE_LEVELS table, I have some possibilities of price level names, let's say "DEFAULT" and LEVEL1, so we ended up with something like:
PRODUCT_ID | PRICE | PRICE_LVL_NAME
1 | 100 | _DEFAULT_
1 | 50 | LEVEL1
2 | 130 | _DEFAULT_
Both tables are joined in the view.
What I need is to get price, but only once - I mean if there is LEVEL1 defined, pick that one, otherwise pick DEFAULT.
Meantime, I have used GROUP BY and thing seems to work, but I have no idea why (ofc. I've used a lot of test data and it's simply always works, but not sure, how it's reliable).
Let's say our view (combining both tables) has name V_PRODUCTS, so I'm running query:
SELECT *
FROM `V_PRODUCTS`
WHERE (PRICE_LVL_NAME = '_DEFAULT_' OR PRICE_LVL_NAME = 'LEVEL1')
GROUP BY `PRODUCT_ID `;
So the questions are:
Why the query above works ? GROUP BY is always choosing LEVEL1, if is available and DEFAULT if not. It's exactly what I need, but need to understand why it's working this way.
Is there any way how to do this more explicit in the SQL ?
UPDATE: there is unlimited number of possible levels
Q1: It's not reliable, it's probably based on internal storage and might change at any time.
Q2: Join the table twice using an Outer Join and return the best match using COALESCE:
SELECT ..., COALESCE(pl1.PRICE, pl2.PRICE)
FROM `V_PRODUCTS` as p
LEFT JOIN PRICE_LEVELS as pl1
ON p.PRODUCT_ID = pl1.PRODUCT_ID
and PRICE_LVL_NAME = 'LEVEL1')
LEFT JOIN PRICE_LEVELS as ply
ON p.PRODUCT_ID = pl2.PRODUCT_ID
and PRICE_LVL_NAME = '_DEFAULT_'
I don't have a MySQL to play with right now but you should only have to replace the IS NULL part of this MSSQL to make it work I think.
If you don't need AlphaNum in the levels or can add a level field that is INT where 0 is default and then 1 and so on, you can join the levels to itself to get the max available level to join to the product table.
SELECT PRODUCTS.*, PriceLvl.*
FROM [PRODUCTS] LEFT JOIN
(SELECT p1.*
FROM PRICE_LEVELS p1
LEFT JOIN PRICE_LEVELS p2 ON
(p1.PRODUCT_ID = p2.PRODUCT_ID
AND p2.PRICE_LVL > p1.PRICE_LVL)
WHERE p2.PRICE_LVL IS NULL) PriceLvl
ON PRODUCTS.PRODUCT_ID = PriceLvl.PRODUCT_ID
That takes
PRODUCT_ID NAME
1 Tshirts
2 Pants
and
PRODUCT_ID PRICE PRICE_LVL
1 10.00 0
1 15.00 1
2 40.00 0
1 20.00 2
and gives
PRODUCT_ID NAME PRODUCT_ID PRICE PRICE_LVL
1 Tshirts 1 20.00 2
2 Pants 2 40.00 0
I'd assign a numeric value for each level with zero for default, and store it same table.
select t1.price from price_levels t1
where t1.product_id = #X and price_level = (select max(price_level)
from price_levels t2 where t1.product_id = t2.product_id)
WHERE price_level IN ('_DEFAULT_', 'mylevel')
ORDER BY
price_level = '_DEFAULT_'
LIMIT 1
The WHERE will grab 1 or 2 rows.
price_level = '_DEFAULT_' will be 1 for default, otherwise 0. 0 sorts before 1. So, if there is a 'mylevel', it will come first.
LIMIT 1 says to pick the first (or only) row.

mysql search keywords in string table

I have a catID column in product table, it is contain category ids as string,
Something like that '142,156,146,143'
and i Have a query '?catID=156,141,120'
i want to search each id in catID column.
I use this query:
SELECT * FROM product WHERE catID REGEXP '156|141|120'
this code return products which have any id in catID column , but I want to return products which is have all id,
So , I'am looking for and operator in REGEXP , but I'am couldn't find.
I want to use REGEXP or something like that which function provide to find product with one query , I don't wan to use
catID LIKE '156' AND catID LIKE '141' ....
if it is posibble.
EDIT : I don't want to perform a function one more time , because the query can be have 100+ id so it's make more harder to write code,
You need to use find_in_set() for each category id parameter in order to find the values in set,also if you can alter the schema then do normalize it, by having another junction table which holds the relation from this table to category table
select * from
product
where
find_in_set('142',catID ) > 0
For multiple values like find_in_set('161,168,234,678',preferred_location ) > 0 no it can't be possible doing like this you have to perform for each location id like
select * from
product
where
find_in_set('142',catID ) > 0
and find_in_set('156',catID ) > 0
and find_in_set('146',catID ) > 0
and find_in_set('143',catID ) > 0 ... for more
Database normalization
find_in_set
Sample Schema
Table
Products (id,other columns)
Categories (id,other columns)
Product_categories (id,product_id,category_id)
Product_categories is a junction table which will hold product_id and one category_id per each product so each will have a relation with single category and single product at a time
For example
Products
id name
1 product 1
2 product 2
Categories
id name
142 category 1
156 category 2
146 category 3
143 category 4
Product_categories
id product_id category_id
1 1 142
2 1 156
3 1 146
4 1 143
Now you can join these tables and query like below using in() and count should be equal to the no of category ids provided as parameter
select p.* from
Products p
join Product_categories pc on (p.id = pc.product_id)
where pc.category_id in(142,156,146,143)
group by p.id
having count(distinct pc.category_id) = 4
Sample Demo
or if you can't count the provided category ids as parameter you can do this by following query
select p.* from
Products p
join Product_categories pc on (p.id = pc.product_id)
where pc.category_id in(142,156,146,143)
group by p.id
having count(distinct pc.category_id) =
ROUND (
(
LENGTH('142,156,146,143')
- LENGTH( REPLACE ( '142,156,146,143', ",", "") )
) / LENGTH(",")
) + 1
Sample Demo 2

Querying SQL data using three tables

I am trying to retrieve the CategoryID and CategoryName by seeing the CategoryBusinessMapping and Review Rating table. I am trying to retrieve the data of following Category table:
Category ParentCategoryID CategoryName
1 null Education
2 1 School
3 null Health
4 3 Doctors
5 1 Colleges
I have the Business table which has BusinessID and BusinessName and BusinessDescription like this:
BusinessID BusinessName BusinessDescription
YP00001 XYZ ABCD
YP00002 ABC XYZA
I have the CategoryBusinessMapping table like this:
MappingID CategoryID BusinessID
1 1 YP00001
2 2 YP00001
3 5 YP00001
4 3 YP00002
5 4 YP00002
I have this mapping table to map the different Category with the Business. I also have the Rating table like this:
RatingID BusinessID
1 YP00001
2 YP00001
3 YP00001
4 YP00002
5 YP00002
Here in this table I am assuming that a record having same BusinessID is fall under most popular Business. Meaning, here in above the Business ABCD having ID = YP00001 has four records in Rating table. Therefore it falls under most popular Business. Similarly YP00002 falls next to YP00001. By seeing the most popular Business in descending order I want to retrieve CategoryName and CategoryID. I have tried this to retrieve from the Rating table only:
select Distinct ReviewRating.BusinessID
,Count(*)as Rating
from YP.utblYPReviewRatingDtls as ReviewRating
group by ReviewRating.BusinessID
order by Rating desc
I have tried this:
SELECT distinct c.CategoryName, b.BusinessID
FROM Category c
INNER JOIN categoryBusinessMapping cbm
ON (c.CategoryID=cbm.CategoryID)
INNER JOIN Business b
ON (cbm.BusinessID=b.BusinessID)
LEFT JOIN Rating r
ON (cbm.BusinessID=r.BusinessID)
where c.ParentCategoryID is null
but I get the result which is redundant. I also remove the BusinessID from the query and I get the result but the result is incorrect. How can I remove redundancy and also get the proper output?
Use join and take the count of BusinessID from rating table and order your results
SELECT c.*, COUNT(r.BusinessID) AS bcount FROM Category c
INNER JOIN CategoryBusinessMapping cbm ON (c.Category=cbm.CategoryID)
INNER JOIN Business b ON (cbm.BusinessID=b.BusinessID)
LEFT JOIN Rating r ON (cbm.BusinessID=r.BusinessID)
GROUP BY r.BusinessID
ORDER BY bcount DESC

MySQL Retrieve Lowest Value in Multi-table Query

My goal is to retrieve the recorded purchase price for an item on an accepted purchase order.
Purchase_Orders table contains metadata for the order, such as the order number and its status (e.g., 1 for accepted, 0 for declined).
Purchase_Ord_Contents table contains contents records, which are linked via foreign key to the parent purchase order on a shared index order_number)
For example: I have two orders in my database, one has been accepted and the other has been declined. The data is represented as follows:
=========================================
PURCHASE_ORDERS TABLE
=========================================
id | order_number | order_status
-----------------------------------------
1 PO_100 0
2 PO_101 1
3 PO_102 1
===================================================
PURCHASE_ORD_CONTENTS TABLE
===================================================
id | order_number | purchase_price | sku
---------------------------------------------------
1 PO_100 1.50 APPLE
2 PO_100 1.50 ORANGE
3 PO_101 2.00 APPLE
4 PO_101 2.00 ORANGE
5 PO_102 1.75 BANANA
The query should return rows 3, 4 and 5, since PO_101 was accepted, whereas PO_100 was declined and row 5 is not only the only record for the given SKU, it was also on an accepted order. I've tried a few different approaches, but I always seem to end up either leaving out parts that were on an unaccepted Purchase Order, or retrieving the wrong order_number for the lowest purchase_price.
Here is what I have thus far (not working properly)
SELECT a.*
FROM purchase_ord_contents AS a
JOIN (SELECT sku,
MIN(purchase_price) AS min_price
FROM purchase_ord_contents
GROUP BY sku) AS b
ON ( a.sku = b.sku
AND a.purchase_price = b.min_price )
WHERE a.order_number
IN (
SELECT order_number
FROM purchase_orders
WHERE order_status != 0
)
This query successfully returns the records from the purchase_ord_contents table, however it omits records of the lowest purchase_price that were on a Purchase Order with an order_status of 0.
Any guidance would be greatly appreciated, I am not very well versed in "advanced" SQL queries as you have probably determined by now. Thank you for your time and please do not hesitate to ask if I should provide any further information.
This could be what you are looking for:
SELECT sku, purchase_price, order_number
FROM (
SELECT MIN(purchase_price) AS purchase_price, sku
FROM purchase_ord_contents
JOIN purchase_orders USING (order_number)
WHERE purchase_orders.order_status = 1
GROUP BY sku
) AS min_sku_price -- this is the lowest sale price for each SKU
JOIN purchase_ord_contents USING (sku, purchase_price) -- gets all orders having sold a SKU at its lowest price
JOIN purchase_orders USING (order_number)
WHERE purchase_orders.order_status = 1
Notice this will return several rows for one given SKU if the lowest price for this SKU was offered in several orders.
If I understand correctly I think you want this:
SELECT po.order_number, poc.sku, min(poc.purchase_price)
FROM purchase_orders AS po
JOIN purchase_ord_contents AS poc ON poc.order_number = po.order_number
WHERE po.order_status != 0
GROUP by po.order_number, poc.sku
order by po.order_number, poc.sku