Get related table's related record in mysql - mysql

I have three tables ClaimHeader, ResClaim and ResActivity. ClaimHeader table's primary key is used as foreign key in ResClaim table and ResClaim table's primary key is used as foreign key in ResActivity table.
Below is my tables
ClaimHeader:
HeaderID FileID FileName
1 fileid1 file1.xml
2 fileid2 file2.xml
3 fileid3 file3.xml
4 fileid4 file4.xml
--------------------------------------------------
ResClaim:
ClaimPKID HeaderPKID ClaimDateSettlement
1 1 2017-04-08
2 1 2017-03-08
3 2 2017-04-10
4 3 2017-05-08
--------------------------------------------------
ResActivity:
ActivityPKID ClaimPKID ActivityNet
1 1 400
2 2 3000
3 2 2030
4 3 5000
Tables screenshot
ResClaim table uses HeaderPKID as the foreign key from ClaimHeader table and ResActivity table uses ClaimPKID as the foreign key from ResClaim table
My scenario is i should display related record from all the three tables.
For example i want to display FileID from ClaimHeader table , Total claims count from ResClaim table and Sum of ActivityNet from ResActivity table with the matching condtion.
My expected result would be:
FileID | Total Claim(s) | ActivityNet
--------------------------------------------------
fileid2 | 1 | 5030 (3000+2030)
--------------------------------------------------
I have tried below query:
SELECT
`ClaimHeader`.*,
count(ResClaim.ClaimPKID) as claims,
sum(ResActivity.ActivityNet) as net
FROM
`ClaimHeader`
RIGHT OUTER JOIN `ResClaim`
ON ResClaim.ClaimPKID = ClaimHeader.HeaderID
RIGHT OUTER JOIN `ResActivity`
ON ResClaim.ClaimPKID = ResActivity.ActivityPKID
The above query is not returning related record values - instead of that it's returning sum of all the columns and count from ResActivity and ResClaim table.

It is still not completely clear what you are asking. It seems you either want one result row per claim header or one result row per file. So group by the column in question.
By joining all records you get of course claim headers and claims multifold, e.g. with
claim headers: head1, head2
claims for head1: claim10, claim11
claims for head2: claim20, claim21
actions for claim 10: action100, action101
actions for claim 11: action110, action111
actions for claim 20: action200, action201
you get this intermediate result from the joins:
header | claim | action
-------+---------+----------
head1 | claim10 | action100
head1 | claim10 | action101
head1 | claim11 | action110
head1 | claim11 | action111
head2 | claim20 | action200
...
Now by grouping per header, you get one aggregated result row for head1, one for head2. As the intermediate result contains one record per action, you can easily sum them. But as to claims: there are four records for head1, and if you do count(*) or count(claimpkid) you get a result of 4 (count(claimpkid) counts all claimpkid that are not null, which is always the case in this example). What you want to do instead is counting distinct claims (namly two here: claim10 and claim11). So use COUNT DISTINCT.
select
h.headerid,
count(distinct c.claimpkid) as claims,
coalesce(sum(a.activitynet), 0) as net
from claimheader h
left outer join resclaim c on c.claimpkid = h.headerid
left outer join resactivity a on a.activitypkid = c.claimpkid
group by h.headerid
order by h.headerid;
I am using left outer joins (and COALESCE) for the case that a header has no claims or a claim has no actions, for which we would show zeros rather then removing them from the results.
As mentioned, if you want this per file, then select fileid and group by it instead of headerid.

Related

How to add a tag based on a column value

I'm trying to join two tables and select certain columns to display in the output including a 'flag' if a certain transaction amount is greater than or equal to 100. The flag would return a 1 if it is, else null.
I thought I could achieve this using a CASE in my SELECT but it only returns one record every time since it returns the first record that meets this condition. How do I just create this 'FLAG' column during my join easily?
SELECT payment_id, amount, type,
CASE
WHEN amount >= 100 THEN 1
ELSE NULL
END AS flag
FROM trans JOIN customers ON (user_id = cust_id)
JOIN bank ON (trans.bank = bank.id)
WHERE (error is false)
I expect an output such as:
payment_id amount type flag
1 81 3 NULL
2 104 2 1
3 150 2 1
4 234 1 1
However, I'm only getting the first record such as:
payment_id amount type flag
2 104 2 1
I tried your table structure in my local and it is working perfectly.
I need one thing from you is in which table you are having error column.
If I comment where condition then it is working fine.
If you're getting fewer rows than you expect, it's either due to:
Join condition
You're doing a INNER joins to the customers and bank tables. If you have 4 source rows in your trans table, but only one row that matches in your customers table (condition user_id = cust_id), then you will only have one row returned.
The same goes for the subsequent join to your bank table. If there you somehow have a transaction that references a bank which is not defined in the bank table, then you won't see a record for this row.
WHERE clause
Obviously you won't see any rows that don't meet the conditions specified here.
It's probably #1 -- check to see if the rows with payment_id IN (1,3,4) have corresponding user id values in the user table and corresponding bank id values in the banks table.

