Complex query involving timestamps - mysql

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.

Related

MySql multiple results tables for each unique values

Sorry if the title is not well explained tried my best.
I currently have this transactions table which hold the records, every row has an agent and a currency assigned to it.
id
amount
agent
currency_id
1
400.00
agent1
1
2
170.00
agent5
3
3
110.00
agent4
2
4
430.00
agent5
3
5
155.00
agent1
1
6
370.00
agent2
2
7
10.00
agent2
2
8
150.00
agent1
1
9
130.00
agent3
3
10
445.00
agent4
2
And this other table called currency which holds the unique currency and name.
id
currency
1
USD
2
VES
3
EUR
The query that I want to make is a SUM and group by agent for every currency there is. I am able to do it with a single query like this but only for one currency in the WHERE clause:
SELECT a.agent,
SUM(a.amount)
FROM transactions AS a
INNER JOIN currency AS b ON b.id = a.currency_id
WHERE b.currency = 'VES'
GROUP BY a.agent
I will be getting this result which is only for the VES currency
agent
total
agent2
380.00
agent4
555.00
I am looking for one query that allow me to get the result of all 3 current existing currencies (USD, VES, EUR) this should give a result of 3 different tables
I suspect that you want a report showing all agents, currencies, and their sums. You may try using this cross join approach:
SELECT a.agent, c.currency, COALESCE(SUM(t.amount), 0) AS total
FROM (SELECT DISTINCT agent FROM transactions) a
CROSS JOIN currency c
LEFT JOIN transactions t
ON t.agent = a.agent AND
t.currency_id = c.id
GROUP BY 1, 2
ORDER BY 1, 2;
The first two tables in the join generate all combinations of agents and currencies. We join this to your transactions table and aggregate to get the totals.

MySQL substring to self join

I'm defining the relationship between the two tables using a join table. I want to arrange them in the order of many overlapping things. Currently, we are using subquery, is there a way to get the same result using join?
People FoodTable PeopleFood
ID | NAME ID | Food ID | PeopleId | FoodId
1 BOB 1 Hamberger 1 1 1
2 JOHN 2 Pizza 2 1 2
3 KATY 3 Chicken 3 1 3
4 MILLER 4 Salad 4 2 1
5 AMANDA 5 Sushi 5 2 2
6 2 3
7 3 2
8 3 3
9 4 3
10 4 5
11 5 5
When the table is defined in this way, I want to arrange food tastes similar to Bob's.
I'm doing it like this now.
SELECT people_id, COUNT(people_id) as count
FROM peopleFood
WHERE food_id IN
(SELECT food_id FROM peopleFood
WHERE people_id = 1)
AND people_id != 1
GROUP BY people_id
ORDER BY count DESC;
-- Result -------------
People_id | count
2 3
3 2
4 1
Is there a better way to change this method or use join?
Thank you!!!
You have been inconsistent in your use of the table and column names -
Tables - PeopleFood in your sample data but you reference peopleFood in your query.
Columns - PeopleId and FoodId in your sample data but you reference people_id and food_id in your query.
Choose a naming convention and stick to it. Everyone has there own preference but the important thing is to be consistent.
The equivalent query with INNER JOIN instead of your sub-query is -
SELECT
`pf2`.`people_id`,
COUNT(`pf2`.`food_id`) as `count`
FROM `PeopleFood` `pf1`
INNER JOIN `PeopleFood` `pf2`
ON `pf2`.`people_id` <> `pf1`.`people_id`
AND `pf2`.`food_id` = `pf1`.`food_id`
WHERE `pf1`.`people_id` = 1
GROUP BY `pf2`.`people_id`
ORDER BY `count` DESC;
The performance difference between the two queries is unlikely to be noticeable and it might be argued that the intent is clearer in your version with the sub-query.
The surrogate key ID on your PeopleFood table should be dropped in favour of the compound “natural” primary key on people_id and food_id.
The Cost of Useless Surrogate Keys in Relationship Tables
Inner join:
SELECT p.People_id, COUNT(p.People_id) as count FROM PeopleTable p
INNER JOIN FoodTable f
ON(p.People_id = f.FoodId)
WHERE people = 1
GROUP BY p.people_id
ORDER BY count DESC;
If it helps, please mark it as an accepted answer!

Get last record from joined table

I have two tables DOCUMENT and SIGNATURES, like below
DOCUMENTS
doc_id doc_name
1 Contract
2 Lead
3 Invoice 1
4 Invoice 2
5 Payment 123
SIGNATURES
sig_id sig_doc_id signature_name
1 1 Paul
2 2 Mark
3 1 Chew
4 2 Paul
5 3 John
6 3 Derek
7 3 Silvan
8 5 Roden
And I'm try to get last signature name.
EXPECTED OUTPUT
doc_id doc_name signature_name
1 Contract Chew
2 Lead Paul
3 Invoice 1 Silvan
4 Invoice 2 < empty because we not have signature
5 Payment 123 Roden
I have a SQL FIDDLE with database and query, but when run search no record has found.
http://sqlfiddle.com/#!9/b98474/3
Here my query
SELECT docs.*, sign.*
FROM cnt_man_docs docs
INNER JOIN cnt_man_doc_signatures sign ON docs.cnt_man_doc_id = sign.cnt_man_doc_signature_doc_id
WHERE sign.cnt_man_doc_signature_id =
(SELECT MAX(cnt_man_doc_signature_id)
FROM cnt_man_doc_signatures
WHERE sign.cnt_man_doc_signature_id = docs.cnt_man_doc_id)
A simple method is a correlated subquery:
select d.*,
(select s.signature_name
from signatures s
where s.sig_doc_id = d.doc_id
order by s.sig_id desc
limit 1
) as signature_name
from documents d;
With an index on signatures(doc_id, sig_id desc, signature_name) this is probably the fastest method as well.

better way to implement the following operations in 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.

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