Subquery to display one column in a Table as two separate columns - mysql

I have two tables
Ledgers Table
==============
ledger_id ledger_name open_bal close_bal dr_bal cr_bal
--------- --------- --------- --------- ------ ------
1 Bank A/C 0 5000 5000 0
2 Capital A/C 0 -50000 0 50000
3 Cash A/C 0 30700 53500 22800
Transactions Table
==============
trans_id trans_date ledger_id ledger_name amount trans_type
--------- --------- --------- --------- --------- ---------
1 2004-01-01 3 Cash A/C 50000 Dr
2 2004-01-01 2 Cap A/C 50000 Cr
3 2004-01-02 9 Purchase A/C 10000 Dr
These are my tables, what am trying to achieve is to get the ledgers balances for a particular month. Here am not trying to insert data into a table.
Using the above two tables i just need to query and out put the result as
Desired Output
==============
ledger_id ledger_name amount trans_type As Debit trans_type as Credit
--------- --------- ------ --------- ---------
3 Cash A/C 50000 Dr Null
2 Capital A/C 50000 Null Cr
So trans_type field here is displayed separately DR as Debits and Cr as Credits. This is what I want to achieve.
What i have tried till now is joining the transaction table with the ledgers! But i have failed to get the desired output just by querying these two tables.
This is what i have tried,
SELECT tr.trans_date, tr.amount, tr.ledger_id, l.ledger_name, tr.trans_type
FROM tbl_transaction tr LEFT JOIN tbl_ledgers l
ON l.ledger_id = tr.ledger_id WHERE trans_date BETWEEN '2004-01-01' AND '2004-01-31';
So i basically want to know how can we achieve this? trans_type column split into two "Dr" separate and "Cr" separate?

