MySQL get one record per group - mysql

I have a MySQL table targeted_refills with columns: id (unique, primary key), pat_name, pat_phone, rx_number, drug_name, qty_disp, last_date, qty_left, price_code, last_price, last_contact, doc_name, ehf, userid
What I'm trying to do is list one record for each userid. The record I want listed is the most recent one based on the field last_date however if there is more than one record for that user with the most recent date then I want it to select the field with the largest value in the field last_price. In the rare event that there are two records with matching last_date and last_price (shouldn't happen but is possible), I really don't care which is chosen so it can be chosen off the largest value of id since that is unique and the primary key.
EDIT:
I've tried this query that I pieced together from another person's question:
SELECT t1.*
FROM `targeted_refills` AS t1
LEFT OUTER JOIN `targeted_refills` AS t2
ON t1.userid = t2.userid
AND (t1.last_date < t2.last_date
OR (t1.last_date = t2.last_date AND t1.last_price < t2.last_price))
WHERE t2.userid IS NULL;
and it does a reasonable job of getting where I want to go, but it's pulling a few more records than I want. I'm comparing now to see what extra records it's pulling and why. It also takes a long time to run.

UPDATED You can tweak your existing query a little bit: you need at least one more join condition that prevent duplicates when both last_date and last_price are equal across several rows. Since you have auto-generated id you can use it to differentiate rows.
SELECT t1.*
FROM targeted_refills t1 LEFT JOIN targeted_refills t2
ON t1.userid = t2.userid
AND (
t1.last_date < t2.last_date
OR
(t1.last_date = t2.last_date AND
t1.last_price < t2.last_price)
OR
(t1.last_date = t2.last_date AND
t1.last_price = t2.last_price AND
t1.id < t2.id)
)
WHERE t2.userid IS NULL;
Or you can achieve your goal another way
SELECT id, pat_name, pat_phone, rx_number, drug_name,
qty_disp, last_date, qty_left, price_code,
last_price, last_contact, doc_name, ehf, userid
FROM
(
SELECT r.*,
#n := IF(#u = userid, #n + 1, 1) rownum,
#u := userid
FROM targeted_refills r
ORDER BY userid, last_date DESC, last_price DESC
) q
WHERE q.rownum = 1
Here is SQLFiddle demo for both queries.

Related

Generating SQL Query

have the following DB structure in MySQL:
Table1: ORDER
With Attributes
ID (PRIMARY KEY)
DELIVERY_DATE
CUSTOMER_ID
RESTAURANT_ID
ORDER_VALUE
Table2 : CUSTOMER
With Attributes
ID (PRIMARY KEY)
NAME
LOGIN
Table 3: Restaurant
ID (PRIMARY KEY)
NAME
CITY
I want to generate a db query where I select the customer_ID, login, Delivery_date, Sequence number, And the number of days from the last order to the second to last order.
I am having trouble using Inner join and generating the last two columns of my query:
Sequence: Each user may have ordered any number of dishes, I want to show a number three if it is the third time he orders, I should be able to get it from his customer_ID and the Order_ID
DateDiff from last order: I am having trouble getting the last and the second to last order and doing a dateDIFF and populating the column
I have so far constructed the following query:
SELECT customer_id,
login,
delivery_date,
sequence,
Datediff(second_to_last_order, last_order)
FROM order AS t1
INNER JOIN customer AS t2
ON t1.customer_id = t2.id
INNER JOIN restaurant AS t3
ON t1.restaurant_id = t3.id;
But it is obviously incomplete but i am stuck, any suggestions?
If this is MySQL Server query as you mentioned then I'd suggest you give the following a go;
SELECT CUSTOMER_ID, LOGIN, DELIVERY_DATE, SEQUENCE, DATEDIFF((SELECT MAX(z.ORDER_DATE) FROM ORDER AS z WHERE z.CUSTOMER_ID = t1.CUSTOMER_ID), (SELECT MAX(x.ORDER_DATE) FROM ORDER AS x WHERE x.CUSTOMER_ID = t1.CUSTOMER_ID AND x.ID != (SELECT MAX(x.ID) FROM ORDER AS x2 WHERE x2.CUSTOMER_ID = t1.CUSTOMER_ID)))
FROM ORDER AS a
INNER JOIN CUSTOMER AS t2 ON t1.CUSTOMER_ID = t2.ID
INNER JOIN RESTAURANT AS t3 ON t1.RESTAURANT_ID = t3.ID
ORDER BY t1;
I would advice that it's good practise to not call a table "ORDER" as this is a keyword used by MySQL, you could instead simple call the table "ORDERS" to avoid the issue.
This should do what you are asking for, if you have any issue please post any error text so we can better understand what you are facing.

