Multiple sum on different cols in same query - mysql

Is it possible to sum on cols if inner join is true and sum on another cols if another inner join true? i.e.
SELECT t1.debit
, t2.credit
, t1.ID
, t2.ID
FROM
( SELECT SUM(booking_value) debit
, gl_acct.id_fin_gl_acct ID
FROM bookings
JOIN gl_acct
ON (CONCAT('1',gl_acct.id_fin_gl_acct) = bookings.id_debit_account)
) t1
JOIN
( SELECT SUM(booking_value) credit
, gl_acct.id_fin_gl_acct ID
FROM bookings
JOIN gl_acct
ON (CONCAT('1',gl_acct.id_fin_gl_acct)=bookings.id_credit_account)
) t2
ON (t1.ID = t2.ID)
GROUP
BY t1.ID
Please explain your answer.

An example of conditional aggregation.
SELECT t.id_account
, SUM(IF(t.cr_or_db='cr',t.tot_booking_value,0)) AS `tot_credit`
, SUM(IF(t.cr_or_db='db',t.tot_booking_value,0)) AS `tot_debit`
FROM (
SELECT 'cr' AS `cr_or_db`
, c.id_credit_account AS `id_account`
, SUM(c.booking_value) AS `tot_booking_value`
FROM bookings c
GROUP BY c.id_credit_account
UNION ALL
SELECT 'db' AS `cr_or_db`
, d.id_debit_account AS `id_account`
, SUM(d.booking_value) AS `tot_booking_value`
FROM bookings d
GROUP BY d.id_debit_account
) t
GROUP
BY t.id_account
The inline view t gets us total credits for each id_credit_account and total debits for each id_debit_account.
The original query joins both of those id_ columns to the same account table, so we're assuming those are both foreign keys to that other table, which means they are the same datatype...
A join operation would only be required if there is some reason we need to include the gl_acct table. Assuming that id_fin_gl_acct is UNIQUE in gl_acct... we could add the join operation before the GROUP BY clause.
...
) t
JOIN gl_acct g
ON CONCAT('1',g.id_fin_gl_acct) = t.id_account
GROUP
BY t.id_account

Related

every derived table must have it is own alias join mysql [duplicate]

This question already has answers here:
What is the error "Every derived table must have its own alias" in MySQL?
(4 answers)
Closed 3 months ago.
on mysql I see this error but can fix it. Anyone can help please?
select * from
(
(select a.* from sessions as a)
join
(
select b.customer_id, min(b.timestamp),
b.marketing_source as first_touch_source,
b.marketing_medium as first_touch_medium
from sessions as b
group by b.customer_id
) on a.customer_id = b=customer_id
) as T
I believe your query should read
select *
from (
select a.*
from sessions as a
join
(select b.customer_id, min(b.timestamp), b.marketing_source as first_touch_source, b.marketing_medium as first_touch_medium
from sessions as b
group by b.customer_id
) c USING (customer_id) # or c.customer_id = a.customer_id
) as T
If you are just trying to bring in the minimum date column you could try two approaches, second one will work if your version supports windows function
subqueries are always a mess so I would suggest cte's if supported
with main as (
select
b.customer_id,
b.marketing_source as first_touch_source,
b.marketing_medium as first_touch_medium,
min(b.timestamp) as min_time,
from sessions as b
group by b.customer_id
)
select
s.*,
main.min_time
from sessions as s
inner join main
on s.customer_id = main.customer_id
select
*,
min(timestamp)
over(partition by customer_id,marketing_medium, marketing_source) as min_date_per_cust_med_source
from sessions
Some DBMS require you to name all derived tables. Your query (I removed the unnessesary derived table T):
select *
from (select a.* from sessions as a)
join (select b.customer_id, min(b.timestamp)
, b.marketing_source as first_touch_source
, b.marketing_medium as first_touch_medium
from sessions as b
group by b.customer_id
)
on a.customer_id = b=customer_id
can be changed to:
select *
from (select a.* from sessions as a) AS c
join (select b.customer_id, min(b.timestamp)
, b.marketing_source as first_touch_source
, b.marketing_medium as first_touch_medium
from sessions as b
group by b.customer_id
) AS d
on c.customer_id = d.customer_id
To avoid confusion, you should choose another alias at the outer level, despite that the inner alias is not visible there.
Side note: The derived table d may or may not be valid SQL. It is not allowed in SQL92, but it is allowed in SQL99 if marketing_* is functionally dependent of customer_id.
You can further simplify it as:
select *
from sessions AS c
join (select b.customer_id, min(b.timestamp) as ts
, b.marketing_source as first_touch_source
, b.marketing_medium as first_touch_medium
from sessions as b
group by b.customer_id
) AS d
on c.customer_id = d.customer_id
I assume you meant to also join with c.timestamp = d.ts. If that is the case and you are on a recent version of MySQL (8+) you can use a window function instead of a self join
select customer_id, ...
from (
select b.customer_id
, b.marketing_source
, b.marketing_medium
, row_number() over (partition by customer_id
order by b.timestamp) as rn
from sessions as b
) as T
where rn = 1

