Display distinct record for each employee - mysql

I have two tables appointment app and faculty fac. they are multiple record with same emp ids
I need to join two tables by app.employeeid=fac.empid (here i will get duplicate records)
and app.begindate<=now and app.enddate>=now and app.percent>.50 (here also i will get duplicate records because empid(multiple records with same id) can have same begindate,end date and >.50 )
max(app.amt) (here also i will get duplicate records because there can be a chance where empid(multiple records with same id) have same amt) . Finally at the end i want to select distinct(max(app.amt)) to get only single record for empid.
I used the query:
select max(app.amt) ,fac.email,fac.employee_id,fac.empid,fac.first_name,fac.last_name,fac.department_id,fac.titlecode,app.begindate,app.enddate,app.percent,
app.title_name,app.department_name from faculty fac, appointment app where
app.employeeid=fac.empid and app.begindate <= now() and app.enddate >= now() and app.percent>.50 group by fac.empid
/
i am having an issue i am getting max(app.amt) but corresponding column values are not properly matched with max amt record.the other column values are taking in random.I want the exact corresponding values to display.
sample date in appointment table for few fileds:
SELECT app.amt, fac.email, fac.employee_id, fac.empid,
fac.first_name, fac.last_name, fac.department_id, fac.titlecode,
app.begindate, app.enddate, app.percent,
app.title_name, app.department_name
FROM appointment app
LEFT JOIN appointment app2 -- self-join to locate max
ON app.employee_id = app2.employee_id AND app.amt < app2.amt -- of app.amt
INNER JOIN faculty fac ON app.employee_id=fac.empid -- join to fac
WHERE app2.amt IS NULL -- isolate rows with max(app.amt)
AND app.begindate <= NOW()
AND app.enddate >= NOW()
AND app.percent>.50 group by fac.empid
employee_id begindate enddate amt percent department_name
5528 7/1/2011 9/30/2011 0 1 m1
5528 7/1/2011 9/30/2011 193100 1 m1
5528 10/1/2011 6/30/2013 79000 1 m1
5528 10/1/2011 6/30/2013 118500 1 m2
5528 10/1/2011 6/30/2013 0 1 m2

