MySQL query to fetch most recent record - mysql

I've got four tables. The structure of these tables is shown below (I am only showing the relevant column names).
User (user_id)
User_RecordType (user_id, recordType_id)
RecordType (recordType_id)
Record (recordType_id, record_timestamp, record_value)
I need to find the most recent record_value for each RecordType that a given user has access to. Timestamps are stored as seconds since the epoch.
I can get the RecordTypes that the user has access to with the query:
SELECT recordType_id
FROM User, User_RecordType, RecordType
WHERE User.user_id=User_RecordType.user_id
AND User_RecordType.recordType_id=RecordType.recordType_id;
What this query doesn't do is also fetch the most recent Record for each RecordType that the user has access to. Ideally, I'd like to do this all in a single query and without using any stored procedures.
So, can somebody please lend me some of their SQL-fu? Thanks!

SELECT
Record.recordType_id,
Record.record_value
FROM
Record
INNER JOIN
(
SELECT
recordType_id,
MAX(record_timestamp) AS `record_timestamp`
FROM
Record
GROUP BY
recordType_id
) max_values
ON
max_values.recordType_id = Record.recordType_id
AND
max_values.record_timestamp = Record.record_timestamp
INNER JOIN
User_RecordType
ON
UserRecordType.recordType_id = RecordType.recordType_id
WHERE
User_RecordType.user_id = ?

Related

Laravel Left Join with a tricky filter

I am assigning unique users to vouchers on my website.1 user may have more than one voucher assigned to them but cannot be assigned the same voucher twice.I have 2 mysql tables that I am fetching data from.
tbl_users
tbl_voucher_users
When a user click on a button on my website, they pass along a voucher_id with which I use to display eligible users that can be assigned this voucher ( I.e Users that have not been assigned to this voucher ).
Below is how I am getting the users where voucher_id = 8
$user_data = DB::table('users')
->leftJoin('voucher_users', 'users.id', '=', 'voucher_users.user_id')
->where('voucher_users.voucher_id','!=',8) //User not assigned this voucher
->select('users.*','users.id as userID','voucher_users.*')
->get();
My problem
I am able to left join without the where clause and get results from both Users table and Voucher_users table having eliminated all users assigned voucher_id=8.
However, the results also include users who are assigned other vouchers but also the voucher I am assigning.
i.e
Expected resulsts would be users: 8,11,12,13,14 having eliminated users: 1,4
But my current results are:4,8,11,12,13,14
How do I get rid of the user 4 to prevent double assignment?
Thanks to the suggestion above by #Kevin Lynch to use NOT EXIST .. I simplified the code to:
SELECT users.*
FROM
users
WHERE
NOT EXISTS(SELECT user_id FROM voucher_users WHERE voucher_users.user_id = users.id AND voucher_id=9)
It works so far, I can then covert it to Laravel style
NOT EXISTS would be a good solution if this were to remain a small project (or if you could put more constraints on the users to keep the dataset small perhaps by limiting based on user created date or similar).
However if you can't do such a thing, this query will eventually give you problems because under the hood, mysql will be running that sub-query for each record returned in the main query. You can check https://dev.mysql.com/doc/refman/5.6/en/subquery-materialization.html for more information.
A different solution which would handle the scaling quite a bit better would be to generate a temporary table of users that have the voucher you are looking to remove...
create temporary table tmp_voucher_user (user_id int not null, primary key (user_id)) as
select distinct user_id from voucher_users where voucher_id = 8;
Now that we have a table of users we which to remove, all we need to do is worry about a simple left join...
select users.*, user_voucher.*
from users
inner join user_voucher on users.id = user_voucher.user_id
left join tmp_voucher_user on users.id = tmp_voucher_user.user_id
where tmp_voucher_user.user_id is null -- this part is important, it's only going to grab users where there isn't a match on tmp_user_voucher
Unfortunately this isn't as clean as just doing a NOT EXISTS and I don't believe Laravel supports a way to build temporary tables outside of just writing a raw query but it should scale quite a bit better.

MySQL Join statement to get data from two tables into a datagridview