MySQL Get exactly one entry per id by multiple ids

my problem is that I want this:
SELECT * FROM table
WHERE userId = 7243
ORDER BY date desc LIMIT 1
But for multiple ids in one request.
I tried this:
SELECT * FROM table
WHERE userId IN (7243, 1)
GROUP BY userId
ORDER BY date desc
But the order by seems to be ignored. Do anyone has a solution for me? Thank you
If you want the max date record for each of the two IDs, then you may use a subquery:
SELECT t1.*
FROM yourTable t1
INNER JOIN
(
SELECT userId, MAX(date) AS max_date
FROM yourTable
WHERE userId IN (7243, 1)
GROUP BY userId
) t2
ON t1.userId = t2.userId AND t1.date = t2.max_date
WHERE
t1.userId IN (7243, 1);
This is the just greatest-value-per-group question with a slight twist, namely that you only want to see two of the possible groups in the output.
As #Raymond commented below, an index on (userId, date) should greatly speed up the t2 subquery. I am not sure if this index would help beyond that, but it should make a difference.

MySQL query to select maximum time value for maximum date for each customer

I have a table named Transaction for customer transactions, it contains:
CustomerID,
Datetrans (transaction date)
Timetrans (transaction time)
I would like to return the max(Datetrans), given that a customer can make two transactions a day; I want to get the max value of Timetrans for that max Datetrans date.
I have tried :
SELECT CustomerID, MAX(Datetrans), MAX(Timetrans) FROM Transaction
It returns the all time maximum time value from the whole table, not depending on the maximum date.
I have also tried some joins (outer and inner), alternating max(Datetrans) and max(Timetrans) and it didn't go through for some reason.
PLEASE HELP!
First, you would need a GROUP BY to get more than one row:
SELECT CustomerID, MAX(Datetrans), MAX(Timetrans)
FROM Transaction
GROUP BY CustomerId;
But what I really think you want is the most recent date/time combination. Here is one method:
select t.*
from transaction t
where t.date = (select max(t2.date)
from transaction t2
where t2.customerid = t.customerid
) and
t.time = (select max(t2.time)
from transaction t3
where t3.customerid = t.customerid and
t3.date = t.date
);
There are other methods. This would be simpler if you stored a single date/time value (and you could combine them for the max()) or if MySQL had window functions. Yet another method uses tuples:
select t.*
from transaction t
where not exists (select 1
from transaction t2
where t2.customerid = t.customerid and
(t2.date, t2.time) > (t.date, t.time)
);
or:
select t.*
from transactions t
where (t.date, t.time) = (select t2.date, t2.time
from transaction t2
where t2.customerid = t.customerid
order by t2.date desc, t2.time desc
limit 1
);
You can do it with a single simpler query by converting the two separate fields into a single timestamp field. I don't know anything about performance though.
SELECT customerID, DATE(maxTime) as datetrans, TIME(maxTime) as timeTrans
FROM (
select customerID, max(Timestamp(dateTrans, timeTrans)) as maxTime
from transactions
group by customerID) as lastCustomerTrans
The only reason you need the outer query is to convert the timestamp back into a date and time.
Link to SQL fiddle

How do I write this kind of query (returning the latest avaiable data for each row)

