Mysql join with one to many relations - mysql

claims
c_id claim id
1 201
2 202
3 203
4 204
claim_status
cs_id claim_id status
1 201 created
2 202 created
3 202 submitted
4 203 submitted
5 204 created
If the claim is created and submitted(like claim_id 202) it would not show up if i search with 'created' condition. this is my main requirement. i need result like below
If i search with status='created' i need to get records as below
c_id claim_id cs_id claim_id status
1 201 1 201 created
4 204 5 204 created
If i search with status='submitted' i need to get records as below
c_id claim_id cs_id claim_id status
2 202 3 202 submitted
3 203 4 203 submitted
But i'm unable to achive my result with below query. I'm new to stackoverflow. so please forgive me if i'm wrong in clear posting.
SQL:
SELECT * from claims c
INNER JOIN claim_status cs
ON c.claim_id = cs.claim_id
WHERE cs.status='created'
GROUP BY cs.claim_id

for 'created':
SELECT * from claims c
INNER JOIN claim_status cs
ON c.claim_id = cs.claim_id
WHERE cs.status='created'
for 'submitted':
SELECT * from claims c
INNER JOIN claim_status cs
ON c.claim_id = cs.claim_id
WHERE cs.status='submitted'

To get claims that are created but not submitted try this:
SELECT * from claims c
INNER JOIN claim_status cs
ON c.claim_id = cs.claim_id
WHERE cs.status='created'
AND NOT EXISTS (
SELECT 1
FROM claim_status cs2
WHERE cs2.claim_id = cs.claim_id
AND cs2.status='submitted'
)

Are those your actual tables? why are you handling claim statuses on another table than claims? You should have a "status" column on "claims" and you are done. You could have another table for versioning (if you want to get older states of the claim) and save changes, timestamps, etc, but for the current status is much better to have that on the same table
I know it's not the answer you are asking, but I think it's better to have something like:
SELECT * FROM claims WHERE status = 'created'
or
SELECT * FROM claims WHERE status = 'submitted'
It's faster, cleaner, better, everything (?)
Also, to make them even faster, status should be an integer column and "created" should have an associated number so you can do
SELECT * FROM claims WHERE status = <value that means created>
the same for submitted or any extra status

Related

How to UPDATE basing on SELECT results from 2 queries in mySQL?

Let assume we have 2 tables:
table name: hids
hid
status
001
2
002
1
003
1
004
2
005
2
...
unique on hid
and:
table name: times
hid
uid
time
001
00001
12345
001
00001
12567
001
00002
12540
001
00003
12541
001
00003
12567
002
00001
12575
...
(no uniques)
The problem is:
For a given user (eg. uid=00001) UPDATE status in "hids" with 0 if:
current status is 2
in the "times" table there isn't any record for any other user with time > (time of the latest entry for uid=00001 and the same hid)
Currently I do it with PHP is a way which is not too effecitve (thousends of atom queries). As the database grows over time (even for several milion records) the code is ineffective due to PHP overhead. Is there any option to make it simpler?
As noted in the comments, you should make the switch to using parameterized prepared statements. Given that you are currently using sprintf to inject your variables into your SQL, it will be a very small change.
You could significantly reduce the overhead of your current process by only returning the maximum time per hid for the given user -
SELECT times.hid, MAX(times.time) AS max_time
FROM times
JOIN hids ON times.hid = hids.hid
WHERE times.uid = 1
AND hids.status = 2
GROUP BY times.hid;
But a better option is to JOIN hids and times and then use NOT EXISTS (or LEFT JOIN IS NULL) to find where there is no other user with a greater time -
UPDATE hids h
JOIN times t ON h.hid = t.hid
SET h.status = 0
WHERE t.uid = 1
AND h.status = 2
AND NOT EXISTS (
SELECT 1 FROM times WHERE hid = t.hid AND uid <> t.uid AND time > t.time
)
Depending on the distribution of your data and how it is indexed you will probably get better performance by pre-calculating the max time per hid -
UPDATE hids h
JOIN (
SELECT t.hid, t.uid, MAX(time) AS max_time
FROM hids h
JOIN times t ON h.hid = t.hid
WHERE t.uid = 1
AND h.status = 2
GROUP BY t.hid, t.uid
) t ON h.hid = t.hid
SET h.status = 0
WHERE NOT EXISTS (
SELECT 1 FROM times WHERE hid = t.hid AND uid <> t.uid AND time > t.max_time
);

Multiple Counts from many INNER JOIN tables with Conditions