Return all rows from table and add extra column to indicate pivot table inclusion

I'm trying to create the data for a form with some grouped checkboxes, which includes the user's previous selections. To prepare the data, I'm trying to return all the rows from my checkbox visibility table and add an extra column which indicates whether or not a row from the visibility table appears in the pivot table users_visibility. How do I do this?
Here is what I have. It returns what the two tables have in common based on a user's id.
select
visibility.id as visibility_id,
visibility.title as visibility_title,
users_visibility.users_id as checked
from visibility
left join users_visibility on users_visibility.visibility_id = visibility.id
where users_visibility.users_id = 2
Tables example
visibility
=======================
id title
-----------------------
1 Drivers licence
2 No Criminal record
3 Senior volunteer
users_visibility
===========================
id users_id visibility_id
---------------------------
1 2 3
What I would like returned
========================================
visibility_id visibility_title checked
----------------------------------------
1 Drivers licence NULL
2 No Criminal record NULL
3 Senior volunteer 2 (user_id or whatever indicator)
Thanks.
For a left join, conditions on all but the first table should be in the on clause.
Table aliases would also make the query easier to write and to read:
select v.id as visibility_id, v.title as visibility_title,
uv.users_id as checked
from visibility v left join
users_visibility uv
on uv.visibility_id = v.id and uv.users_id = 2;

mysql - Keeping unique records after removing parent child relationship

In a mysql database, I have a business Unit table which maintain the hierarchy of a client's business units. Each business unit can have a parent and/or a child.
products_client_1.business_units
id parent_id
1
2 1
3 1
4 1
8 1
14 3
17 2
31 1
35 4
36 1
37 4
38 2
39 31
40 8
41 3
42 31
43
44 43
Currently, I have a customerId table which maintains the customerId at a business unit level
contacts_client_1.buid_customer_id
global_id customer_id bu_id
ABC1000033 1812130 2
ABC1000033 1812130 54
ABC1000034 4049809 2
ABC1000035 5630631 2
ABC1000082 5707052 2
ABC1000082 1111116 54
ABC1000091 5813085 2
ABC1000091 5813085 54
ABC1000093 5208477 2
ABC1000115 5045891 2
ABC1000115 5045891 54
ABC1000117 6114245 2
ABC1000117 6114247 54
ABC1000117 6114247 1
ABC1000111 1234567 38
ABC1000100 9023456 43
ABC1000100 9023457 44
Going forward, I do not want to maintain the customer id at individual business unit level. It should be unique for a given globalId. For this I want to migrate the existing customer id data based on the following condition.
If a globalId has customerId for a only single BU, migrate it as it is without bu_id.
If a globalId has customerId for 2 BUs (they can be parent-child at any level), keep the customerId of the parent most available BU.
required table contacts_client_1.customer_id
global_id customer_id
ABC1000033 1812130
ABC1000034 4049809
ABC1000035 5630631
ABC1000082 5707052
ABC1000091 5813085
ABC1000093 5208477
ABC1000100 9023456
ABC1000111 1234567
ABC1000115 5045891
ABC1000117 6114247
PS:
globalId are not overlapping among different parent most BUs.
business_unit table is under products_client_1 schema and buid_customer_id table is under contacts_client_1 schema.
The same code should be executable for different clients.
This is a one time migration.
Need help in writing the query.
I'm not sure what exactly you are going to do with your data, but the following should help:
Show only rows which have no parent for the same global_id in the buid_customer_id table:
select child.*
from contacts_client_1.buid_customer_id child
left join products_client_1.business_units bu
on bu.id = child.bu_id
left join contacts_client_1.buid_customer_id parent
on parent.global_id = child.global_id
and parent.bu_id = bu.parent_id
where parent.global_id is null
Examples:
Row (ABC1000100 9023456 43) - The bu_id (43) has no parent in buid_customer_id, so there will be no match for the first LEFT JOIN and also no match for the second. Since all columns from the left joined tables will be NULL, parent.global_id is null is TRUE and the row will be selected.
Row (ABC1000100 9023457 44) - The bu_id (44) has a parent_id (43), so the first JOIN will find a match. The second JOIN will also find a match, because a row with the parent BU and the same global_id exists in the buid_customer_id table. Thus parent.global_id is not NULL and the row won't be selected.
Row (ABC1000033 1812130 2) - The bu_id (2) has a parent_id (1). The first JOIN will find a match. But tere is no row in the buid_customer_id table with bu_id = 1 and global_id = ABC1000033, so there is no match for the second JOIN. Thus parent.global_id will be NULL and the row will be selected.
Now you can use this statement to copy (migrate) the data to a new table with
insert into new_table
select child.*
[..]
You can also go the other way. If you replace the LEFT JOINs with INNER JOINs and remove the WHERE clause, you will get the opposite result (all rows which are not returned by the first query). You can use it to remove all those rows from the table.
Delete all rows which have a parent row for the same global_id:
delete child
from contacts_client_1.buid_customer_id child
join products_client_1.business_units bu
on bu.id = child.bu_id
join contacts_client_1.buid_customer_id parent
on parent.global_id = child.global_id
and parent.bu_id = bu.parent_id
Now the table buid_customer_id will contain the same rows which are selected by the first query. If this data needs to be in another table - just rename it. Then you can copy global_id and customer_id with
insert into customer_id (global_id, customer_id)
select global_id, customer_id
from new_table