You may use a CASE to inspect the actual value of trans_type and display it, doing so once for each alias Debit, Credit. This gets unwieldy with more than a few values, but since you have just two it is a simple method.
SELECT
tr.ledger_id,
l.ledger_name,
tr.amount,
CASE WHEN tr.trans_type = 'Dr' THEN tr.trans_type ELSE NULL END AS Debit,
CASE WHEN tr.trans_type = 'Cr' THEN tr.trans_type ELSE NULL END AS Credit
FROM
tbl_transaction tr
LEFT JOIN tbl_ledgers l ON l.ledger_id = tr.ledger_id
WHERE
trans_date BETWEEN '2004-01-01' AND '2004-01-31';
Here is a demonstration: http://sqlfiddle.com/#!9/f5623/2
Note that this uses a LEFT JOIN, resulting in ledger_id = 9 being returned in the demonstration (different than your example). Changing it to an INNER JOIN would correct that. (http://sqlfiddle.com/#!9/f5623/3)

Try this:
SELECT IIf([trans_type]='dr',[trans_type],"") AS debit, IIf([trans_type]='cr',[trans_type],"") AS credit
FROM tr;
This code creates an IF statement, to determine whether the [trans_type] field is 'cr' or 'dr' and returns the value in trans_type ('cr' or 'dr') if the condition is met or returns a blank ("") when the condition is not met.

Related

Refine SQL Query given list of ids

I am trying to improve this query given that it takes a while to run. The difficulty is that the data is coming from one large table and I need to aggregate a few things. First I need to define the ids that I want to get data for. Then I need to aggregate total sales. Then I need to find metrics for some individual sales. This is what the final table should look like:
ID | Product Type | % of Call Sales | % of In Person Sales | Avg Price | Avg Cost | Avg Discount
A | prod 1 | 50 | 25 | 10 | 7 | 1
A | prod 2 | 50 | 75 | 11 | 4 | 2
So % of Call Sales for each product and ID adds up to 100. The column sums to 100, not the row. Likewise for % of In Person Sales. I need to define the IDs separately because I need it to be Region Independent. Someone could make sales in Region A or Region B, but it does not matter. We want aggregate across Regions. By aggregating the subqueries and using a where clause to get the right ids, it should cut down on memory required.
IDs Query
select distinct ids from tableA as t where year>=2021 and team = 'Sales'
This should be a unique list of ids
Aggregate Call Sales and Person Sales
select ids
,sum(case when sale = 'call' then 1 else 0 end) as call_sales
,sum(case when sale = 'person' then 1 else 0 end) as person_sales
from tableA
where
ids in t.ids
group by ids
This will be as follows with the unique ids, but the total sales are from everything in that table, essentially ignoring the where clause from the first query.
ids| call_sales | person_sales
A | 100 | 50
B | 60 | 80
C | 100 | 200
Main Table as shown above
select ids
,prod_type
,cast(sum(case when sale = 'call' then 1 else 0 end)/CAST(call_sales AS DECIMAL(10, 2)) * 100 as DECIMAL(10,2)) as call_sales_percentage
,cast(sum(case when sale = 'person' then 1 else 0 end)/CAST(person_sales AS DECIMAL(10, 2)) * 100 as DECIMAL(10,2)) as person_sales_percentage
,mean(price) as price
,mean(cost) as cost
,mean(discount) as discount
from tableA as A
where
...conditions...
group by
...conditions...
You can combine the first two queries as:
select ids, sum( sale = 'call') as call_sales,
sum(sale = 'person') as person_sales
from tableA
where
ids in t.ids
group by ids
having sum(year >= 2021 and team = 'Sales') > 0;
I'm not exactly sure what the third is doing, but you can use the above as a CTE and just plug it in.

How to do arithmetic group by quarter mySQL?

I have 2 tables which have not relation both of them.
Table of income
Id Category Nominal Description Date
---- -------- -------- -------- --------
1 ADD 10000 Q1 2020-03-05
2 DD 15000 Q2 2020-05-11
3 PAD 5000 Q3 2020-08-10
Table of outcome
Id Category Nominal Description Date
---- -------- -------- -------- --------
1 ADD 7000 Q1 2020-03-20
2 DD 10000 Q2 2020-06-02
3 PAD 2000 Q3 2020-08-28
So, I want to do subtraction of nominal from income with nominal from outcome group by quarter.
Here is my query :
CREATE view Total AS
SELECT QUARTER(outcome.date) AS Qperiod, income.nominal-outcome.nominal AS remain
FROM income, outcome
GROUP BY YEAR(outcome.date), QUARTER(outcome.date)
this result shown below, it describe that first row in income table subtraction by all outcome nominal.
Qperiod remain
---- --------
1 3000
2 0
3 8000
Can anyone help me to solve this?
You have no JOIN condition in your query, so each row of income gets matched to every row of outcome. Since you have no aggregation function, this effectively means that a random row from outcome is subtracted from each row of income. You should use modern, explicit JOIN syntax and put in the appropriate JOIN condition, which is that the year and quarter are the same in both tables:
CREATE view Total AS
SELECT QUARTER(outcome.date) AS Qperiod, income.nominal-outcome.nominal AS remain
FROM income
JOIN outcome ON YEAR(outcome.date) = YEAR(income.date)
AND QUARTER(outcome.date) = QUARTER(income.date)
GROUP BY YEAR(outcome.date), QUARTER(outcome.date)
Output:
Qperiod remain
1 3000
2 5000
3 3000
Demo on SQLFiddle

How to return NULL values in MySQL query if no results in LEFT JOIN and WHERE clause are found

I have a problem with a MySQL query.
Here are my three tables:
employees:
id initials deleted
------ -------- ---------
1 TV 0
2 AH 0
3 JE 0
4 MA 0
5 MJ 0
6 CE 0
7 KB 1
8 KL 1
Schedule :
id place start end deleted
------ -------------- ------------------- ------------------- ---------
1 Somewhere 2018-05-11 16:48:17 2018-05-15 16:48:26 0
2 Somewhere else 2018-05-12 16:48:50 2018-05-14 16:48:55 1
3 Here 2018-05-13 00:00:00 2018-05-13 00:00:00 0
4 Not here 2018-05-18 16:49:42 2018-05-16 16:49:48 0
And schedule_link :
id id_employee id_schedule
------ ----------- -------------
1 1 1
2 1 2
3 4 3
4 5 4
I would like a request that returns all that is concerned by the date of the day for each employee. Even if the employee does not have any records found, I would like the query to return its initials with NULL in the other columns.
Here is my current query:
SELECT
`employees`.`id`,
`employees`.`initials`,
`schedule`.`place`,
`schedule`.`start`,
`schedule`.`end`,
`schedule`.`deleted`
FROM
`employees`
LEFT JOIN `schedule_link`
ON (
`employees`.`id` = `schedule_link`.`id_employee`
)
LEFT JOIN `schedule`
ON (
`schedule_link`.`id_schedule` = `schedule`.`id`
)
WHERE (
`employees`.`deleted` = '0'
AND `schedule`.`deleted` = '0'
)
AND (
DATE(CURDATE()) BETWEEN CAST(schedule.start AS DATE)
AND CAST(schedule.end AS DATE)
)
This returns me the following data:
id initials place start end deleted
------ -------- --------- ------------------- ------------------- ---------
1 TV Somewhere 2018-05-11 16:48:17 2018-05-15 16:48:26 0
4 MA Here 2018-05-13 00:00:00 2018-05-13 00:00:00 0
It's correct, but what I want is the following result:
id initials place START END deleted
------ -------- --------- ------------------- ------------------- ---------
1 TV Somewhere 2018-05-11 16:48:17 2018-05-15 16:48:26 0
4 MA Here 2018-05-13 00:00:00 2018-05-13 00:00:00 0
2 AH NULL NULL NULL 0
3 JE NULL NULL NULL 0
5 MJ NULL NULL NULL 0
6 CE NULL NULL NULL 0
Is it possible to obtain this result with a single request?
Thank you in advance for your help.
You need to put the conditions on all but the first table in the on clause. Your where clause is turning the outer join into an inner join.
I have some other suggestions:
SELECT e.id, e.initials, s.place, s.start, s.end, s.deleted
FROM employees e LEFT JOIN
schedule_link sl
ON e.id = sl.id_employee LEFT JOIN
schedule s
ON sl.id_schedule = s.id AND s.deleted = 0 AND
CURDATE() BETWEEN CAST(s.start AS DATE) AND CAST(s.end AS DATE)
WHERE e.deleted = 0;
Notes:
Table aliases make the query easier to write and to read.
Backticks just make the query harder to read and write.
Don't use start and end as column names (i.e., rename them if you can). They are keywords (although not reserved), so they have other purposes in a SQL statement.
I am guessing that deleted is numeric. Don't use single quotes for the comparison (unless the column is really a string).
CURDATE() is already a date. No need for conversion.
I don't recommend using BETWEEN with dates, because of the possibility of a lingering time component. However, you are using explicit conversions, so the code unambiguously does what you want (at the risk perhaps of not using an available index).
EDIT:
I see. Because the date condition is in the third table, not the second, you are getting duplicate rows. I think this will fix your problem:
SELECT e.id, e.initials, ss.place, ss.start, ss.end, ss.deleted
FROM employees e LEFT JOIN
(SELECT sl.id_employee, s.*
FROM schedule_link sl JOIN
schedule s
ON sl.id_schedule = s.id AND s.deleted = 0
WHERE CURDATE() BETWEEN CAST(s.start AS DATE) AND CAST(s.end AS DATE)
) ss
ON e.id = ss.id_employee
WHERE e.deleted = 0;
This will include every employee with no match on the time frame exactly once. You will still get every record from schedule if there are multiple matches.
You can actually express this without a subquery:
SELECT e.id, e.initials, s.place, s.start, s.end, s.deleted
FROM employees e LEFT JOIN
(schedule_link sl JOIN
schedule s
ON sl.id_schedule = s.id AND s.deleted = 0 AND
CURDATE() BETWEEN CAST(s.start AS DATE) AND CAST(s.end AS DATE)
)
ON e.id = sl.id_employee
WHERE e.deleted = 0;
I find the subquery version easier to follow.
My general take (without testing the query since I am away posting from my phone atm):
You should use RIGHT JOIN on second join in your query.
You will also need additional WHERE clause, i.e. where employees.id is not null
Don't forget to utilize ifull() for all fields from schedule table, i.e. ifnull(schedule_link.id, schedule_link.id, schedule.myfield)
Please notice that this should be done for all fields you want to show that come out of schedule table in this query
Hope these guidelines will be of any help to you.

mysql - can I select values from the same column and show them in 2 columns result?

suppose I have a table of an account entries moves, Like
ACCOUNTS table
+-------+------+---------+
| title | side | balance |
+-------+------+---------+
| cash | debit| 500.0 |
+-------+------+---------+
| cash |credit| 300.0 |
+-------+------+---------+
| cash |credit| 600.0 |
+-------+------+---------+
#..... more than 10'000 debit and credit rows
I want to group the sum of credit rows and the sum of debit rows and show every sum of them in different column.
what I tried to do is to sum the balance on groups of sides , like
select title, side, sum(balance) from accounts group by side
I get 2 rows , one for debits sum, and another for credit sum, like
+-------+------+---------+
| cash | debit| 500.0 |
+-------+------+---------+
| cash |credit| 900.0 |
+-------+------+---------+
What I want is to get the whole result in ONE result row, the sum of debits in one field and the sum of credits in another field. I want the end result to be something like this
+-------+-------+-------+
| cash | 500.0 | 900.0 |
+-------+-------+-------+
Thanks.
You can use case
select title, sum( case side when 'debit' then balance else 0 end ),
sum( case side when 'credit' then balance else 0 end )
from accounts
group by title
Here's an example using subqueries. A lot more verbose than the CASE statement already provided but if you end up having more than one title or want to do calculations it makes it pretty straightforward.
SELECT
title
,credit.credit_balance
,debit.debit_balance
,(credit.credit_balance - debit.debit_balance) AS net
FROM
(SELECT
title,
sum(balance) debit_balance
FROM accounts
WHERE
side = 'debit'
GROUP BY side) debit
INNER JOIN (
SELECT
title,
sum(balance) debit_balance
FROM accounts
WHERE
side = 'credit'
GROUP BY side) credit ON debit.title = credit.title
GROUP BY
title;

MYSQL: Counting category instances only if the user is unique

I'm a bit lost in this MySQL query problem which involves counting each instance of cust.category grouped by the branch which delivered the customer's order. If a customer has multiple orders in the same month, the cust.category is added according to how many orders they might have made. Is it possible to limit the counting of cust.category only to unique users?
Sample table
Customer table
id name category nearestbranch created
------ ------ --------- ------------- -------------------
1 John Engineer 3 2014-09-10 18:10:10
2 Mary Developer 4 2014-09-10 18:10:10
-------------------------------------------------------------
Orders table
id delivery_date customer_id
------ ------------- -----------
1 2014-09-01 1
2 2014-09-02 2
3 2014-09-03 1
-----------------------------------
My query:
SELECT cust.nearestbranch,
SUM(IF(cust.category = 'Engineer', 1, 0)) eng,
SUM(IF(cust.category = 'Developer', 1, 0)) dev
FROM customers cust
LEFT JOIN orders ON cust.id = orders.customer_id
WHERE DATE_FORMAT(orders.delivery_date, '%Y-%m') = '2014-09'
GROUP BY cust.nearestbranch
But instead of getting this (eng = 1)
nearestbranch eng dev
------------- ------ ------
3 1 0
4 0 1
I get this (eng = 2)
nearestbranch eng dev
------------- ------ ------
3 2 0
4 0 1
Use count(distinct) with case so for every category you will have count of distinct users
SELECT cust.nearestbranch,
COUNT(distinct case when cust.category = 'Engineer' then customer_id end) eng,
COUNT(distinct case when cust.category = 'Developer'then customer_id end) dev
FROM customers cust
LEFT JOIN orders ON cust.id = orders.customer_id
WHERE DATE_FORMAT(orders.delivery_date, '%Y-%m') = '2014-09'
GROUP BY cust.nearestbranch
Demo