join on sub query returns fails - mysql

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.

Related

How to rewrite UNION with LEFT JOIN more efficiently

I have two tables...one that registers users and one that checks in users. A user will always have a single entry in the register table but a user may have 0 or multiple entries in the checkin table. For a raffle selector, I wrote a query that is picking 1 entry from the register table and then 1 entry from the checkin table - each sub query picks a random entry so long as that userID does not exist in a 3rd table that stores the raffle winners. After the two entries are returned than it randomly selects one of the two returned entries as the winnner.
However, I believe there should be a more efficient way of writing this so its ONLY picking an entry once....not picking two entries and then picking one of the two.
It took me quite a while to figure out how to correctly write the below query as I am not proficient in mysql at all. The query works and seems to work efficiently, but I believe there should be a better way of writing it that also consolidates the amount of query code.
Hoping someone here can help or advise.
Table note: clubusers/clubHistory have multiple overlapping columns but the tables are not the same:
register = clubUsers
checkins = clubHistory
winners = clubRaffleWinners
SELECT * FROM (
(SELECT ch.user_ID,ch.clID FROM clubHistory AS ch
LEFT OUTER JOIN clubRaffleWinners AS cr1 ON
ch.user_ID=cr1.user_ID
AND cr1.cID=1157
AND cr1.rafID=18
AND cr1.crID=1001
AND cr1.ceID=1167
AND cr1.chDate1='2022-06-04'
WHERE
ch.cID=1157
AND ch.crID=1001
AND ch.ceID=1167
AND ch.chDate='2022-06-04'
AND cr1.user_ID IS NULL
GROUP BY ch.user_ID ORDER BY RAND() LIMIT 1
)
UNION
(SELECT cu.user_ID,cu.clID FROM clubUsers AS cu
LEFT OUTER JOIN clubRaffleWinners AS cr2 ON
cu.user_ID=cr2.user_ID
AND cr2.cID=1157
AND cr2.rafID=18
AND cr2.crID=1001
AND cr2.ceID=1167
AND cr2.chDate1='2022-06-04'
WHERE
cu.cID=1157
AND cu.crID=1001
AND cu.ceID=1167
AND cu.calDate<='2022-06-04'
AND cr2.user_ID IS NULL
GROUP BY cu.user_ID ORDER BY RAND() LIMIT 1
)
) AS foo order by RAND() LIMIT 1 ;
UPDATE:
As #JettoMartinez points out below, my current query could in fact randomly return the same user from each table so the final returned entry would just be the same user. I didn't realize this in my struggles just to get the above query to work. Thus my original OP asking for a more optimized query simply selecting a single random entry from both tables (where that user is not already in the winners table) is applicable for yet another reason.
There are two ways I can think of (Do note that since I don't fully understand the tables, I'm not using all the conditions you used in your JOIN statements, meaning it might need more work):
Using a exclusive subquery:
SELECT
cu.user_ID,
cu.clID,
ch.cID
FROM
clubUsers cu
LEFT JOIN clubHistory ch ON ch.user_ID = cu.user_ID
WHERE user_ID NOT IN (
SELECT
user_ID
FROM
clubRaffleWinners
WHERE
-- other conditions
)
ORDER BY RAND() LIMIT 1;
Using a LEFT "OUTER" JOIN, as you asked for:
SELECT
cu.user_ID,
cu.clID,
ch.cID -- Or any relevant field from clubHistory, really
FROM
clubUsers cu
LEFT JOIN clubHistory ch ON ch.user_ID = cu.user_ID
LEFT JOIN clubRaffleWinners cr ON cr.user_ID = cu.user_ID
AND ... -- other conditions to ensure uniqueness
AND ... -- that could also be in the WHERE part
WHERE
cr.user_ID IS NULL -- this will filter out the INNER part of the JOIN
ORDER BY RAND() LIMIT 1;
I don't have a dataset to properly test this queries, so please take them as a concept. I also didn't queried in clubHistory since I honestly don't see the point of doing so. Interpolating clubRaggleWinners to clubUsers seems enough for me.
EDIT
Since the user_ID in clubHistory is relevant to the raffle, I added a LEFT JOIN to it and added a field from said table in the SELECT statement, so that the user_id repeats once per entry in clubHistory plus the row of clubUsers, meaning that every user has 1 + number of entries / number of users + number of entries - number of winners chances to win.
This logic can be applied to the first query with a subquery too, and if the added field needs to be out, the query could be wrapped in a CTE or a subquery.
From what you are describing, and I want to make sure I understand.
Every registered person is qualified 1 entry.
However, each time they have checked in, they get 1 entry for each time they checked in. So, for someone registered and has NEVER checked-in, they get 1 entry. But if someone registered, and checked in 3 times, they would get a total of JUST the 3 times they checked in, vs 4 just for being registered.
Regardless of who is POSSIBLE, you want to EXCLUDE all people who have already been a winner in the raffle.
You SHOULD be able to get results from this below. Since the columns appear to be the same filtering on the cID, crID, ceID and Date, I have the primary FROM based on the registered clubUsers.
From that, a left-join to the clubHistory will either allow that person's ID to be returned once if only registered, OR multiple times based on the times checked in such as the example.
From the given user, I am also directly left-joining to the raffle winning history on the same criteria. If its the same criteria to the club history join, and the same criteria to the raffle (with exception of rafID = 18), appearing to indicate a specific raffle being drawn for, If the person is found, or not, the final WHERE accounts to exclude if its the single entry, or multiple entries via the IS NULL test.
The query will return all entries single or multiple, that have not already won in the order by RAND() qualifier, and apply a single LIMIT 1 to get the final winner. I dont know why you needed what appeared to be the clubhouse ID when you only really care about WHO won, without any regard to being a clubhouse history entry or not.
SELECT
cu.user_ID
FROM
clubUsers AS cu
LEFT JOIN clubHistory ch
on cu.user_ID = ch.user_ID
AND cu.cID = ch.cID
AND cu.crID = ch.crID
AND cu.ceID = ch.ceID
AND ch.chDate = '2022-06-04'
LEFT JOIN clubRaffleWinners AS crw
ON cu.user_ID = crw.user_ID
AND cu.cID = crw.cID
AND cu.crID = crw.crID
AND cu.ceID = crw.ceID
AND crw.chDate1 = '2022-06-04'
AND crw.rafID = 18
WHERE
cu.cID = 1157
AND cu.crID = 1001
AND cu.ceID = 1167
AND cu.calDate <= '2022-06-04'
AND crw.user_id IS NULL
order by
RAND()
LIMIT 1
For performance purposes, I would ensure the following indexes
table index
clubUsers ( cid, crID, ceID, calDate, user_id )
clubHistory ( user_id, cID, crID, ceID, chDate )
clubRaffleWinners ( user_id, cID, crID, ceID, chDate1, rafID )
(Just a Comment, but need formatting.)
I would start by trying to put these 4 values in a single table, not repeated across 3 tables:
cu.cID=1157
AND cu.crID=1001
AND cu.ceID=1167
AND cu.calDate<='2022-06-04'
Please provide SHOW CREATE TABLE for each table; then I can assess whether the recommended indexes make sense.