SQL Validate a column with the same column

I have the following situation. I have a table with all info of article. I will like to compare the same column with it self. because I have multiple type of article. Single product and Master product. the only way that I have to differences it, is by SKU. for example.
ID | SKU
1 | 11111
2 | 11112
3 | 11113
4 | 11113-5
5 | 11113-8
6 | 11114
7 | 11115
8 | 11115-1-W
9 | 11115-2
10 | 11116
I only want to list or / and count only the sku that are full unique. follow th example the sku that are unique and no have variant are (ID = 1, 2, 6 and 10) I will want to create a query where if 11113 are again on the column not cout it. so in total I will be 4 unique sku and not "6 (on total)". Please let me know. if this are possible.
Assuming the length of master SKUs are 5 characters, try this:
select a.*
from mytable a
left join mytable b on b.sku like concat(a.sku, '%')
where length(a.sku) = 5
and b.sku is null
This query joins master SKUs to child ones, but filters out successful joins - leaving only solitary master SKUs.
You can do this by grouping and counting the unique rows.
First, we will need to take your table and add a new column, MasterSKU. This will be the first five characters of the SKU column. Once we have the MasterSKU, we can then GROUP BY it. This will bundle together all of the rows having the same MasterSKU. Once we are grouping we get access to aggregate functions like COUNT(). We will use that function to count the number of rows for each MasterSKU. Then, we will filter out any rows that have a COUNT() over 1. That will leave you with only the unique rows remaining.
Take that unique list and LEFT JOIN it back into your original table to grab the IDs.
SELECT ID, A.MasterSKU
FROM (
SELECT
MasterSKU = SUBSTRING(SKU,1,5),
MasterSKUCount = COUNT(*)
FROM MyTable
GROUP BY SUBSTRING(SKU,1,5)
HAVING COUNT(*) = 1
) AS A
LEFT JOIN (
SELECT
ID,
MasterSKU = SUBSTRING(SKU,1,5)
FROM MyTable
) AS B
ON A.MasterSKU = B.MasterSKU
Now one thing I noticed from you example. The original SKU column really looks like three columns in one. We have multiple values being joined with hypens.
11115-1-W
There may be a reason for it, but most likely this violates first normal form and will make the database hard to query. It's part of the reason why such a complicated query is needed. If the SKU column really represents multiple things then we may want to consider breaking it out into MasterSKU, Version, and Color or whatever each hyphen represents.

GROUP BY does not remove duplicates