I'm having a lot of trouble figuring out how to write this query. Here's an exmaple of the data set and what I need to query:
**System Table**
SystemID Active
1 T
2 T
3 T
4 F
5 F
6 F
**BlogPost Table**
BlogPostID SystemID Create_Month
100 2 Jan
101 2 Jan
102 2 Feb
103 3 Feb
104 3 Mar
105 6 Mar
106 6 Mar
**Comment Table**
Comment ID BlogPostID Liked
201 100 T
202 100 T
203 100 T
204 102 T
205 102 T
206 102 T
207 103 F
So, In words, I'm trying to get: By month, show me all the active systems who created a post during that month, the number of posts they made in aggregate, and the count of the subset of those posts who had a comment that was like.
The end result would be like:
Column 1 - Month
Column 2 - Count of Active Systems where a Post Created in Month
Column 3 - Count of Posts Applicable to those systems
Column 4 - Count of Applicable Posts that had comments that were liked
I don't even know where to start really. My terrible "this is obviously wrong" attempt is below. Any help is much appreciated, thanks!
SELECT
Month,
COUNT(DISTINCT system.systemid),
COUNT(blogpost.BlogPostID)
COUNT(comments.commentiD)
FROM
system INNER JOIN
blogpost ON system.systemid = blogpost.systemid INNER JOIN
comments ON blogpost.BlogPostID = comment.BlogPostID
WHERE
system.active = T
AND comments.like = T
GROUP BY month
A complicated one !
SELECT
b.Create_Month,
COUNT(DISTINCT s.SystemID) as SystemCount,
COUNT(DISTINCT b.BlogPostID) as PostsCount,
COUNT(DISTINCT t.BlogPostID) as PostsWithLike
FROM System s
JOIN BlogPost b
ON s.systemID = b.systemID
AND s.Active = 'T'
LEFT JOIN Comment c
ON b.BlogPostID = c.BlogPostID
LEFT JOIN
(
SELECT DISTINCT c.BlogPostID as BlogPostID
FROM
Comment c
GROUP BY c.BlogPostID
HAVING SUM(if(c.Liked='T',1,0))>0
) as t
ON b.BlogPostID = t.BlogPostID
GROUP BY b.Create_Month
This is probably what you want :
SELECT s.systemid, active, bp.create_month, bp.systemid, COUNT(bp.blogpostid), COUNT(c.liked)
FROM system AS s
LEFT OUTER JOIN Blogpost AS bp ON s.systemid = bp.systemid
LEFT OUTER JOIN Comment AS c ON bp.blogpostid = c.blogpostid
WHERE active = 'T' AND c.Liked = 'T' GROUP BY s.systemid,bp.create_month

mySQL Query - select join if latest record in join table contains a specific value

I am trying to write a select statement with a right join (to clients), that will find a specific value in the join table - but ONLY if that is the most recent value for each client (ignoring blanks and nulls).
Clients
Id Name
0 John Doe
1 Frank Smith
2 Sue Smith
3 John Smith
Activity (join table)
ClientId Type Date
0 500 2013-01-01 00:00:08
1 900 2013-01-01 00:00:07
2 NULL 2013-01-01 00:00:06
3 2013-01-01 00:00:05
4 500 2013-01-01 00:00:05
0 800 2013-01-01 00:00:04
1 500 2013-01-01 00:00:03
2 500 2013-01-01 00:00:02
3 500 2013-01-01 00:00:01
4 800 2013-01-01 00:00:00
So this query will at least give me only the client records that have an activity type of 500 (in this case I would get back client 0 and 4):
select * from clients right join activity on activity.clientid = clients.id
where activity.type = 500
HOWEVER, I need to figure out how to make this return ONLY the first record in the above list of records. The logic there is Client #0 is the only client that has 500 as it's latest activity type = 500. The other 3 clients have NULL, blank, or 900 for example as their 'latest' activity type.
I am thinking some magic with ordering (the date would normally be pretty accurate), a 'top' and/or 'limit' and possibly union? Just cant quite wrap my head around it.
Please try this
SELECT activity.id AS activityid
, activity.type
, activity.date
, clients.id AS clientid
, clients.name
FROM activity
LEFT JOIN activity AS other_activities
ON activity.ClientID = other_activities.ClientID
AND activity.date < other_activities.date
LEFT JOIN clients
ON activity.ClientID = clients.id
WHERE activity.type = 500
AND other_activities.ClientID IS NULL;
SELECT * from Activity
INNER JOIN (SELECT MIN(Date) as min_date, clientID
FROM Activity
GROUP BY clientID) temp
ON Activity.clientID = temp.clientID
WHERE date = min_date and type = 500
This will return all clientID's whose most recent activity was of type 500.
This will get you the most recent Activity of type 500 and the client of that activity
SELECT * FROM
(SELECT *
FROM activity
WHERE type=500
ORDER BY date DESC
LIMIT 1) a
LEFT JOIN
clients c
ON (a.clientid = c.id)
of if you only want the result if it's the most recent activity and the type is 500 you can use
SELECT * FROM
(SELECT *
FROM activity
ORDER BY date DESC
LIMIT 1) a
LEFT JOIN
clients c
ON (a.clientid = c.id)
WHERE a.type = 500;
sqlFiddle here to get clients who have the latest activity of type 500
SELECT a1.ClientID,c.name,a1.Type,a1.Date
FROM activity a1
LEFT JOIN clients c ON (c.id = a1.clientid)
WHERE NOT EXISTS (SELECT 1
FROM activity a
WHERE a.clientid = a1.clientid
and a.date > a1.date)
AND a1.type = 500;