Use SELECT through three table

I tried to write a query, but unfortunately I didn't succeed.
I want to know how many packages delivered over a given period by a person.
So I want to know how many packages were delivered by John (user_id = 1) between 01-02-18 and 28-02-18. John drives another car (another plate_id) every day.
(orders_drivers.user_id, plates.plate_name, orders.delivery_date, orders.package_amount)
I have 3 table:
orders with plate_id delivery_date package_amount
plates with plate_id plate_name
orders_drivers with plate_id plate_date user_id
I tried some solutions but didn't get the expected result. Thanks!
Try using JOINS as shown below:
SELECT SUM(o.package_amount)
FROM orders o INNER JOIN orders_drivers od
ON o.plate_id=od.plate_id
WHERE od.user_id=<the_user_id>;
See MySQL Join Made Easy for insight.
You can also use a subquery:
SELECT SUM(o.package_amount)
FROM orders o
WHERE EXISTS (SELECT 1
FROM orders_drivers od
WHERE user_id=<user_id> AND o.plate_id=od.plate_id);
SELECT sum(orders.package_amount) AS amount
FROM orders
LEFT JOIN plates ON orders.plate_id = orders_drivers.plate_id
LEFT JOIN orders_driver ON orders.plate_id = orders_drivers.plate_id
WHERE orders.delivery_date > date1 AND orders.delivery_date < date2 AND orders_driver.user_id = userid
GROUP BY orders_drivers.user_id
But seriously, you need to ask questions that makes more sense.
sum is a function to add all values that has been grouped by GROUP BY.
LEFT JOIN connects all tables by id = id. Any other join can do this in this case, as all ids are unique (at least I hope).
WHERE, where you give the dates and user.
And GROUP BY userid, so if there are more records of the same id, they are returned as one (and summed by their pack amount.)
With the AS, your result is returned under the name 'amount',
If you want the total of packageamount by user in a period, you can use this query:
UPDATE: add a where clause on user_id, to retrieve John related data
SELECT od.user_id
, p.plate_name
, SUM(o.package_amount) AS TotalPackageAmount
FROM orders_drivers od
JOIN plates p
ON o.plate_id = od.plate_id
JOIN orders o
ON o.plate_id = od.plate_id
WHERE o.delivery_date BETWEEN convert(datetime,01/02/2018,103) AND convert(datetime,28/02/2018,103)
AND od.user_id = 1
GROUP BY od.user_id
, p.plate_name
It groups rows on user_id and plate_name, filter a period of delivery_date(s) and then calculate the sum of packageamount for the group

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.