I have a table defined like this:
CREATE TABLE mytable (id INT NOT NULL AUTO_INCREMENT, PRIMARY KEY(id),
user_id INT REFERENCES user(id) ON UPDATE CASCASE ON DELETE RESTRICT,
amount REAL NOT NULL CHECK (amount > 0),
record_date DATE NOT NULL
);
CREATE UNIQUE INDEX idxu_mybl_key ON mytable (user_id, amount, record_date);
I want to write a query that will have two columns:
user_id
amount
There should be only ONE entry in the returned result set for a given user. Furthermore, the amount figure returned should be the last recoreded amount for the user (i.e. MAX(record_date).
The complication arises because weights are recorded on different dates for different users, so there is no single LAST record_date for all users.
How may I write (preferably an ANSI SQL) query to return the columns mentioned previously, but ensuring that its only the amount for the last recorded amount for the user that is returned?
As an aside, it is probably a good idea to return the 'record_date' column as well in the query, so that it is eas(ier) to verify that the query is working as required.
I am using MySQL as my backend db, but ideally the query should be db agnostic (i.e. ANSI SQL) if possible.
First you need the last record_date for each user:
select user_id, max(record_date) as last_record_date
from mytable
group by user_id
Now, you can join previous query with mytable itself to get amount for this record_date:
select
t1.user_id, last_record_date, amount
from
mytable t1
inner join
( select user_id, max(record_date) as last_record_date
from mytable
group by user_id
) t2
on t1.user_id = t2.user_id
and t1.record_date = t2.last_record_date
A problem appears becuase a user can have several rows for same last_record_date (with different amounts). Then you should get one of them, sample (getting the max of the different amounts):
select
t1.user_id, t1.record_date as last_record_date, max(t1.amount)
from
mytable t1
inner join
( select user_id, max(record_date) as last_record_date
from mytable
group by user_id
) t2
on t1.user_id = t2.user_id
and t1.record_date = t2.last_record_date
group by t1.user_id, t1.record_date
I do not now about MySQL but in general SQL you need a sub-query for that. You must join the query that calculates the greatest record_date with the original one that calculates the corresponding amount. Roughly like this:
SELECT B.*
FROM
(select user_id, max(record_date) max_date from mytable group by user_id) A
join
mytable B
on A.user_id = B.user_id and A.max_date = B.record_date
SELECT datatable.* FROM
mytable AS datatable
INNER JOIN (
SELECT user_id,max(record_date) AS max_record_date FROM mytable GROUP BS user_id
) AS selectortable ON
selectortable.user_id=datatable.user_id
AND
selectortable.max_record_date=datatable.record_date
in some SQLs you might need
SELECT MAX(user_id), ...
in the selectortable view instead of simply SELECT user_id,...
The definition of maximum: there is no larger(or: "more recent") value than this one. This naturally leads to a NOT EXISTS query, which should be available in any DBMS.
SELECT user_id, amount
FROM mytable mt
WHERE mt.user_id = $user
AND NOT EXISTS ( SELECT *
FROM mytable nx
WHERE nx.user_id = mt.user_id
AND nx.record_date > mt.record_date
)
;
BTW: your table definition allows more than one record to exist for a given {id,date}, but with different amounts. This query will return them all.

Remove WHERE condition when subquery does not return any row

I'm writing a complex MySQL query and I'm having trouble figuring out how to finish it.
Here's the part that's giving me trouble (it's only a part of my query):
SELECT * FROM table AS t1
WHERE date < (
SELECT date FROM table AS t2
WHERE phase="B" AND t2.target = t1.target
)
Basically, I have items, each one with a date, a phase (A,B,C) and a target. For a target, there are several items of type A, then an single and optional item of type B, then items with type C.
For each target, I want to select all the rows following these conditions:
If there is an item with phase "B" (lets call him itemX), I want to return all items with a date inferior to the date of itemX
If there is no item with phase "B", I want to return all rows
The date parameter is very important. In most cases, the 3 phases are distinct, and cannot overlap, but there are some cases in which that happens.
The problem here, is that my subquery does not return any rows in case 1, and a single cell in case 2.
If we are in case 1, the whole condition WHERE date < (...) is irrelevant and should not be applied in the query.
I tried several possibilities with IFNULL and EXISTS, but I think I did it wrong because I keep getting syntax errors.
SELECT m.*
FROM (
SELECT target, MAX(date) AS maxdate
FROM mytable
) md
JOIN mytable m
ON m.target = md.target
AND m.date <
COALESCE
(
(
SELECT date
FROM mytable mb
WHERE mb.target = md.target
AND mb.phase = 'B'
ORDER BY
mb.target, pmb.phase, mb.date
LIMIT 1
),
maxdate + INTERVAL 1 SECOND
)
Create two indexes:
mytable (target, date)
mytable (target, phase, date)
for this to work fast.
Perhaps
SELECT *
FROM table AS t1
LEFT JOIN table AS t2 ON t2.target = t1.target AND (t1.date < t2.date)
WHERE (phase = 'B')
I'm assuming the table in your query is actually two tables and you're not doing a self join? If so, then you'll have to specify which table's phase you're referring to.
You might try
SELECT * FROM table AS t1
left join
table as t2
on t1.Target = t2.Target
and t2.phase="B"
where t2.target is null OR
OR t1.date < t2.Date
The code you posted is called "One subquery per condition anti-pattern". Use the CASE-WHEN-THEN.
SELECT t1.*
FROM table t1
LEFT
JOIN ( SELECT t.target
, MIN(t.date) AS b_date
FROM table t
WHERE t.phase = 'B'
GROUP BY t.target
) t2
ON t1.target = t2.target AND t1.date < t2.b_date
If there is some guarantee that a given target will have no more than one row with "phase"='B' at most, you can get by without the MIN and GROUP BY, like this:
SELECT t1.*
FROM table t1
LEFT
JOIN ( SELECT t.target
, t.date AS b_date
FROM table t
WHERE t.phase = 'B'
) t2
ON t1.target = t2.target AND t1.date < t2.b_date