Difference over sums in SQL - mysql

I have a table like the following:
PARENTREF TRANSTYPE(BIT(1)) DUEDATE(DateTime) TOTAL
2038 0 2015-01-01 1000
2038 1 2015-03-05 500
2039 0 2015-01-01 1000
2040 0 2015-01-01 1000
2041 0 2015-01-01 1000
2040 1 2015-04-07 200
I want a SELECT query that returns SUM(TOTAL) when TRANSTYPE=1 subtracted from SUM(TOTAL) when TRANSTYPE=0 for each distinct PARENTREF. I also would like to get in a separate column the DUEDATE for the PARENTREF when TRANSTYPE=0. There may be only one PARENTREF with TRANSTYPE=0 so that won't be a problem. In other words, I should get the following table:
PARENTREF DUEDATE(DateTime) TOTAL
2038 2015-01-01 500
2039 2015-01-01 1000
2040 2015-01-01 800
2041 2015-01-01 1000

(1-transtype*2) is 1 when transtype=0 and is -1 when transtype=1, so query subtract values of total where transtype=1 from value of total where transtype=0. max ignore null values, so it select only not null value where transtype=0.
select
parentref,
sum((1-transtype*2)*total) as total,
max(if(transtype=0,duedate,null)) as duedate
from tablename
group by parentref

Try this....
select t.PARENTREF,t.DueDate,(t.Total-isnull(m.Total,0)) as total
from tabl t LEFT outer join tabl m on t.PARENTREF=m.PARENTREF and t.TRANSTYPE <> m.TRANSTYPE
where (t.Transtype=0 ) and (isnull(m.Transtype,1)=1 )
Please Check out this fiddle http://sqlfiddle.com/#!3/d4988/1
or use thiss...
select t.PARENTREF,t.DueDate,(sum(t.Total)-sum(isnull(m.Total,0))) as total
from tabl t LEFT outer join tabl m on t.PARENTREF=m.PARENTREF and t.TRANSTYPE <> m.TRANSTYPE
where (t.Transtype=0 ) and (isnull(m.Transtype,1)=1 )
group by t.PARENTREF,t.DueDate
Check this fiddle http://sqlfiddle.com/#!3/d4988/2

Related

how to display months as columns along with min bal in MySQL

For example
ID TID DATE BALANCE
1 24 02-11-2018 198
2 2 08-11-2018 199
2 3 05-11-2018 0
4 13 26-11-2018 115
4 14 28-11-2018 113
Balance for Nov-18 should be displayed as below
ID BALANCE
1 198
2 0
3 0
4 113
for id-3, 0 should be displayed since there is no balance for these id in the month of November for id -1,2 & 4 min balance should be displayed.
Most of what you want can be obtained by a simple GROUP BY and some date formatting:
select a.id,
date_format(date, '%Y-%m') as month,
coalesce(min(balance), 0) as min_balance
from accounts a
left outer join balances b
on b.id = a.id
group by a.id, date_format(date, '%Y-%m')
order by a.id, date_format(date, '%Y-%m')
Here we're using the date_format function to extract the year and month from the date value, and then grouping the results by id and the formatted date. This produces the result
id month min_balance
1 2018-11 198
2 2018-11 0
3 0
4 2018-11 113
It seems apparent that there must be another table which contains all the account values, so I added a table named accounts with these values. Change this if needed.
db<>fiddle here

Selecting from 2 tables with possibly corresponding dates