I have two tables that I'm trying to join, 'holidays' and 'users'.
Users contains all my user info, the the column 'id' being primary and unique.
Holidays contains a column called 'userid', which corresponds to the id in the user table.
I'm struggling to get the join statement to work... what I'm looking for is the result of the select statement to give me the friendlyname (column 'fname' in user table) instead of giving me the value of userid.
Here's what I'm trying...
SELECT * FROM holidays JOIN users on users.id=holidays.userid WHERE holidays.status = 0
But i'm not getting a correct result - SQL executes without error, but my DGV is filled with tons of erroneous results.
Apologies If I have not used the correct terminology or whatever.
I'm new to the concept of joins.
Here is hopefully a better explanation of what I am after...
Thanks in advance.
You need to select the specific values you want from every table in the JOIN:
SELECT u.fname
FROM holidays h
JOIN users u
ON u.id = h.userid
WHERE h.status = 0
by the alias (FROM users u) you can select column from users table by u.fname
First try to right join to the User table. If you just want the fname then select the column name in the SELECT query, as SELECT * takes more time then SELECT column name.

How to use one table multiple times for inner join in SQL Server?

I am trying to make a table that includes join between 3 tables in the MSSS 2008. There is a fact table, a date table, and a course table. I should join them to make a base table. In date table there is a one parameter that name is Academic Year lookup, and the values in this parameter is like 2000/1, 2001/2. This parameter in the base table should separate to three parameter such as CensusYear, StartYear, and ApplicationYear. Therefore, I need the data table multiple times. I executed a inner join query, and already I have four inner join statement, but I am getting some extra years, and I'm losing some years. I believe, my query should be wrong somewhere.
The attached file is include the design view that created in the MS Access, it'll help to see the tables, and understand what I need to create.
[Design View in Ms Access][1]
SELECT
A.[EventCount],
B.[AcademicYearLookup] AS [CensusYear],
C.[AcademicYearLookup] AS [StartYear],
D.[AcademicYearLookup] AS [ApplicationYear],
B.[CurrentWeekComparisonFlag],
B.[AcademicWeekOfYear],
case
when A.[ApplicationCensusSK] = 1 then 'Same Year'
when A.[ApplicationCensusSK] = 2 then 'Next Year'
when A.[ApplicationCensusSK] = 5 then 'Last Year'
ELSE 'Other'
END as [CensusYearDescription],
B.[CurrentAcademicYear],
A.[StudentCodeBK],
A.[ApplicationSequenceNoBK],
A.[CourseSK],
A.[CourseGroupSK],
A.[CourseMoaSK],
A.[CboSK],
A.[CourseTaughtAbroadSK],
A.[ApplicationStatusSK],
A.[ApplicationFeeStatusSK],
A.[DecisionResponseSK],
A.[NationalityCountrySK],
A.[DomicileCountrySK],
A.[TargetRegionSK],
A.[InternationalSponsorSK] INTO dbo.[BaseTable3yrs]
FROM Student.FactApplicationSnapshot A
INNER JOIN Conformed.DimDate AS B ON A.[CensusDateSK] = B.[DateSK]
INNER JOIN Conformed.DimDate AS C ON A.[AcademicYearStartDateSK] = C.[DateSK]
INNER JOIN Conformed.DimDate AS D ON A.[ApplicationDateSK] = D.[DateSK]
INNER JOIN Student.DimCourse ON A.CourseSK = Student.DimCourse.CourseSK
WHERE (((B.CurrentAcademicYear) In (0,-1))
AND ((A.ApplicationCensusSK) In (1,2,5))
AND ((Student.DimCourse.DepartmentShortName)= 'TEACH ED'));
/* the query to check that the result it's correct or not, and I check it by academic week of year, and I found that I am lossing some data, and I have some extra data, means maybe join is wrong*/
select * from [BaseTable3yrs]
where [StudentCodeBK]= '26002423'
AND [ApplicationSequenceNoBK] = '0101'
order by [AcademicWeekOfYear]
When doing recursive joins like this, it's easy to get duplicate records. You could try gathering the Conformed data separately into a table variable and then joining to it. This would also make your query more readable.
You might also try a SELECT DISTINCT on your main query.