MAX() value on a LEFT JOIN

I have 2 tables, one with a set of proposals and one with votes cast on the proposals, both joined with a unique ID.
The proposals table has a date field when the proposal was started, the votes have a date when the vote was cast. The proposal is marked as "success" when enough votes are cast. I want to find out how many days it takes in average to close votes.
My initial approach was to create a query that lists each proposal with the most recent vote through a left join:
SELECT proposals.pr_id, DATEDIFF(MAX(proposals_votes.`date`), proposals.`date`)
FROM proposals
LEFT JOIN proposals_votes ON proposals.pr_id = proposals_votes.pr_id
WHERE STATUS = 'success'
The issue is that this returns only one line, which is a surprise to me. I would have thought the MAX is done on the LEFT JOIN table and not on the resulting table.
Is there a way to do this within the LEFT JOIN or do I need to do a sub-query?
Use GROUP BY clause on pr_id to fetch no of days for each proposals
Try this:
SELECT p.pr_id, DATEDIFF(MAX(pv.`date`), p.`date`)
FROM proposals p
LEFT JOIN proposals_votes pv ON p.pr_id = pv.pr_id
WHERE pv.status = 'success'
GROUP BY p.pr_id;
If you want to multiple line you should use GROUP BY because of aggregate function always return only one rows. You will get multiple rows result by GROUP BY parameter like GROUP BY proposals.pr_id.
SELECT
proposals.pr_id,
DATEDIFF(MAX(proposals_votes.date),proposals.date)
FROM
proposals
LEFT JOIN
proposals_votes ON proposals.pr_id = proposals_votes.pr_id
WHERE
STATUS = 'success'
GROUP BY proposals.pr_id;

Stepped table not sorting

I'm trying to create a stepped table report using SQL report builder 3.0. The stepped report contains Groups/devices/users along with associated totals for each group/device/user.
I want the entire report to be sorted by these totals along with each individual step sorted this way also.
Currently users are sorted by their totals, but not devices or groups.
Is there a way to sort the other steps?
You can just do this in SQL using some nested queries. Let's assume you have the following tables: Transaction, User, Device, and Group. The transaction table records the transactions of the User on a Device and has an Amount field to sum. A user belongs to a Group.
So you need to sum the Amount for the User, for the Groups and for the Devices used within a Group which will give you SQL that looks like this:
SELECT G.Description AS [Group], D.Description AS Device, U.Description AS UserName, MAX(GT.GroupTotal) AS GroupTotal, MAX(GDT.GroupDeviceTotal) AS GroupDeviceTotal, SUM(T.Amount) AS UserTotal
FROM Transaction AS T
INNER JOIN User AS U ON L.UserId = F.UserId
INNER JOIN Group AS G ON G.GroupId = L.GroupId
INNER JOIN Device AS D ON T.DeviceId = L.DeviceId
INNER JOIN
(SELECT GroupId, SUM(Amount) AS GroupTotal
FROM Transaction
INNER JOIN User ON User.UserId = Transaction.UserId
WHERE (Transaction.TxDate >= '2011-01-01')
GROUP BY User.GroupId) AS GT ON GT.GroupId = U.GroupId
INNER JOIN
(SELECT GroupId, DeviceId, SUM(Amount) AS GroupDeviceTotal
FROM Transaction
INNER JOIN User ON User.UserId = Transaction.UserId
WHERE (TxDate >= '2011-01-01')
GROUP BY GroupId, DeviceId) AS GDT ON GDT.GroupId = U.GroupId AND GDT.DeviceId = T.DeviceId
WHERE (T.TxDate >= '2011-01-01')
GROUP BY G.GroupId, D.DeviceId, U.UserId
ORDER BY GroupTotal DESC, GroupDeviceTotal DESC, UserTotal DESC
Note that the where clause you use has to be the same in the main query and each nested query (this is the "WHERE (T.TxDate >= '2011-01-01')" bit).
You can try going to the Row/Column groups area... then for each group you have, double click the group, select "Sorting" and then add as many sorting fields as you need for the info contained at that group level.
If you have other sorts applied on the data... such as to the tablix/matrix, sometimes SSRS can get confused, so If my suggestion does help you with the effect you're going for but there are some issues, try removing all other sorting you've applied to the data elsewhere in the report besides on those groups... And I would start with the innermost and work out, trying not to repeat a field that is in a lower group's data. (if that makes sense).
edit:
So, let's say we have a report for a vet's office that shows client information, and we want to group by personID, petID and visitID. The tablix as a whole would be sorted by the person's name (or last name, then first name... or whatever). Then your first group would group on the personID and be sorted by the petName. The second, lower group would group on the petID and be sorted by the visitDate. The third level would group on the visitID, and... this doesn't really need to be sorted unless by visitTime if its not included in visitDate.