Issue with using 2 INNER JOINS within 1 mySQL Query

I have the following query that is doing an INNER JOIN within a select query to insert records into the table. The issue is instead of using the WHERE clause and listing the 3 t2.device I would like to see, I need to do another INNER JOIN with a table called tbl_Full_List.
Both tbl_Full_List and tbl1_device have a column called device so instead of the WHERE clause I would like it to show only devices that match in both tbl_Full_List and tbl1_device
INSERT INTO TEMP_Table1 (place, device, description, quantity)
(SELECT t1.place, t2.device, t2.description, Sum(t2.quantity) AS quantity
FROM Device_Table AS t1
INNER JOIN tbl1_device AS t2
ON t1.rma = t2.rma
WHERE t2.device IN ('PRT1030', 'PRT-23','PRT-20139')
AND t1.date_made = Current_Date()
GROUP BY t1.place, t2.device, t2.description ORDER BY place ASC, device ASC )
Without knowing how the two relate... I'm assuming FL.ID is the PK of full list and we have a Foreign Key to tbl1_devcice called FL_ID
INSERT INTO TEMP_Table1 (place, device, description, quantity)
(SELECT t1.place, t2.device, t2.description, Sum(t2.quantity) AS quantity
FROM Device_Table AS t1
INNER JOIN tbl1_device AS t2
ON t1.rma = t2.rma
INNER JOIN tbl_Full_List FL
ON FL.Device = t2.Device
WHERE t1.date_made = Current_Date()
GROUP BY t1.place, t2.device, t2.description ORDER BY place ASC, device ASC )

MySQL Query for Subtraction of 2 fields with decimal(13,2)

How do you subtract (2)two different fields with the same data type of decimal(13,2) ?
I have table for users
attribtues:
id, name, password, email, deleted, created_at, updated
and
table for top_up_history
attributes:
id, userId, paymentId, paymentDesc, amount, deleted, created_at, updated_at
and
table for transaction_details
id, userId, merchId, transId, amount, refCode, procId, deleted, created_at, updated_at
and
I have view for user_balance
attributes:
userId, total_topup, total_balance
Here's my current query:
SELECT a.id as userId, SUM(b.amount) as total_topup,
SUM(b.amount) - SUM(c.amount) as total_balance
FROM `users` AS a LEFT JOIN `top_up_history` AS b
ON a.id = b.userId LEFT JOIN`transaction_details` as c
ON a.id = c.userId GROUP BY a.id
Now the current output of this user_balance is this:
But the problem is
the data from transaction_details is:
the data from top_up_history
the data from users
There's something wrong with my computation.
The output should be:
userId total_topup total_balance
1 NULL NULL
2 NULL NULL
3 15000 14,725
9 10150 9,875
you should manage the null value
SELECT a.id as userId
, SUM(ifnull(b.amount,0)) as total_topup
, SUM(ifnull(b.amount,0)) - SUM(ifnull(c.amount,0)) as total_balance
FROM `users` AS a
LEFT JOIN `top_up_history` AS b ON a.id = b.userId
LEFT JOIN`transaction_details` as c ON a.id = c.userId
GROUP BY a.id
Just take a look at resulting table without GROUP BY function.
LEFT JOIN top_up_history produces two rows for each unique user,
It caused by repeated userId values (9 and 3) in top_up_history table. At this moment (before joining transaction_details) you already have 6 rows. Now sql joins transaction_details and duplicates its amount column value for each matched row.
Then, finally you group, and sum duplicated values. Substruction itself works ok.
The easiest way to deal with that kind of problem is to do subquery like so:
SELECT a.id as userId, SUM(b.amount) as total_topup,
SUM(b.amount) -
(select sum(tr.amount) from transaction_details_ as tr where tr.users_id = a.id) as total_balance
FROM users_ AS a
LEFT JOIN top_up_history_ AS b ON a.id = b.users_id
LEFT JOIN transaction_details_ as c ON a.id = c.users_id
GROUP BY a.id

