MySQL - Query For Rows With Updates Based On Another Table - mysql

I have a table called listings with many fields like id, name, address etc.
I have another table called listing_updates with many of the same fields like id, name, address and also a status field with values like NEW, CANCELLED, CLOSED, etc.
A listing has many listing updates (one-to-many)
I want a query that does the following:
SELECT all of the listings where the latest update for that listing is in the NEW status
The "latest update" in the case is defined by the row with the greatest id (auto-incrementing) with the correct listing association.
Hopefully, any answer given will be able to expand to if we add more statuses in future.

The following Query selects all listings for which the latest update has status "NEW":
SELECT
l.*,
u.status
FROM
listings AS l
INNER JOIN listing_updates AS u ON (l.id = u.listing_id)
WHERE
u.id = (
SELECT MAX(id)
FROM listing_updates AS u2
WHERE u2.listing_id = l.id
)
AND u.status = 'NEW'
SELECT, FROM and JOIN should be pretty self explanatory if you know basic SQL. In the WHERE clause the id of the update record is restricted to the last (MAX) id of all updates for the current listing using a subquery. Last only results with status "NEW" are selected. This discards all listings, which have a last update with a different status.

Related

MYSQL count listings for each user where listing published in past

I have a MYSQL query that I am having difficulties getting to do what I want.
I have a users table (userstbl) containing all my user records, and a listings table (listings) contains all listings posted by each user. I am trying to select the name and address of each user and provide a count of listings for each user which was listed between a certain date range, but only count adverts for unique category_id's which is working fine.
The issue is that I only want to count listings that have been published. I have another table which is identical to my listings table called "listings_log" and contains a record for every change made to every listing record. If one of the records in "listings_log" for the listing has a "listings_log.published=1" than the listing was published. Each record in the "listings_log" table has a "listing_id" which is the same as in the "listings" table.
This is the query I have now :
SELECT
userstbl.userid,
userstbl.fullname,
userstbl.fulladdress,
COUNT(DISTINCT(
CASE WHEN listings.ad_type = 1
AND DATE(listings.date_listed) BETWEEN '2018-01-01' AND '2018-04-01'
THEN listings.category_id
END )
) AS Listings_Count_2018,
DATE_FORMAT(userstbl.reg_date, "%d/%m/%Y") AS RegisteredDate
FROM
users
LEFT JOIN listings ON listings.userid = userstbl.user_id
GROUP BY userstbl.userid
This counts the number of unique listings records between the correct dates for each user.
But I somehow only need to count listings records, where there is a corresponding listings_log record for that listing with published set to "1". The "listings_log" table and "listings" table both have a common listing_id column, but the listings_log table can have multiple records for each listing showing every change to each listing.
So I want to also join on the listings_log.listing_id = listings.listing_id and at least one of the "listings_log" records for that "listing_id" has listings_log.published = "1".
As you did not provide sample tables and a minimal reproducible example, a lot of this is guesswork. I am assuming for each user you want the total number of listing records. I built up the SQL with subqueries that are meant to be read "from the inside out."
select u.userid, u.fullname, u.fulladdress, sq.count from usertbl u join (
select u.userid, sum(c.count) as count from usertbl u join (
select count(*) as count, l.userid, l.listing_id from listings l join (
select distinct listing_id from listings_log where listings_log.published = "1"
) ll on l.listing_id = ll.listing_id
and l.ad_type = 1
and date(l.date_listed) between '2018-01-01' and '2018-04-01'
group by l.userid, l.listing_id
) c on u.userid = c.userid
group by u.userid
) sq on u.userid = sq.userid
;
See DB Fiddle

Add to VIEW from another table

I need to find out how to pull user names into a VIEW from a different table. I have 3 tables, User, Lead, and Lead_detail. In the users table there is an ID field which is stored in the Created_By field in the Lead table. In the Lead table I have an ID field that is stored in the Lead_detail Lead_ID field.
I have created a VIEW to the Lead_detail table which pulls all the info I need but I have found that I don't have the users name in that VIEW so I need to ALTER my view to add in the users names per lead but I am having trouble with the statement.
Before altering the VIEW I wanted to try a SELECT statement to see if I get any data,
SELECT * FROM Lead_detail
JOIN Lead
ON Lead_detail.lead_id = Lead.id
WHERE Lead.Created_by = Users.ID
But this didn't work. What would be a correct statement so that I can pull the users names into the Lead VIEW?
I think you missed a join to the Users table:
SELECT
*
FROM
Lead_detail
INNER JOIN Lead
ON Lead_detail.lead_id = Lead.id
INNER JOIN Users
ON Lead.Created_by = Users.ID

Querying a large table using mysql