To select not only a MAX(column) but the row corresponding to it, for each employee_id, you usually use a LEFT JOIN to join the table to itself. (This is also known as "greatest-n-per-group" and there are a number of questions in this vein on stackoverflow under this tag).
e.g. to select the max(app.amt) per employee_id with its corresponding row you would do:
SELECT app.*
FROM appointment app
LEFT JOIN appointment app2
ON app.employee_id = app2.employee_id AND app.amt < app2.amt
WHERE app2.amt IS NULL;
Why does this work? In essence, this says "pick the row with the amt for which there is no higher amt for that employee", for each employee.
The LEFT JOIN appointment app2 ON app.employee_id=app2.employee_id joins app to itself on the employee_id (note this is what you wanted to GROUP BY). This produces a table of appointment with every possible pair of amt for each employee_id.
Then, we also specify that app.amt < app2.amt. So we only display rows where the (app.amt,app2.amt) pair has the former less than the latter.
Since this is a LEFT JOIN, if we happen to find an app.amt for which we can't find a corresponding row in app2 with the same employee_id and a greater app2.amt, then app2.amt is set to NULL. This occurs precisely when we've found the greatest app.amt for that employee.
Solution
So, adapt your query using this "LEFT JOIN to myself to get the corresponding row" method:
SELECT app.amt, fac.email, fac.employee_id, fac.empid,
fac.first_name, fac.last_name, fac.department_id, fac.titlecode,
app.begindate, app.enddate, app.percent,
app.title_name, app.department_name
FROM appointment app
LEFT JOIN appointment app2 -- self-join to locate max
ON app.employee_id = app2.employee_id AND app.amt < app2.amt -- of app.amt
INNER JOIN faculty fac ON app.employee_id=fac.empid -- join to fac
WHERE app2.amt IS NULL -- isolate rows with max(app.amt)
AND app.begindate <= NOW()
AND app.enddate >= NOW()
AND app.percent>.50
So, the only things that have changed are:
added a LEFT JOIN appointment app2 ... WHERE app2.amt IS NULL to locate the row with the max app.amt for each employeeid.
changed your FROM app, fac .. WHERE app.employeeid=fac.empid into a JOIN (see note below)
no need for the GROUP BY or MAX(app.amt) -- the INNER JOIN takes care of that.
Note on converting the FROM app, fac ... WHERE join_condition to FROM app LEFT JOIN fac ON join_condition: a join tends to be more efficient than selecting from multiple tables and joining in the WHERE.
What type of join you use (app to fac) may make a difference to your results; to recover your former behaviour use INNER JOIN. This will only display rows where the employee exists in both the app table and the fac table.
Using a LEFT JOIN is slightly more efficient than the INNER JOIN, and the results will be identical unless there is an employee in your appointments table who does not have an entry in the faculty table. Then they will be displayed in the results list with NULL for all the fac.xxx fields.
Using a RIGHT JOIN will show every employee from the faculty table regardless of whether they have appointments or not -- if they have no appointments they'll still be shown in your results, just with NULL for all their appointment-related fields.
Just subtle differences in the JOINs (ahh, but that's another question).

Related

How to select data from 2 tables by date

I have two tables:
In this table I have all employees:
Employe (id_employe, name, tel)
In this table I have all employees who are present:
Present (id_present, date, #id_empoye)
Now, I want to select all employees who are absent (all of employees who are not in the table present) with the date of absence.
I'm sorry for by bad English, and I want a help please!
Here's my sql query:
select id_employe, date from employe, absence where id_employe not in
(select id_personnel from absence group by date) group by id_employe
order by date asc;
What you first want to do is use a subquery to get all of your employees with a record for every day that was a work day. I am assuming that if any one person showed up for work that day, it was a work day. So, I select every distinct work date and join it onto every employee. I say ON 1=1 because that condition is always true and will give me a record for every employee and the work day.
Then, I take that temp table and I join on the Present table. If an employee was present on a work day, he will have a record joined on from the Present table, so I can look for only records where there was no join, i.e. p.id_present IS NULL.
WITH WorkDateTable AS (
SELECT e.id_employe, p.work_date
FROM Employe e LEFT JOIN (SELECT DISTINCT work_date FROM Present) p ON 1=1)
SELECT wd.id_employe, wd.work_date AS AbsenceDate
FROM WorkDateTable wd
LEFT JOIN Present p ON p.work_date = wd.work_date AND p.id_employe = wd.id_employe
WHERE p.id_present IS NULL
ORDER BY AbsenceDate, wd.id_employe
Note that date is a reserved word and you cannot use that for a field, that is why I changed it to work_date. Your final result is a list of work dates and the ID of who was absent on that day.
If you want to know who was absent on a particular date or range of dates, you just need to add a clause to the WHERE statement at the end.
EDIT:
MySQL does not support the use of the WITH clause. Therefore, move the statement in the WITH to inside the FROM statement like this:
SELECT wd.id_employe, wd.work_date AS AbsenceDate
FROM (SELECT e.id_employe, p.work_date
FROM Employe e LEFT JOIN (SELECT DISTINCT work_date FROM Present) p ON 1=1) wd
LEFT JOIN Present p ON p.work_date = wd.work_date AND p.id_employe = wd.id_employe
WHERE p.id_present IS NULL
ORDER BY AbsenceDate, wd.id_employe
This query should work for either MySQL or Oracle searches.
Assuming you only want the employees who are not present on a particular day, you can use the following query:
SELECT e.*
FROM employee e
WHERE NOT EXISTS (
SELECT id_present FROM present WHERE id_employee = e.id_employee AND date = <date>
);
This would give you employees who are absent on <date> date. As we are querying for one date only, no sorting is needed.

MySQL Compare Result in WHERE clause

I imagine I'm missing something pretty obvious here.
I'm trying to display a list of 'bookings' where the total charges is higher than the total payments for the booking. The charges and payments are stored in separate tables linked using foreign keys.
My query so far is:
SELECT `booking`.`id`,
SUM(`booking_charge`.`amount`) AS `charges`,
SUM(`booking_payment`.`amount`) AS `payments`
FROM `booking`
LEFT JOIN `booking_charge` ON `booking`.`id` = `booking_charge`.`booking_id`
LEFT JOIN `booking_payment` ON `booking`.`id` = `booking_payment`.`booking_id`
WHERE `charges` > `payments` ///this is the incorrect part
GROUP BY `booking`.`id`
My tables look something like this:
Booking (ID)
Booking_Charge (Booking_ID, Amount)
Booking_Payment (Booking_ID, Amount)
MySQL doesn't seem to like comparing the results from these two tables, I'm not sure what I'm missing but I'm sure it's something which would be possible.
try HAVING instead of WHERE like this
SELECT `booking`.`id`,
SUM(`booking_charge`.`amount`) AS `charges`,
SUM(`booking_payment`.`amount`) AS `payments`
FROM `booking`
LEFT JOIN `booking_charge` ON `booking`.`id` = `booking_charge`.`booking_id`
LEFT JOIN `booking_payment` ON `booking`.`id` = `booking_payment`.`booking_id`
GROUP BY `booking`.`id`
HAVING `charges` > `payments`
One of the problems with the query is the cross join between rows from `_charge` and rows from `_payment`. It's a semi-Cartesian join. Each row returned from `_charge` will be matched with each row returned from `_payment`, for a given `booking_id`.
Consider a simple example:
Let's put a single row in `_charge` for $40 for a particular `booking_id`.
And put two rows into `_payment` for $20 each, for the same `booking_id`.
The query will would return total charges of $80. (= 2 x $40). If there were instead five rows in \'_payment\' for $10 each, the query would return a total charges of $200 ( = 5 x $40)
There's a couple of approaches to addressing that issue. One approach is to do the aggregation in an inline view, and return the total of the charges and payments as a single row for each booking_id, and then join those to the booking table. With at most one row per booking_id, the cross join doesn't give rise to the problem of "duplicating" rows from _charge and/or _payment.
For example:
SELECT b.id
, IFNULL(c.amt,0) AS charges
, IFNULL(p.amt,0) AS payments
FROM booking b
LEFT
JOIN ( SELECT bc.booking_id
, SUM(bc.amount) AS amt
FROM booking_charge bc
GROUP BY bc.booking_id
) c
ON c.booking_id = b.id
LEFT
JOIN ( SELECT bp.booking_id
, SUM(bp.amount) AS amt
FROM booking_payment bp
GROUP BY bp.booking_id
) p
ON p.booking_id = b.id
WHERE IFNULL(c.amt,0) > IFNULL(p.amt,0)
We could make use of a HAVING clause, in place of the WHERE.
The query in this answer is not the only way to get the result, nor is it the most efficient. There are other query patterns that will return an equivalent result.

How can I select data from one table depending on the data from another table

I have 2 tables: contracts_main_list and contracts_detail.
In contracts_main_list I have columns:
user_id
contract_id
and in contracts_detail:
contract_id
other columns with data
I need to select all the rows from the table contracts_main_list WHERE user_id = some number.
From these rows I need to get the list of contract numbers (from column contract_id) and according to them select rows corresponding to each of the contract number from the list. So something like:
WHERE contracts_detail.contract_id = contracts_main_list.contract_id
The contract_ids are probably gonna be unique, but in case there is some kind of error and there will be more rows with the same contract_id in either of the tables, I need to select only one row (so probably using DISTINCT) and select the latest record (both tables have a column id as a primary key)
Also if there is no row in contracts_detail matching with the contract_id to the contract_id of the first table contracts_main_list it should skip the row. But I guess the condition:
WHERE contracts_detail.contract_id = contracts_main_list.contract_id
already covers it.
I hope I made it clear enough. What I am trying to do in real life is show list of contracts with all the relevant data belonging to the user.
To sum this up, I need to find all the contracts belonging to the user and select the rows with details about each contract and finally get the data from the contracts_detail table as a result.
Here is the result you're looking for:
SELECT CD.*
FROM (SELECT C2.contract_id
,MAX(C2.id) AS last_main_list_id
,MAX(CD2.id) AS last_contracts_detail_id
FROM contracts_main_list C2
INNER JOIN contracts_detail CD2 ON CD2.contract_id = C2.contract_id
GROUP BY C2.contract_id) L
INNER JOIN contracts_main_list C ON C.id = L.last_main_list_id
AND C.user_id = ?
INNER JOIN contracts_detail CD ON CD.id= L.last_contracts_detail_id
This query use a subquery for the FROM because of the following indication you provided:
The contract_ids are probably gonna be unique, but in case there is
some kind of error and there will be more rows with the same
contract_id in either of the tables, I need to select only one row
If you're sure that the contract_id are unique, here is the same query without this check on contract_id:
SELECT CD.*
FROM contracts_main_list C
INNER JOIN contracts_detail CD ON CD.contract_id = C.contract_id
WHERE C.user_id = ?
Hope this will help you.

MySQL subquery - Find only first record in a LEFT JOIN

I'm trying to display a list of member records, and I have a few tables I'm using to display what I need.
That's the easy part. The part I need help with is with a table that has many records to each member record: Login history
I want to display only the first row for each member record, that exists in the Login History table. Alternatively, I may want to flip flop and display the last record in the Login History table, as well.
here's what I've got so far:
SELECT m.memberid, m.membername, m.gender, mp.phone
FROM tbl_members m,
tbl_members_phones mp,
tbl_members_addresses ma
WHERE m.defaultphoneid = mp.phoneid
AND m.defaultaddressid = ma.addressid
So that returns what's expected.
The 2 columns from tbl_members_login_history I'd like to add to the returned result are: mh.loggedtime, mh.ipaddy
I know adding the tbl_members_login_history as a LEFT JOIN would return duplicates, so I'm thinking there must be a Subquery necessity here, in order to return just the 1st record for that memberid that exists in tbl_members_login_history.
What I'm worried about is if no record in the history table exists, I still want to display that member info, but leave the history columns as NULL.
Would this be a subquery incident? and if so, how does one add that type of LIMIT?
This is the greatest-n-per-group problem, which is asked frequently on Stack Overflow.
Here's how I would solve it in your scenario:
SELECT m.memberid, m.membername, m.gender, mp.phone, mh.loggedtime, mh.ipaddy
FROM tbl_members m
INNER JOIN tbl_members_phones mp ON m.defaultphoneid = mp.phoneid
INNER JOIN tbl_members_addresses ma ON m.defaultaddressid = ma.addressid
LEFT OUTER JOIN tbl_members_login_history mh ON m.memberid = mh.memberid
LEFT OUTER JOIN tbl_members_login_history mh2 ON m.memberid = mh2.memberid
AND mh.pk < mh2.pk
WHERE mh2.pk IS NULL;
That is, we want mh to be the most recent row in tbl_member_login_history for the given memberid. So we search for another row mh2 that is even more recent. If none more recent than the mh row is found, then mh2.* will be NULL, so mh must be the most recent.
I'm assuming this table has a primary key column that contains increasing values. For this example, I assume the column name is pk.
Using LEFT OUTER JOIN for both references to the login history table means that the m row will be reported even if there is no matching row.
add like this
LEFT OUTER JOIN (SELECT member_id, MAX(LAST_LOGIN_DATE) from tbl_members_login_history) Last_Login ON Last_Login.memberid = m.memberid
PS. LAST_LOGIN_DATE is pseudo column, you can try your restictive column

join on sub query returns fails

Trying to join a table "fab_qouta.qoutatype" to at value inside a sub query "fab_status_members.statustype" but it returns nothing.
If I join the 2 tables directly in a query the result is correct.
Like this:
select statustype, takst
from
fab_status_members AS sm
join fab_quota as fq
ON fq.quotatype = sm.statustype
So I must be doing something wrong, here the sub query code, any help appreciated
select
ju.id,
name,
statustype,
takst
from jos_users AS ju
join
( SELECT sm.Members AS MemberId, MaxDate , st.statustype
FROM fab_status_type AS st
JOIN fab_status_members AS sm
ON (st.id = sm.statustype) -- tabels are joined
JOIN
( SELECT members, MAX(pr_dato) AS MaxDate -- choose members and Maxdate from
FROM fab_status_members
WHERE pr_dato <= '2011-07-01'
GROUP BY members
)
AS sq
ON (sm.members = sq.members AND sm.pr_dato = sq.MaxDate)
) as TT
ON ju.id = TT.Memberid
join fab_quota as fq
ON fq.quotatype = TT.statustype
GROUP BY id
Guess the problem is in the line: join fab_quota as fq ON fq.quotatype = TT.statustype
But I can't seem to look through it :-(
Best regards
Thomas
It looks like you are joining down to the lowest combination of per member with their respective maximum pr_dato value for given date. I would pull THIS to the FIRST query position instead of being buried, then re-join it to the rest...
select STRAIGHT_JOIN
ju.id,
ju.name,
fst.statustype,
takst
from
( SELECT
members,
MAX(pr_dato) AS MaxDate
FROM
fab_status_members
WHERE
pr_dato <= '2011-07-01'
GROUP BY
members ) MaxDatePerMember
JOIN jos_users ju
on MaxDatePerMember.members = ju.ID
JOIN fab_status_members fsm
on MaxDatePerMember.members = fsm.members
AND MaxDatePerMember.MaxDate = fsm.pr_dato
JOIN fab_status_type fst
on fsm.statustype = fst.id
JOIN fab_quota as fq
on fst.statusType = fq.quotaType
I THINK I have all of what you want, and let me reiterate in simple words what I think you want. Each member can have multiple status entries (via Fab_Status_Members). You are looking for all members and what their MOST RECENT Status is as of a particular date. This is the first query.
From that, whatever users qualify, I'm joining to the user table to get their name info (first join).
Now, back to the complex part. From the first query that determined the most recent date status activity, re-join back to that same table (fab_status_members) and get the actual status code SPECIFIC to the last status date for that member (second join).
From the result of getting the correct STATUS per Member on the max date, you need to get the TYPE of status that code represented (third join to fab_status_type).
And finally, from knowing the fab_status_type, what is its quota type.
You shouldn't need the group by since the first query is grouped by the members ID and will return a single entry per person (UNLESS... its possible to have multiple status types in the same day in the fab_status_members table... unless that is a full date/time field, then you are ok)
Not sure of the "takst" column which table that comes from, but I try to completely qualify the table names (or aliases) they are coming from, buy my guess is its coming from the QuotaType table.
... EDIT from comment...
Sorry, yeah, FQ for the last join. As for it not returning any rows, I would try them one at a time and see where the break is... I would start one at a time... how many from the maxdate query, then add the join to users to make sure same record count returned. Then add the FSM (re-join) for specific member / date activity, THEN into the status type... somewhere along the chain its missing, and the only thing I can think of is a miss on the status type as any member status would have to be associated with one of the users, and it should find back to itself as that's where the max date originated from. I'm GUESSING its somewhere on the join to the status type or the quota.