Getting max record on varchar field

I have this query
SELECT
s.account_number,
a.id AS 'ASPIRION ID',
a.patient_first_name,
a.patient_last_name,
s.admission_date,
s.total_charge,
astat.name AS 'STATUS',
astat.definition,
latest_note.content AS 'LAST NOTE',
a.insurance_company
FROM
accounts a
INNER JOIN
services s ON a.id = s.account_id
INNER JOIN
facilities f ON f.id = a.facility_id
INNER JOIN
account_statuses astat ON astat.id = a.account_status_id
INNER JOIN
(SELECT
account_id, MAX(content) content, MAX(created)
FROM
notes
GROUP BY account_id) latest_note ON latest_note.account_id = a.id
WHERE
a.facility_id = 56
My problem comes from
(SELECT
account_id, MAX(content) content, MAX(created)
FROM
notes
GROUP BY account_id)
Content is a varchar field and I am needed to get the most recent record. I now understand that MAX will not work on a varchar field the way that I want it. I am not sure how to be able to get the corresponding content with the MAX id and group that by account id on in this join.
What would be the best way to do this?
My notes table looks like this...
id account_id content created
1 1 This is a test 2011-03-16 02:06:40
2 1 More test 2012-03-16 02:06:40
Here are two choices. If your content is not very long and don't have funky characters, you can use the substring_index()/group_concat() trick:
(SELECT account_id,
SUBSTRING_INDEX(GROUP_CONCAT(content ORDER BY created desc SEPARATOR '|'
), 1, '|') as content
FROM notes
GROUP BY account_id
) latest_note
ON latest_note.account_id = a.id
Given the names of the columns and tables, that is likely not to work. Then you need an additional join or a correlated subquery in the from clause. I think that might be easiest in this case:
select . . .,
(select n.content
from notes n
where n.account_id = a.id
order by created desc
limit 1
) as latest_note
from . . .
The advantage to this method is that it only gets the notes for the rows you need. And, you don't need a left join to keep all the rows. For performance, you want an index on notes(account_id, created).
SELECT
s.account_number,
a.id AS 'ASPIRION ID',
a.patient_first_name,
a.patient_last_name,
s.admission_date,
s.total_charge,
astat.name AS 'STATUS',
astat.definition,
latest_note.content AS 'LAST NOTE',
a.insurance_company
FROM
accounts a
INNER JOIN services s ON a.id = s.account_id
INNER JOIN facilities f ON f.id = a.facility_id
INNER JOIN account_statuses astat ON astat.id = a.account_status_id
INNER JOIN
(SELECT account_id, MAX(created) mxcreated
FROM notes GROUP BY account_id) latest_note ON latest_note.account_id = a.id and
latest_note.mxcreated = --datetime column from any of the other tables being used
WHERE a.facility_id = 56
You have to join on the max(created) which would give the latest content.
Or you can change the query to
SELECT account_id, content, MAX(created) mxcreated
FROM notes GROUP BY account_id
as mysql allows you even if you don't include all non-aggregated columns in group by clause. However, unless you join on the max date you wouldn't get the correct results.
The last created record is the one for which does not exist a newer one. Hence:
SELECT
s.account_number,
a.id AS "ASPIRION ID",
a.patient_first_name,
a.patient_last_name,
s.admission_date,
s.total_charge,
astat.name AS "STATUS",
astat.definition,
latest_note.content AS "LAST NOTE",
a.insurance_company
FROM accounts a
INNER JOIN services s ON a.id = s.account_id
INNER JOIN facilities f ON f.id = a.facility_id
INNER JOIN account_statuses astat ON astat.id = a.account_status_id
INNER JOIN
(
SELECT account_id, content
FROM notes
WHERE NOT EXISTS
(
SELECT *
FROM notes newer
WHERE newer.account_id = notes.account_id
AND newer.created > notes.created
)
) latest_note ON latest_note.account_id = a.id
WHERE a.facility_id = 56;

MySQL Query not displaying correctly