I manage a property website. I have a table with banned users (small table) and a table called advert_views which keeps track of each listing that each user views (currently 1.3m lines and growing). The advert_views table alsio takes note of the IP address for every advert viewed).
I want to get the IP addresses used by the banned users and check if any of these banned users have opened new accounts. I ran the following query:
SELECT adviews.user_id AS 'banned user_id',
adviews.client_ip AS 'IPs used by banned users',
adviews2.user_id AS 'banned users that opened a new account'
FROM banned_users
LEFT JOIN users on users.email_address = banned_users.email_address #since I don't store the user_id in banned_users
LEFT JOIN advert_views adviews ON adviews.user_id = users.id AND adviews.user_id IS NOT NULL # users may view listings when not logged in but they have restricted access to the information on the listing
LEFT JOIN (SELECT client_ip,
user_id
FROM advert_views
WHERE user_id IS NOT NULL
) adviews2
ON adviews2.client_ip = adviews.client_ip
WHERE banned_users.rec_status = 1 and adviews.user_id <> adviews2.user_id
GROUP BY adviews2.user_id
I applied an index on the advert_views table and the users table as per below:
enter image description here
My query takes half an hour to execute. Is there a way how to improve my query speed?
Thanks!
Chris
First of all: Why do you outer join the tables? Or better: Why do you try to outer join the tables? A left join is meant to get data from a table even when there is no match. But then your results could contain rows with all values null. (That doesn't happen though, because adviews.user_id <> adviews2.user_id in your where clause dismisses all outer-joined rows.) Don't give the DBMS more work to do than necessary. If you want inner joins, then don't outer join. (Though the difference in execution time won't be huge.)
Next: You select from banned_users, but you only use it to check existence. You shouldn't do this. Use an EXISTS or IN clause instead. (This is mainly for readability and in order not to produce duplicate results. This probably won't speed things up.)
SELECT av1.user_id AS 'banned user_id',
av2.client_ip AS 'IPs used by banned users',
av2.user_id AS 'banned users that opened a new account'
FROM adviews av1
JOIN adviews av2 ON av2.client_ip = av1.client_ip AND av2.user_id <> av1.user_id
WHERE av1.user_id IN
(
SELECT user_id
FROM users
WHERE email_address IN (select email_address from banned_users where rec_status = 1)
)
GROUP BY av2.user_id;
You may replace the inner IN clause with a join. It's mostly a matter of personal preference, but it is also that in the past MySQL sometimes didn't perform well on IN clauses, so many people made it a habit to join instead.
WHERE av1.user_id IN
(
SELECT u.user_id
FROM users u
JOIN banned_users bu ON bu.email_address = u.email_address
WHERE bu.rec_status = 1
)
At last consider removing the GROUP BY clause. It reduces your results to one row per reusing user_id, showing one of its related banned user_ids (arbitrarily chosen in case there is more than one). I don't know your tables. Are you getting many records per reusing user_id? If not, remove the clause.
As to indexes I suggest:
banned_users(rec_status, email_address)
users(email_address, user_id)
adviews(user_id, client_ip)
adviews(client_ip, user_id)

SQL making new table from existing tables

I have this very basic problem and just can't figure out how to do it. I have two tables.
The first one, users, contains two columns: id which is just number representing a person and sex. Second column doesn't matter now.
The other table, orders has columns: id_user, time, state. The id_user refers to id in the first table. state has three different values (finished, canceled, new). I need to make a table that would show count of finished state (how many finished states one person has) next to the id of that person. I can run this thing:
select * , count(state) as CountOfFinished from orders
where state = 'finished'
group by id_user;
to show me that information, but i need to have a table that would show id,sex, CountOfFinished.
I copied the first table to a new one, but don't know how to add the CountOfFinished column next to these two. I don't even know how to make a column out of it so I can join it or something.
Any idea what should I do?
You don't need a new table. What you want is a JOIN, or in this case, a LEFT JOIN:
SELECT
u.id,
u.sex,
ISNULL(COUNT(o.state), 0) as CountOfFinished
FROM users u
LEFT JOIN orders o
ON o.id_user = u.id
AND o.state = 'finished'
GROUP BY
u.id, u.sex
The above query will list all users and the number of finished orders. If you want to list only users with at least one finished order, use INNER JOIN.
To insert into a new table, create the table first and use INSERT INTO:
CREATE TABLE FinishedOrderCountByUser(
UserId INT,
Sex CHAR(1),
CountOfFinished INT
)
INSERT INTO FinishedOrderCountByUser(UserId, Sex, CountOfFinished)
SELECT
u.id,
u.sex,
ISNULL(COUNT(o.state), 0) as CountOfFinished
FROM users u
LEFT JOIN orders o
ON o.id_user = u.id
AND o.state = 'finished'
GROUP BY
u.id, u.sex

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.