better way to implement the following operations in MySQL? - mysql

I get a list of options with price like the following:
(it's the result from a select query sort by price asc)
price color quanlity
o_id
1 2 R medium
3 3 G bad
4 4 G good
5 6 B good
2 8 R medium
Now I need to pair those options according to requirements:
e.g. if I need 2 R(red) and 4 G(green)
I'd like to return a list of possible combinations (sort by price asc), like:
R(2) G(4)
c_id o_id o_id total price
1 1 3 16
2 1 4 20
3 2 3 28
4 2 4 32
My current solution for this is to make multiple queries to the DB:
(I'm using Java at the application layer / back end.)
select distinct colors, and store it in a List
In a For loop, select options of each color into a different temp table
join the List of Tables, and calculate the total, sort by total.
But is there a way to condense the above operations into a stored procedure or something more elegant?

You just need a simple self-join:
SELECT R.o_id AS R_id, G.o_id AS G_id, 2*R.price + 4*G.price AS total
FROM mytable R JOIN mytable G ON R.color = 'R' AND G.color = 'G'
ORDER BY total
See it on sqlfiddle.

Related

Count Category products with mysql

I have to count products inside each category like I have a category A that have two childs B and C.
In B category have 10 products and C category have 5 Products.
I want to write a query that count product like A = 15 B = 10 C = 5.
I have wrote a query that count only for child categories.
My query is given below
SELECT
c.`id`,
c.`parent_id`,
c.`name`,
c.`slug`,
c.`route_id`,
c.`description`,
c.`excerpt`,
c.`sequence`,
c.`image`,
c.`seo_title`,
c.`meta`,
c.`enabled`,
(SELECT
COUNT(`product_id`)
FROM
HM_category_products
WHERE
HM_category_products.category_id IN (SELECT
HM_categories.`id`
FROM
HM_categories
WHERE
(id = c.`id` OR id = c.`parent_id`))) AS count
FROM
`HM_categories` AS c
EDITED :- Below is my tables structure. This is just an example.
Category Table
id parent_id name slug enabled
1 0 Mobiles & Tablets mobiles-tablets 1
2 1 Mobile Phones mobile-phones 1
3 1 Tablets tablets 1
4 1 Mobile Accessories mobile-accessories 1
5 0 Electronics & Computers electronics-computers 1
6 5 Cameras & Accessories cameras-accessories 1
7 5 TV - Video - Audio tv-video-audio 1
Category Product Table
product_id category_id
1 2
2 2
3 2
4 2
5 3
6 2
7 3
8 3
9 3
10 2
11 3
12 2
13 2
14 2
15 2
You hav to use the "Group by" with something like:
SELECT
category_id, COUNT(*)
FROM
HM_category_products
GROUP BY category_id
This would get your table HM_category_products Grouped by category_id so will know how mane rows of each category its in the table, whats mean you will know the number of product of each category. Then you can join this resulting table with category one to get the category info. (Sorry for my english)
The GROUP BY statement is used in conjunction with the aggregate
functions to group the result-set by one or more columns. SQL GROUP BY Statement
for more info abaut "Group by" read
12.16.2 GROUP BY Modifiers and
12.16.1 GROUP BY (Aggregate) Functions
EDIT: I see now what you whant. I already give you the direction but here its a step furder: DISCLAIMER: THIS QUERY ITS JUST EXAMPLE, IT DOSNT WORK
CREATE VIEW view_2 AS
SELECT
category_id, COUNT(*) AS product_sum
FROM
HM_category_products
GROUP BY category_id
CREATE VIEW view_1 AS
SELECT
*
FROM
HM_categories
LEFT JOIN
view_2 AS a ON HM_categories.id = a.category_id;
SELECT
id,
name,
(SELECT
SUM(product_sum)
FROM
view_1
WHERE
parent_id = final.id) as product_count
FROM
HM_categories AS final;
that way you will get the product of a parent category, your only missing a CASE on the last select "product_count" to the child categorys, but that sum its easy because you already hav it on the view_1 (again sorry for my english)

MySQL order by points from 2nd table

So I have MySQL 3 tables, items (which in this case are lodging properties and the data is simplified below), amenities that the properties might offer, and amenities_index which is a list of item ids and amenity ids for each amenity offered. The end user can select any number of amenities they want and I want to return the results in order of the number of amenities that match what they are looking for. So, if they search for 3 different amenities, I want the items listed that offer all 3, then those that offer 2, 1 and finally the rest of the items. I have a query that I think is working for getting the results in the correct order, but I was hoping that I could also return a point value based on the matches, and that's where I'm running into trouble. My SQL skills are a bit lacking when it comes to more complex queries.
Here is an example query I have that returns the results in the correct order:
SELECT * FROM items
ORDER BY
(
SELECT count(*) AS points
FROM `amenities_index`
WHERE
(amenity_id = 1 || amenity_id = 2)
AND amenities_index.item_id = items.id
) DESC
And here is what the tables are structured like. Any help is appreciated.
items table
id name
1 location 1
2 location 2
3 location 3
4 location 4
amenities table
id name
1 fireplace
2 television
3 handicapped accessible
4 kitchenette
5 phone
amenities_index
item_id amenity_id
1 2
1 3
1 5
2 1
2 2
2 6
3 2
3 3
3 4
3 5
You want to move your expression into the select clause:
SELECT i.*,
(SELECT count(*) AS points
FROM `amenities_index` ai
WHERE amenity_id in (1, 2) AND
ai.item_id = i.id
) as points
FROM items i
ORDER BY points desc;
You can also do this as a join query with aggregation:
SELECT i.*, ai.points
FROM items i join
(select ai.item_id, count(*) as points
from amenities_index ai
where amenity_id in (1, 2)
) ai
on ai.item_id = i.id
ORDER BY ai.points desc;
In most databases, I would prefer this version over the first one. However, MySQL would allow the first in a view but not the second, so it has some strange limitations under some circumstances.

Aggregation and finding mode of data set

I am aggregating data and I cannot sum certain columns so I would like to take the most frequent observation from that column, or the mode value. Each ID can have only one site and number, so if there are ties then pick the smaller of the two numbers.
Example follows:
ID site number
1 3 45
1 3 45
1 2 56
1 3 56
2 4 5
2 5 5
2 5 3
2 5 5
I want it to look like:
ID site number
1 3 45
2 5 5
Here's one way of doing it:
with aggregation as
(
select id
, site
, number
, numberCount = count(1)
from SiteNumbers
group by id
, site
, number
), aggregateRanks as
(
select *
, idRank = row_number() over (partition by id order by numberCount desc, number, site)
from aggregation
)
select id
, site
, number
from aggregateRanks
where idRank = 1
SQL Fiddle with demo.
It matches your results, but depending on all your different cases might need some tweaking; hopefully it gives you some ideas.

Complex query involving timestamps

I'm having some trouble with a complex query involving the following tables. Assume time is using the built-in sqlite timestamp datatype.
I am trying to return the customers whose 2nd purchase is within 4 hours of their first purchase AND if it's within 2 hours it must be from a different store.
I'm having trouble wrapping my head around how to refer to the specific rows to compare a first purchase with a second purchase.
purchases
purchase_id | customer_id | store_id | purchase_time
1 1 1 2009-01-27 10:00:00.0
2 1 2 2009-01-27 10:30:00.0
3 2 1 2009-01-27 10:00:00.0
4 2 1 2009-01-27 10:30:00.0
5 3 1 2009-01-27 10:00:00.0
6 3 2 2009-01-27 16:00:00.0
7 4 3 2009-01-27 10:00:00.0
8 4 3 2009-01-27 13:00:00.0
stores
store_id | misc columns...
1
2
3
customers
customer_id | f_name
1 name1
2 name2
3 name3
4 name4
The correct return would be name1, name4 in this case.
You're going to be joining the purchase table to itself, and then selecting on one of the two criteria.
The only real trick here is to formulate the different time criteria as:
Purchases that were made < 2 hours at different stores.
Purchases that were made between 2 and 4 hours, independent of store_id.
Both of which obviously apply for the same customer_id.
So, we've got:
select p1.purchase_id purchase_1,
p2.purchase_id purchase_2,
c.name,
p1.customer_id customer
from purchases p1
join purchases p2 on
p1.customer_id = p2.customer_id
join customer c on c.customer_id = p1.customer_id
where p1.purchase_time < p2.purchase_time
and (
(
addtime(p1.purchase_time,'2:00:00') >= p2.purchase_time
and p1.store_id <> p2.store_id
)
or
(
addtime(p1.purchase_time,'2:00:00') < p2.purchase_time
and addtime(p1.purchase_time,'4:00:00') >= p2.purchase_time
)
)
Which joins purchases to itself by customer_id, first checks that you're comparing earlier purchases to later purchases, and then applies the two different criteria in the criteria that are ORed.
I find the time comparisons easiest to do with the addtime() and then comparing the results. Others may prefer other ways.
SQL Fiddle here: http://sqlfiddle.com/#!2/14dda/2
Results:
PURCHASE_1 PURCHASE_2 NAME CUSTOMER
1 2 name1 1
7 8 name4 4
--
EDIT: Perhaps, you'd get some efficiency by moving the p1.purchase_time < p2.purchase_time up into the join clause. This might be faster with lots of data, though the execution plans for this little amount of data are identical. You'd like the optimizer to eliminate all those cases where p1.purchase_time > p2.purchase_time before doing the more expensive comparisons. But that's somewhat beyond the basic question of ways to write this query.

Joining with subqueries, counting and grouping

I have three tables, which are each 1:n. An entry in table1 has n entries in table2, and so on. Let's call them cars, wheels, and screws for illustration.
Screws can be clean(1) or rusty(2). I am joining them together, because I want to count two things. First, I want to have rows telling me how many good/bad screws per wheel I have for each car. So basically I am getting:
car_id wheel_id screw_state count(screws)
1 1 1 3
1 1 2 7
1 2 1 5
1 2 2 3
2 1 1 1
... and so on...
Now I want a second fact, namely how many rusty and clean screws I have for all wheels per car, without needing to know each specific number per wheel.
So basically now I just leave off the GROUP BY over wheel_id, like this:
car_id screw_state count(screws)
1 1 8
1 2 10
2 1 1
... and so on...
The thing is, I would need both of them in one single query, because else I'd have a lot of sorting and rearranging to do.
I believe the second, easier count over the total screws per car should be done as a subquery, but can I join the first, bigger query easily with a subquery?
How is this done?
I would be happy over a quite specific answers, because I am not really an SQL wizard.
edit : I am working on an ORM, so funky thinks like below (hacking the col values to some constant) can't be done there easily. I have to get this solution working there, so JOIN/subquery/UNIONs without funky workarounds would be great.
SELECT car_id, wheel_id, screw_state, count(screws)
FROM cars C, wheels W, screws S
WHERE W.car_id = C.car_id
AND S.wheel_id = W.wheel_id
GROUP BY car_id, wheel_id, screw_state
UNION ALL
SELECT car_id, -1 AS wheel_id, screw_state, count(screws)
FROM cars C, wheels W, screws S
WHERE W.car_id = C.car_id
AND S.wheel_id = W.wheel_id
GROUP BY car_id, screw_state
ORDER BY car_id
you can UNION 2 queries, the second one for all wheels per car, that's why wheel_id = -1.
result:
car_id wheel_id screw_state count(screws)
1 1 1 3
1 1 2 7
1 2 1 5
1 2 2 3
1 -1 1 8
1 -1 2 10
2 1 1 1
2 -1 1 1
...
A quick search says that MySQL supports GROUPING SETS. This is a good candidate for that feature:
SELECT car_id, wheel_id, screw_state, count(screws)
FROM cars C
JOIN wheels W ON W.car_id = C.car_id
JOIN screws S ON S.wheel_id = W.wheel_id
GROUP BY GROUPING SETS (
(car_id, screw_state, wheel_id),
(car_id, screw_state)
)
ORDER BY car_id, wheel_id, screw_state