I am having to set up a query that retrieves the last comment made on a customer, if no one has commented on them for more than 4 weeks. I can make it work using the query below, but for some reason the comment column won't display the latest record. Instead it displays the oldest, however the date shows the newest. It may just be because I'm a noob at SQL, but what exactly am I doing wrong here?
SELECT DISTINCT
customerid, id, customername, user, MAX(date) AS 'maxdate', comment
FROM comments
WHERE customerid IN
(SELECT DISTINCT id FROM customers WHERE pastdue='1' AND hubarea='1')
AND customerid NOT IN
(SELECT DISTINCT customerid FROM comments WHERE DATEDIFF(NOW(), date) <= 27)
GROUP BY customerid
ORDER BY maxdate
The first "WHERE" clause is just ensuring that it shows only customers from a specific area, and that they are "past due enabled". The second makes sure that the customer has not been commented on within the last 27 days. It's grouped by customerid, because that is the number that is associated with each individual customer. When I get the results, everything is right except for the comment column...any ideas?
Join much better to nested query so you use the join instead of nested query
Join increase your speed
this query resolve your problem.
SELECT DISTINCT
customerid,id, customername, user, MAX(date) AS 'maxdate', comment
FROM comments inner join customers on comments.customerid = customers.id
WHERE comments.pastdue='1' AND comments.hubarea='1' AND DATEDIFF(NOW(), comments.date) <= 27
GROUP BY customerid
ORDER BY maxdate
I think this might probably do what you are trying to achieve. If you can execute it and maybe report back if it does or not, i can probably tweak it if needed. Logically, it ' should' work - IF i have understood ur problem correctly :)
SELECT X.customerid, X.maxdate, co.id, c.customername, co.user, co.comment
FROM
(SELECT customerid, MAX(date) AS 'maxdate'
FROM comments cm
INNER JOIN customers cu ON cu.id = cm.customerid
WHERE cu.pastdue='1'
AND cu.hubarea='1'
AND DATEDIFF(NOW(), cm.date) <= 27)
GROUP BY customerid) X
INNER JOIN comments co ON X.customerid = co.customerid and X.maxdate = co.date
INNER JOIN customer c ON X.customerid = c.id
ORDER BY X.maxdate
You need to have subquery for each case.
SELECT a.*
FROM comments a
INNER JOIN
(
SELECT customerID, max(`date`) maxDate
FROM comments
GROUP BY customerID
) b ON a.customerID = b.customerID AND
a.`date` = b.maxDate
INNER JOIN
(
SELECT DISTINCT ID
FROM customers
WHERE pastdue = 1 AND hubarea = 1
) c ON c.ID = a.customerID
LEFT JOIN
(
SELECT DISTINCT customerid
FROM comments
WHERE DATEDIFF(NOW(), date) <= 27
) d ON a.customerID = d.customerID
WHERE d.customerID IS NULL
The first join gets the latest record for each customer.
The second join shows only customers from a specific area, and that they are "past due enabled".
The third join, which uses LEFT JOIN, select all customers that has not been commented on within the last 27 days. In this case,only records without on the list are selected because of the condition d.customerID IS NULL.
But tomake your query shorter, if the customers table has already unique records for customer, then you don't need to have subquery on it.Directly join the table and put the condition on the WHERE clause.
SELECT a.*
FROM comments a
INNER JOIN
(
SELECT customerID, max(`date`) maxDate
FROM comments
GROUP BY customerID
) b ON a.customerID = b.customerID AND
a.`date` = b.maxDate
INNER JOIN customers c
ON c.ID = a.customerID
LEFT JOIN
(
SELECT DISTINCT customerid
FROM comments
WHERE DATEDIFF(NOW(), date) <= 27
) d ON a.customerID = d.customerID
WHERE d.customerID IS NULL AND
c.pastdue = 1 AND
c.hubarea = 1
Two of your table columns are not contained in either an aggregate function or the GROUP BY clause. for example suppose that you have two data rows with the same customer id and same date, but with different comment data. how SQL should aggregate these two rows? :( it will generate an error...
try this
select customerid, id, customername, user,date, comment from(
select customerid, id, customername, user,date, comment,
#rank := IF(#current_customer = id, #rank+ 1, 1),
#current_customer := id
from comments
where customerid IN
(SELECT DISTINCT id FROM customers WHERE pastdue='1' AND hubarea='1')
AND customerid NOT IN
(SELECT DISTINCT customerid FROM comments WHERE DATEDIFF(NOW(), date) <= 27)
order by customerid, maxdate desc
) where rank <= 1