Join only first matching record from joining table without duplicate

I have two tables, 1. (inbox) keep delivery reports, 2. (outbox) keep send SMS. I can't add foreign key, and change datebase structure.
inbox
id number smsdate
-- ---------- -------------------
1 600600600 2013-08-16 11:51:18
2 700600600 2013-08-16 11:51:16
3 600600600 2013-08-16 11:51:14
4 900600600 2013-08-16 11:51:12
outbox
id number processed_date
--- ---------- -------------------
167 600600600 2013-08-16 10:51:10
288 700600600 2013-08-16 09:51:10
356 600600600 2013-08-16 08:51:10
473 900600600 2013-08-16 07:51:10
536 600600600 2013-08-16 06:51:10
I would now join the report of sent messages. I can do it in such a way that comparing the number and date of dispatch of the table outbox, with the same number and the nearest date of receipt of the table inbox. I am sure that the reports will be in order.
If i use
SELECT outbox.id, inbox.id, outbox.number, inbox.number,
outbox.processed_date, inbox.smsdate FROM outbox
LEFT JOIN inbox ON inbox.number= outbox.number
AND inbox.smsdate >= outbox.processed_date
GROUP BY outbox.id
ORDER BY outbox.id DESC;
I'm getting strange results and reports are duplicated. For if I have 3 sent, and 2 received, for the same number, it should be one empty. And instead of a blank for the latter, it duplicates my previous one.
I tried to add.
GROUP BY outbox.id, inbox.id
But it was even worse.
It is a way to solve this?
Desired output:
output
outbox.id inbox.id
--------- ----------
167 NULL
288 2
356 1
473 4
536 3
My approach is to use a correlated subquery to get the inbox id, and then join back to the inbox table to pull the columns you want:
select o.id, iid, o.number, i.number, o.processed_date, i.smsdate
from (select o.*,
(select i.id
from inbox i
where i.number = o.number and
i.smsdate >= o.processed_date
order by i.sms.date
limit 1
) iid
from outbox o
) o left outer join
inbox i
on o.iid = i.id
ORDER BY outbox.id DESC;
Try this::
SELECT outbox.id, inbox.id, outbox.number, inbox.number,
outbox.processed_date, inbox.smsdate FROM outbox
LEFT JOIN inbox ON inbox.number= outbox.number
WHERE inbox.smsdate >= outbox.processed_date

How to display a non matching value also?

USING VB6 AND MS-ACCESS 2003
So on…,
TABLE 1
EMPID DATE
101 22-07-2009
201 22-07-2009
501 22-07-2009
301 23-07-2009
401 23-07-2009
501 23-07-2009
101 24-07-2009
501 24-07-2009
So on…,
From the above table two tables I want to display all EMP ids for the date wise
Expected Output
EMPID DATE
101 22-07-2009
201 22-07-2009
301
401
501 22-07-2009
101
201
301 23-07-2009
401 23-07-2009
501 23-07-2009
101 24-07-2009
201
301
401
501 24-07-2009
So on…,
Need Query Help.
Haven't executed to verify for sure, but this should get you most of the way there:
SELECT
AllPossibleCardEvents.PersonId,
AllPossibleCardEvents.EmpName,
AllPossibleCardEvents.TitleCode,
AllPossibleCardEvents.TitleName,
AllPossibleCardEvents.CardEventDate,
ActualCardEvents.CardEventDate AS MatchingCardEventDate
FROM
(
(
SELECT
p.PersonId,
p.EmpName,
p.TitleCode,
p.TitleName,
AllDates.CardEventDate
FROM
(SELECT DISTINCT CardEventDate FROM T_Cardevent) AllDates,
T_Person p
) AllPossibleCardEvents
LEFT OUTER JOIN T_Cardevent ActualCardEvents ON
AllPossibleCardEvents.PersonId = Actual.PersonId AND
AllPossibleCardEvents.CardEventDate = Actual.CardEventDate
)
Where "MatchingCardEventDate" will be NULL for records that are NOT actual events. For actual events, the value of "MatchingCardEventDate" will be the valid date.
Hope this helps.
Without questioning your data model, to get the results you want you will need a third table (which I will call Dates) You need a Cross Join on Table 1 and Dates, which will give a result of all employees for all days. Then you need to Left Join to EmpID and Date.
The Left Join will include all of the results from the first join but only the matching rows from Table 2 will be populated. Access is funny in how it handles query structure, also it does not support the SQL-92 CROSS JOIN syntax, but it would look something like the below.
SELECT t1.EmpID, t2.Date
FROM (
SELECT t1.EmpID, d.Date
FROM [Table 1] AS t1,
Dates AS d
) AS DT1
LEFT OUTER JOIN [Table 2] AS t2
ON DT1.EmpID = t2.EmpID
AND DT1.Date = t2.Date
ORDER
BY DT1.Date, DT1.EmpID;