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

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.

Related

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

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.

How to group by relation property b if property a is null

I have a table containing items, each item belongs to an store, e.g:
id | name | store_id | price
1 | hat | 1 | 110
2 | bag | 1 | 120
3 | coat | 2 | 130
A Store can be canonical or a duplicate. A canonical Store has canonical_id equal to null, and a duplicate Store has canonical_id equal to the ID of the canonical Store, e.g:
id | name | canonical_id
1 | NYC | null
2 | Bronx | 1
I need to group items by their Store to get the total stock value of all items at the store, e.g:
SELECT store_id, SUM(price) as `stock_value` FROM items GROUP BY store_id
This would produce 2 results, Store 1 has a stock_value of 230 and Store 2 has a stock_value of 130.
Because Store 2 is a duplicate of Store 1 the items from Store 2 should be included in the total for Store 1. The goal is for this example to provide a single result of 360.
I think the correct implementation would involve some sort of join which retrieves the Store ID from stores by using IFNULL to get either the canonical_id or id after selecting based on id from the items table, but I'm struggling to implement a solution that works.
Any pointers would be greatly appreciated, thank you.
Edit: my attempt is as follows, it appears to meet my needs, are there any caveats / issues with my approach?
SELECT SUM(price) as `stock_value`, IFNULL(stores.canonical_id, store_id) as `store`
FROM items
JOIN stores on stores.id = items.store_id
GROUP BY store
I just realize you don't wanted to keep the stock_values of the "children" or "related" stores. However the next approach take those into account too:
SELECT
s.*,
(SELECT
SUM(i.price)
FROM
items AS i
INNER JOIN
stores AS s1 ON s1.id = i.store_id
WHERE
s1.cannonical_id = s.id
OR
s.id = i.store_id) AS "stock_value"
FROM
stores AS s
Online example: DB-Fiddle
If you don't want they, you just could filter the previous query using the condition WHERE s.cannonical_id is NULL like this:
SELECT
s.*,
(SELECT
SUM(i.price)
FROM
items AS i
INNER JOIN
stores AS s1 ON s1.id = i.store_id
WHERE
s1.cannonical_id = s.id
OR
s.id = i.store_id) AS "stock_value"
FROM
stores AS s
WHERE
s.cannonical_id is NULL
But, you should note that the query you posted on the updated question will be better in performance than this approach.

MySQL - select product_id from 2 different tables and group results

situation:
table 1 - #__virtuemart_products
virtuemart_product_id | product_special
PRODUCTS_IDS | 0 or 1
table 2 - #__virtuemart_product_badges
virtuemart_product_id | product_badge
PRODUCTS_IDS | for this situation code 3
I have a default SQL
SELECT p.`virtuemart_product_id`
FROM `#__virtuemart_products` as p
WHERE p.`product_special` = 1;
results is product IDs like 2,3,225,...
I need modify this SQL syntax for select IDs from 2 different tables and return one column.
If I modify syntax like that:
SELECT p.`virtuemart_product_id`, badges_table.`virtuemart_product_id`
FROM `#__virtuemart_products` as p, `#__virtuemart_product_badges` as badges_table
WHERE p.`product_special` = 1 OR badges_table.`badge` = 3
Result is:
virtuemart_product_id | virtuemart_product_id
1 | 123
1 | 321
1 | 231
....
why is first column 1,1,1,...? here must be product_id, no product_special code
I need group this results into one column virtuemart_product_id
What I doing wrong?
I think what you are looking for is UNION of the IDs fetched from two different tables.
SELECT p.`virtuemart_product_id`, badges_table.`virtuemart_product_id`
FROM `#__virtuemart_products` as p, `#__virtuemart_product_badges` as
badges_table
WHERE p.`product_special` = 1 OR badges_table.`badge` = 3
What the above query is doing is, it is performing a join between the two tables with the condition that product_special should be 1 or badge should be 3. Hence, each row from one table will be joined with each row of the other table where the condition will satisfy.
To get IDs from both the tables you can get the results from each table according to condition and then perform a UNION on them. So for example
(SELECT `virtuemart_product_id` FROM `#__virtuemart_products` WHERE
`product_special` = 1)
UNION
(SELECT `virtuemart_product_id` FROM
`#__virtuemart_product_badges` WHERE `badge` = 3)
I hope this helps.