I am looking for the correct query for my mysql db that has 2 seperate tables for lengths and weights.
I want to have the result returned as 1 query with 3 columns: datetime, length and weight.
The query should also allow to specify the user.
Eg.:
Table heights:
id user_id created_on height
1 2 2019-01-01 00:00:01 180
2 2 2019-01-02 00:00:01 181
3 3 2019-01-03 00:00:01 182
4 3 2019-01-04 00:00:01 183
5 2 2019-01-07 00:00:01 184
Table weights:
id user_id created_on weight
1 2 2019-01-01 00:00:01 80
2 2 2019-01-04 00:00:01 81
3 3 2019-01-05 00:00:01 82
4 3 2019-01-06 00:00:01 83
5 2 2019-01-07 00:00:01 84
I am looking to get the following result with a single query:
user_id created_on weight height
2 2019-01-01 00:00:01 80 180
2 2019-01-02 00:00:01 null 181
2 2019-01-04 00:00:01 81 null
2 2019-01-07 00:00:01 84 184
I have tried working with JOIN statements but fail to get the required result.
This join statement
SELECT w.* , h.* FROM weight w
JOIN height h
ON w.created_on=h.created_on
AND w.user_id=h.user_id AND user_id=2
will return only those results that have both a height and weight item for user_id and created_on
A full outer join would do the trick, however this is not supported by mysql.
The following query seems to be returning the required result, however it is very slow:
SELECT r.* FROM
(SELECT w.user_id as w_user, w.created_on as weightdate, w.value as weight, h.created_on as heightdate ,h.user_id as h_user, h.value as height FROM weight w
LEFT JOIN height h ON w.user_id = h.user_id
AND w.created_on=h.created_on
UNION
SELECT w.user_id as w_user, w.created_on as weightdate, w.value as weight, h.created_on as heightdate ,h.user_id as h_user, h.value as height FROM weight w
RIGHT JOIN height h ON w.user_id = h.user_id
AND w.created_on=h.created_on ) r
WHERE h_user=2 OR w_user =2
The query takes more than 3 seconds if the 2 tables have around 3000 entries.
Is there a way to speed this up, possibly using a different approach?
For extra bonus points: is it possible to allow for a small time discrepancy between both created_on datetimes? (eg. 10 minutes or within the same hour). Eg. if column weight has an entry for 2019-01-01 00:00:00 and table height has an entry for height at 2019-01-01 00:04:00 they appear in the same row.
Instead of using a calendar table to select dates of interest, you can use a UNION to select all the distinct dates from the heights and weights tables. To deal with matching times within an hour of each other, you can compare the times using TIMESTAMPDIFF and truncate the created_on time to the hour. Since this might create duplicate entries, we add the DISTINCT qualifier to the query:
SELECT DISTINCT COALESCE(h.user_id, w.user_id) AS user_id,
DATE_FORMAT(COALESCE(h.created_on, w.created_on), '%y-%m-%d %H:00:00') AS created_on,
w.weight,
h.height
FROM (SELECT created_on FROM heights
UNION
SELECT created_on FROM weights) d
LEFT JOIN heights h ON ABS(TIMESTAMPDIFF(HOUR, h.created_on, d.created_on)) = 0 AND h.user_id = 2
LEFT JOIN weights w ON ABS(TIMESTAMPDIFF(HOUR, w.created_on, d.created_on)) = 0 AND w.user_id = 2
WHERE h.user_id IS NOT NULL OR w.user_id IS NOT NULL
ORDER BY created_on
Output (from my demo, where I've modified your times to allow for matching within the hour):
user_id created_on weight height
2 19-01-01 01:00:00 80 180
2 19-01-02 00:00:00 181
2 19-01-04 04:00:00 81
2 19-01-07 06:00:00 84 184
Demo on dbfiddle
This is probably best handled using a calendar table, containing all dates of interest for the query. We can start the query with the calendar table, then left join to the heights and weights tables:
SELECT
COALESCE(h.user_id, w.user_id) AS user_id,
d.dt AS created_on,
w.weight,
h.height
FROM
(
SELECT '2019-01-01 00:00:01' AS dt UNION ALL
SELECT '2019-01-02 00:00:01' UNION ALL
SELECT '2019-01-03 00:00:01' UNION ALL
SELECT '2019-01-04 00:00:01' UNION ALL
SELECT '2019-01-05 00:00:01' UNION ALL
SELECT '2019-01-06 00:00:01' UNION ALL
SELECT '2019-01-07 00:00:01'
) d
LEFT JOIN heights h
ON d.dt = h.created_on AND h.user_id = 2
LEFT JOIN weights w
ON d.dt = w.created_on AND w.user_id = 2
WHERE
h.user_id IS NOT NULL OR w.user_id IS NOT NULL
ORDER BY
d.dt;
Demo

HAVING clause and performance

I've got a performance problem with a SQL (MySql) query. Basically I have a table similar to this:
ID PRICE ID OBJECT
-----------------------------
1 500.00 1 1
2 300.00 1 1
3 400.00 1 1
4 100.00 1 1
5 100.00 1 1
6 100.00 2 3
And I need to get the maximum amount of lines given an amount.
For example, given the amount 1000.00 the query must returns these ids (order by price asc) and the total price.
ID PRICE TOTAL_PRICE
---------------------------------
4 100 100.00
5 100 200.00
2 300 500.00
3 400 900.00
Atm I'm using a query similar to the below one:
set #total=0;
select a.id, a.price , #total:=#total + a.price as total_price , a.id_user
from shares a
where a.`id_user` != 0 and a.id_object = 1
having #total < 1000.00 order by a.price asc;
It works fine but it's not efficient. It takes around 1.5 seconds to extract the data (the table has around 1M lines).
The problem is related to the Having clause.
Do you have any suggestions?
Is there a way to perform this type of query without using the clause Having ?
I believe this (cumulative sum) is what you are looking for:
set #total = 0;
SELECT ID,
Price,
Total_Price
FROM (
SELECT a.*
,(#total := #total + price) AS total_price
FROM shares a
WHERE a.id_user != 0
AND a.id_object = 1
ORDER BY a.price
) q1
WHERE q1.total_price < 1000;
SQL Fiddle Demo

Count 2 columns by the same grouping

I'm trying to get two counts of separate columns for data in one table.
I have a database that tracks issues, and one table, Issue, has the 2 relevant columns that each contain a date. Very similar to the following.
DateOpened DateClosed
2015-01-08 2015-01-08
2015-01-08 2015-01-08
2015-01-06 2015-01-08
2015-01-06 2015-01-08
2015-01-04 2015-01-07
2015-01-02 2015-01-07
2015-01-02 2015-01-07
My goal is to be able to count the number of entries opened and closed on each date. An example of the expected output from above would be.
Date CountDateOpened CountDateClosed
2015-01-08 2 4
2015-01-07 0 3
2015-01-06 2 0
2015-01-05 0 0
2015-01-04 1 0
2015-01-03 0 0
2015-01-02 2 0
I know I need to group by Date, but there should be days where more issues are closed than opened, but my COUNT(DateClosed) never seems to exceed my Count(DateOpened). I am doing on the fly date conversions in the query, but I do not believe them to be relevant since I always round to the nearest day. Here is the query I'm running so far, skinned down for simplicity.
SELECT
CREATEDATE AS [Date],
COUNT(CREATEDATE) AS [Number Opened],
COUNT(CLOSEDATE) AS [Number Closed]
FROM
ISSUE
GROUP BY
CREATEDATE
ORDER BY
[Date] DESC
One way of doing this is to use union all to create a single column for both dates and then group according to its type:
SELECT `Date`,
COUNT(`open`) AS `CountDateOpened`
COUNT(`closed`) AS `CountDateClosed`
FROM (SELECT `DateOpened` AS `Date`, 1 AS `open`, NULL AS `closed`
FROM `issue`
UNION ALL
SELECT `DateClosed` AS `Date`, NULL AS `open`, 1 AS `closed`
FROM `issue`
) t
GROUP BY `Date`
ORDER BY `Date` DESC
Try this
select
d.dt,(select COUNT(DateOpened) ct from ISSUE where
CAST(DateOpened as DATE)=CAST(d.dt as DATE) )
,(select COUNT(DateClosed) ct from ISSUE where
CAST(DateClosed as DATE)=CAST(d.dt as DATE) )
from (
select number,DATEADD(D,number-7,GETDATE()) dt
from master.dbo.spt_values sp
where type='P' and DATEADD(D,number-7,GETDATE())<'2015-01-09'
)
d
ORDER BY d.dt desc
OUTPUT
Date DateOpened DateClosed
2015-01-08 2 4
2015-01-07 0 3
2015-01-06 2 0
2015-01-05 0 0
2015-01-04 1 0
2015-01-03 0 0
2015-01-02 2 0
Same as Mureinik's answer, just a little less typing...
SELECT date,SUM(status='opened') opened, SUM(status = 'closed') closed
FROM
( SELECT dateopened date,'opened' status FROM my_table
UNION ALL
SELECT dateclosed,'closed' FROM my_table
) x
GROUP
BY date DESC;

mysql get the sum of unique date

From MySQL table I have the list amount based on the dates. I need to get the sum of amount for each date:
ex:
id type date amount
1 1 2015-01-01 100
2 1 2015-01-01 150
3 1 2015-01-02 10
4 1 2015-01-03 250
Here 2015-01-01 appears more than once.
so i need the result like
date amount
2015-01-01 200
2015-01-02 10
2015-01-03 250
My Query getting between this week start and end
SELECT * from mytable WHERE YEARWEEK(`date`) = YEARWEEK(CURRENT_DATE) AND `type` = 1 ORDER BY `date` ASC
You need a group by clause:
SELECT `date`, SUM(amount)
FROM mytable
GROUP BY `date`
I think the result should be
date amount
2015-01-01 250
2015-01-02 10
2015-01-03 250
you can use this mysql query to get that result:
Select date, sum(amount) as amount
from mytable
GROUP BY date
ORDER BY date asc
dont forget to add "ORDER BY" clause if you want the result in good order
Use GROUP BY
Use AS clause to change the column Name
SELECT `date`, SUM(amount) AS amount
FROM mytable
GROUP BY `date`