I have a watchlist system that I've coded, in the overview of the users' watchlist, they would see a list of records, however the list shows duplicates when in the database it only shows the exact, correct number.
I've tried GROUP BY watch.watch_id, GROUP BY rec.record_id, none of any types of group I've tried seems to remove duplicates. I'm not sure what I'm doing wrong.
SELECT watch.watch_date,
rec.street_number,
rec.street_name,
rec.city,
rec.state,
rec.country,
usr.username
FROM
(
watchlist watch
LEFT OUTER JOIN records rec ON rec.record_id = watch.record_id
LEFT OUTER JOIN members usr ON rec.user_id = usr.user_id
)
WHERE watch.user_id = 1
GROUP BY watch.watch_id
LIMIT 0, 25
The watchlist table looks like this:
+----------+---------+-----------+------------+
| watch_id | user_id | record_id | watch_date |
+----------+---------+-----------+------------+
| 13 | 1 | 22 | 1314038274 |
| 14 | 1 | 25 | 1314038995 |
+----------+---------+-----------+------------+
GROUP BY does not "remove duplicates". GROUP BY allows for aggregation. If all you want is to combine duplicated rows, use SELECT DISTINCT.
If you need to combine rows that are duplicate in some columns, use GROUP BY but you need to to specify what to do with the other columns. You can either omit them (by not listing them in the SELECT clause) or aggregate them (using functions like SUM, MIN, and AVG). For example:
SELECT watch.watch_id, COUNT(rec.street_number), MAX(watch.watch_date)
... GROUP by watch.watch_id
EDIT
The OP asked for some clarification.
Consider the "view" -- all the data put together by the FROMs and JOINs and the WHEREs -- call that V. There are two things you might want to do.
First, you might have completely duplicate rows that you wish to combine:
a b c
- - -
1 2 3
1 2 3
3 4 5
Then simply use DISTINCT
SELECT DISTINCT * FROM V;
a b c
- - -
1 2 3
3 4 5
Or, you might have partially duplicate rows that you wish to combine:
a b c
- - -
1 2 3
1 2 6
3 4 5
Those first two rows are "the same" in some sense, but clearly different in another sense (in particular, they would not be combined by SELECT DISTINCT). You have to decide how to combine them. You could discard column c as unimportant:
SELECT DISTINCT a,b FROM V;
a b
- -
1 2
3 4
Or you could perform some kind of aggregation on them. You could add them up:
SELECT a,b, SUM(c) "tot" FROM V GROUP BY a,b;
a b tot
- - ---
1 2 9
3 4 5
You could add pick the smallest value:
SELECT a,b, MIN(c) "first" FROM V GROUP BY a,b;
a b first
- - -----
1 2 3
3 4 5
Or you could take the mean (AVG), the standard deviation (STD), and any of a bunch of other functions that take a bunch of values for c and combine them into one.
What isn't really an option is just doing nothing. If you just list the ungrouped columns, the DBMS will either throw an error (Oracle does that -- the right choice, imo) or pick one value more or less at random (MySQL). But as Dr. Peart said, "When you choose not to decide, you still have made a choice."
While SELECT DISTINCT may indeed work in your case, it's important to note why what you have is not working.
You're selecting fields that are outside of the GROUP BY. Although MySQL allows this, the exact rows it returns for the non-GROUP BY fields is undefined.
If you wanted to do this with a GROUP BY try something more like the following:
SELECT watch.watch_date,
rec.street_number,
rec.street_name,
rec.city,
rec.state,
rec.country,
usr.username
FROM
(
watchlist watch
LEFT OUTER JOIN est8_records rec ON rec.record_id = watch.record_id
LEFT OUTER JOIN est8_members usr ON rec.user_id = usr.user_id
)
WHERE watch.watch_id IN (
SELECT watch_id FROM watch WHERE user_id = 1
GROUP BY watch.watch_id)
LIMIT 0, 25
I Would never recommend using SELECT DISTINCT, it's really slow on big datasets.
Try using things like EXISTS.
You are grouping by watch.watch_id and you have two results, which have different watch IDs, so naturally they would not be grouped.
Also, from the results displayed they have different records. That looks like a perfectly valid expected results. If you are trying to only select distinct values, then you don't want ot GROUP, but you want to select by distinct values.
SELECT DISTINCT()...
If you say your watchlist table is unique, then one (or both) of the other tables either (a) has duplicates, or (b) is not unique by the key you are using.
To suppress duplicates in your results, either use DISTINCT as #Laykes says, or try
GROUP BY watch.watch_date,
rec.street_number,
rec.street_name,
rec.city,
rec.state,
rec.country,
usr.username
It sort of sounds like you expect all 3 tables to be unique by their keys, though. If that is the case, you are simply masking some other problem with your SQL by trying to retrieve distinct values.