Select a relative result set as an array in mysql

So here's my problem. I have 3 tables
case_assign.cfid => case_fields.cfid and case_assign.uid => users.uid.
The single query I am trying to write basically fetches all the cases between two dates while also selecting every person assigned to the case via the case_assign table and the case_assign table also fetches the names via the uid fields in both it and the users table.
Now here's the challenge. Since many people can be assigned to any one case, it will be fetching multiple rows from case_assign for a single row fetched from case_fields but the query isn't case_assign based, its case_fields based which means that I will get an error saying something like "mysql error: too many fields returned" or something like that.
So I am thinking, is there a way I can return the total number of rows selected for any one query gotten from the case_assign table into an array that will be sent to the client then I can probably json.parse() it on the client side.
Thanks
SELECT cf.cfid, GROUP_CONCAT(u.fname) assignees
FROM case_fields cf
LEFT JOIN case_assign ca ON cf.cfid = ca.cfid
LEFT JOIN users u ON ca.uid = u.uid
GROUP BY cf.cfid
This will create a comma-separated list of user names in assignees. You can split this into an array in the client application.
Or you can use this query:
SELECT cf.cfid, u.fname
FROM case_fields cf
LEFT JOIN case_assign ca ON cf.cfid = ca.cfid
LEFT JOIN users u ON ca.uid = u.uid
ORDER BY cf.cfid
Then as you loop through the results, check if the current cfid is the same as the previous one; if it is, append fname to the assignee list, otherwise start a new entry in the result array.

Removing duplicate user entries from mySQL database table

I have a table in my database to store user data. I found a defect in the code that adds data to this table database where if a network timeout occurs, the code updated the next user's data with the previous user's data. I've addressed this defect but I need to clean the database. I've added a flag to indicate the rows that need to be ignored and my goal is to mark these flags accordingly for duplicates. In some cases, though, duplicate values may actually be legitimate so I am more interested in finding several user's with the same data (i.e, u> 2).
Here's an example (tablename = Data):
id---- user_id----data1----data2----data3----datetime-----------flag
1-----usr1--------3---------- 2---------2---------2012-02-16..-----0
2-----usr2--------3---------- 2---------2---------2012-02-16..-----0
3-----usr3--------3---------- 2---------2---------2012-02-16..-----0
In this case, I'd like to mark the 1 and 2 id flags as 1 (to indicate ignore). Since we know usr1 was the original datapoint (assuming the oldest dates are earlier in the list).
At this point there are so many entries in the table that I'm not sure the best way to identify the users that have duplicate entries.
I'm looking for a mysql command to identify the problem data first and then I'll be able to mark the entries. Could someone guide me in the right direction?
Well, first select duplicate data with their min user id:
CREATE TEMPORARY TABLE duplicates
SELECT MIN(user_id), data1,data2,data3
FROM data
GROUP BY data1,data2,data3
HAVING COUNT(*) > 1 -- at least two rows
AND COUNT(*) = COUNT(DISTINCT user_id) -- all user_ids must be different
AND TIMESTAMPDIFF( MINUTE, MIN(`datetime`), MAX(`datetime`)) <= 45;
(I'm not sure, if I used TIMESTAMPDIFF properly.)
Now we can update the flag in those rows where user_id is different:
UPDATE duplicate
INNER JOIN data ON data.data1 = duplicate.data1
AND data.data2 = duplicate.data2
AND data.data3 = duplicate.data3
AND data.user_id != duplicate.user_id
SET data.flag = 1;
UPDATE Data A
LEFT JOIN
(
SELECT user_id,data1,data2,data3,min(id) min_id
FROM Data GROUP BY user_id,data1,data2,data3
) B
ON A.id = B.min_id
SET A.flag = IF(ISNULL(B.min_id),1,0);
If there are duplicate times involved, maybe try this
UPDATE Data A
LEFT JOIN
(
SELECT user_id,data1,data2,data3,,`datetime`,min(id) min_id
FROM Data GROUP BY user_id,data1,data2,data3,`datetime`
) B
ON A.id = B.min_id
SET A.flag = IF(ISNULL(B.min_id),1,0);