MySQL left join COUNT() and SUM() with CASE from same table

I have the following tables in a succession of 1-to-many relationships:
company_company, company_portfolio, building_site and statistics_meter. The area of difficulty I am having is the final table, statistics_meter.
For the benefit of this exercise, it's structure is as follows:
Records are related within the same table, with some being parent meters, and some being child meters. Where a record is a child, it will have parent_meter_id set, and building_id, which crucially, is how the table is LEFT JOIN'ed set to NULL.
id | parent_meter_id | site_ref | building_id
1 | NULL | some building | 45
2 | NULL | some other building | 45
3 | 1 | and another | NULL
4 | 1 | one another one | NULL
5 | 2 | final one | NULL
I have two requirements:
1 - count the number of parent meters where the building_id is set (which I am doing successfully)
2 - count the number of meters where the parent_meter_id matches the meter_id of those counted in (1)
Thus I would expect a result whereby (1) = 2 and (2) = 3.
Here is the SQL I've got so far...I tried fiddling around with a SUM case when but I think it's totally wrong. Is this even possible within one query?
Thanks for the help.
SELECT
building_site.id as site_id,
building_site.site_ref as building_name,
COUNT(statistics_meter.id) AS meter_count,
SUM(CASE WHEN statistics_meter.parent_meter_id = [???] THEN 1 ELSE 0 END) AS check_meter_count
FROM company_company
LEFT JOIN company_portfolio ON company_portfolio.company_id=company_company.id
LEFT JOIN building_site ON building_site.portfolio_id=company_portfolio.id
LEFT JOIN statistics_meter ON statistics_meter.building_id=building_site.id
WHERE company_company.id=41
GROUP BY building_site.id
Well if I understand you, you'll need to use a subquery to get the parent meters with a building id, and then join that to your main table.
SQL Fiddle
select
sm.id,
sm.parent_meter_id,
sm2.id as ID2,
sm.site_ref,
sm.building_id
from
statistics_meter sm
inner join (
select
id,
parent_meter_id
from
statistics_meter
where
building_id is not null) sm2
on sm.parent_meter_id = sm2.id
Not sure if this is the most efficient way to do it, but in the end I performed a left join and subquery as below and performed two counts, one COUNT() for total number to answer my requirement (2) and a COUNT(distinct) to answer my requirement (1)
SELECT
count(distinct statistics_meter.id) as meter_count,
count(statistics_meter.id) as check_meter_count
FROM company_company
LEFT JOIN company_portfolio ON company_portfolio.company_id=company_company.id
LEFT JOIN building_site ON building_site.portfolio_id=company_portfolio.id
LEFT JOIN statistics_meter ON statistics_meter.building_id=building_site.id
LEFT JOIN (select * from statistics_meter where parent_meter_id is not NULL) sm2 on sm2.parent_meter_id = statistics_meter.id

How to find all products with some properties?

I'm listing product properties in a MySQL table where each row contains a product ID prod and a property ID prop. If a product has three properties, this results in three rows for that product. Example table:
prod | prop
-----+-----
1 | 1
2 | 1
2 | 2
2 | 3
3 | 2
3 | 4
How can I find which products have both properties #1 and #2 (product #2)?
The only way that I can think of is one select and inner join per property, but I think that would be very inefficient. It's a search function for a website and has to work for 10k lines in the table and 10 requested properties.
SELECT prod
FROM tbl
WHERE prop IN (1, 2)
GROUP BY prod
HAVING COUNT(*) = 2
And if there will be always 2 properties to find - then INNER JOIN would be a bit more efficient:
SELECT t1.p
FROM tbl t1
INNER JOIN tbl.t2 ON t2.prod = t1.prod
AND t2.prop = 2
WHERE t1.prop = 1
The recommended index for this query to be efficient as much as possible is a compound one (prop, prod)