Join a table on itself - sql-server-2008

I have a table ValuationHistory with the following columns
Code | ValuationDate | NetAssetValue | PricePerShare | Subscriptions | Redemptions
ABC | 2014-06-30 | 12546.50 | 100.23 | 60 | 70
CEF | 2014-06-30 | 10025.20 | 120.50 | 30 | 20
ABC | 2014-07-31 | 12505.50 | 101.50 | 40 | 60
ABC | 2014-08-31 | 13051.41 | 102.50 | 35 | 70
Now the user will select a Valuation date and a code from an aspx page. I want to write an sql query that will give me a report with the following columns for all the valuation dates prior to the valuation date selected by the user
Code | BeginningEquity | Subscriptions | Redemptions | EndingEquity
Where
Code is the code selected by the user
BeginningEquity is the NetAssetValue for the previous valuation
Subscriptions is straight forward
Redemptions is straight forward
EndingEquity is the NetAssetValue on the valuationDate
I first created a table variable called #ValDates and got all the valuation dates into this temp table. Then I did a join of #ValDates with the ValuationHistory table.
But I am getting an error on the following query. The error message is:
subquery returned more than one value
Could somebody help me write this query in a better way
SELECT (SELECT NetAssetValue
FROM ValuationHistory
WHERE ValuationDate IN (SELECT Max(ValuationDate)
FROM ValuationHistory
WHERE ValuationDate < nd.ValuationDate)),
Subscriptions,
Redemptions,
EndingEquity
FROM ValuationHistory vh
INNER JOIN #ValDates vd
ON vh.ValuationDate = vd.ValuationDate
WHERE vh.Code = #Code
AND vh.ValuationDate < = #ValuationDate

I'm not very clear about your requirement. Based on the description, you may try to start your query as below
DECLARE #Code VARCHAR(10) = 'ABC'
,#ValuationDate date = '2014-07-31'
;WITH cte AS (
SELECT
rn = ROW_NUMBER() OVER (PARTITION BY Code ORDER BY ValuationDate), *
FROM ValuationHistory
)
SELECT
c1.Code,
c2.NetAssetValue AS BeginningEquity,
c2.ValuationDate AS BeginningDate,
c1.NetAssetValue AS EndingEquity,
c1.ValuationDate AS EndingDate,
c1.[Subscriptions],
c1.[Redemptions]
from cte c1
LEFT JOIN cte c2
ON c1.Code = c2.Code
AND c2.rn = c1.rn - 1
WHERE c1.Code = #Code
AND c1.ValuationDate < = #ValuationDate
SQL Fiddle Demo

Related

How to Select First Date, Previous Date, Latest Date where first date is higher than a reference date

I want to SELECT the Latest Date, the Second Latest Date and the First Date FROM a table1 where the First Date is higher than a reference Date found in another table2. And that reference Date should also be the latest from that table2. I have a solution, supposed to be. But the problem is, the solutions will not return an output if there is ONLY 1 record from table1. Example of the tables:
table1
Reg ID | DateOfAI | byTechnician
2GP001 | 2015-01-13 | 31
2GP001 | 2015-02-18 | 31
2GP001 | 2017-11-10 | 45
2GP001 | 2017-11-30 | 32
2GP044 | 2017-11-30 | 28
2GP001 | 2017-12-23 | 32
table2
Reg ID | DateOfCalving | DryOffDate
2GP001 | 2016-01-14 |
2GP070 | 2016-01-14 |
2GP065 | 2017-04-08 |
2GP001 | 2017-04-12 |
my expected output would be:
Reg ID | LatestDateOfCalving | 1stDateOfAI | PreviousAIDate | LastestAIDate
2GP001 | 2017-04-12 | 2017-11-10 | 2017-11-30 | 2017-12-23
I have searched everywhere from the moon and back... still no luck. these are the queries that i have used
the Fisrt:
SELECT b.actualDam,COUNT(x.actualDam) AS ilanba, max(b.breedDate) AS huli, max(x.breedDate) AS nex,MIN(x.breedDate) AS una,IFNULL(c.calvingDate,NULL) AS nganak,r.*,h.herdID,a.animalID,a.regID, IFNULL(a.dateOfBirth,NULL) AS buho
FROM x_animal_breeding_rec b
LEFT JOIN x_animal_calving_rec c ON b.recID=c.brecID
LEFT JOIN x_herd_animal_rel r ON b.actualDam=r.animal
LEFT JOIN x_herd h ON r.herd=h.herdID
LEFT JOIN x_animal_main_info a ON b.actualDam=a.animalID
JOIN x_animal_breeding_rec x ON b.actualDam = x.actualDam AND x.breedDate < b.breedDate
WHERE h.herdID = ? AND x.mateType = ? AND x.recFlag = ? GROUP BY b.actualDam
and the Second one that I've tried is this code:
SELECT b.recID
, b.actualDam
, b.breedDate
, min(b.breedDate) AS una
, max(b.breedDate) AS huli
, COUNT(b.actualDam) AS sundot
, b.mateType
, b.recFlag
, a.animalID
, a.regID
, h.*
FROM
( SELECT c.recID, c.actualDam
, c.breedDate
, c.mateType
, c.recFlag
, CASE WHEN #prev=c.recID THEN #i:=#i+1 ELSE #i:=1 END i
, #prev:=c.recID prev
FROM x_animal_breeding_rec c
, ( SELECT #prev:=null,#i:=0 ) vars
ORDER BY c.recID,c.breedDate DESC
) b
LEFT JOIN x_animal_main_info a ON b.actualDam=a.animalID
LEFT JOIN x_herd_animal_rel h ON b.actualDam=h.animal
WHERE i <= 2 GROUP BY b.actualDam HAVING h.herd = ? AND b.mateType = ? AND b.recFlag = ? ORDER BY b.breedDate DESC
Another problem here is the first solution returns a WRONG COUNT. the second solution returns a CORRECT COUNT, however, wrong Dates were returned. I hope you could give me an idea. Thanx in Advance.
The following query answers your question:
SELECT
RegID,
LatestDateOfCalving,
MIN(DateOfAI) AS 1stDateOfAI,
REPLACE(SUBSTRING_INDEX(GROUP_CONCAT(DateOfAI ORDER BY DateOfAI DESC), ',', 2), CONCAT(MAX(DateOfAI), ','), '') AS PreviousAIDate,
MAX(DateOfAI) AS LatestAIDate
FROM (
SELECT
t1.RegID,
LatestDateOfCalving,
DateOfAI,
IF(DateOfAI >= LatestDateOfCalving, 1, 0) AS dates
FROM table1 AS t1
INNER JOIN (
SELECT
RegID,
MAX(DateOfCalving) AS LatestDateOfCalving
FROM table2 GROUP BY RegID
) AS tt2 ON t1.RegID = tt2.RegID) AS x
WHERE dates = 1
GROUP BY RegID
HAVING COUNT(dates) >= 3;
Output:
+--------+---------------------+-------------+----------------+--------------+
| RegID | LatestDateOfCalving | 1stDateOfAI | PreviousAIDate | LatestAIDate |
+--------+---------------------+-------------+----------------+--------------+
| 2GP001 | 2017-04-12 | 2017-11-10 | 2017-11-30 | 2017-12-23 |
+--------+---------------------+-------------+----------------+--------------+
DEMO
In a subquery we select RegID and LatestDateOfCalving from table2 in order to have a reference date. Then join it to table1 and flag the record whether DateOfAI is greater or equal to LatestDateOfCalving (IF(DateOfAI >= LatestDateOfCalving, 1, 0)). We use this subquery in the outer query (SELECT RegID, LatestDateOfCalving, MIN(DateOfAI) AS 1stDateOfAI, MAX(DateOfAI) AS LatestAIDate, ...) and select only those records where the DateOfAI are at or after LatestDateOfCalving (WHERE dates = 1, where 1 is the flag where the condition was true) and have at least 3 records (HAVING COUNT(dates) >= 3). In the outer query I use the REPLACE(SUBSTRING_INDEX(GROUP_CONCAT(...))) structure in order to extract the previousAIDate from a comma (,) separated list of dates.

Select only latest record for every employees and for specific employee in MySQL

I have a MySQL DB and in it there's a table with activity logs of employees.
+-------------------------------------------------+
| log_id | employee_id | date_time | action_type |
+-------------------------------------------------+
| 1 | 1 | 2015/02/03 | action1 |
| 2 | 2 | 2015/02/01 | action1 |
| 3 | 2 | 2017/01/02 | action2 |
| 4 | 3 | 2016/02/12 | action1 |
| 5 | 1 | 2016/10/12 | action2 |
+-------------------------------------------------+
And I would need 2 queries. First, to get for every employee his last action. So from this example table I would need to get row 3,4 and 5 with all columns. And second, get the latest action only for specified employee.
Any ideas how to achieve this? I'm using Spring Data JPA, but raw SQL Query would be also great.
Thank you in advance.
Ready for a fred ed...
SELECT x.*
FROM my_table x
JOIN
( SELECT employee_id
, MAX(date_time) date_time
FROM my_table
GROUP
BY employee_id
) y
ON y.employee_id = x.employee_id
AND y.date_time = x.date_time;
For your first query. Simply
SELECT t1.*
FROM tableName t1
WHERE t1.log_id = (SELECT MAX(t2.log_id)
FROM tableName t2
WHERE t2.employee_id = t1.employee_id)
For the second one
SELECT t1.*
FROM tableName t1
WHERE t1.employee_id=X and t1.log_id = (SELECT MAX(t2.log_id)
FROM tableName t2
WHERE t2.employee_id = t1.employee_id);
You can get the expected output by doing a self join
select a.*
from demo a
left join demo b on a.employee_id = b.employee_id
and a.date_time < b.date_time
where b.employee_id is null
Note it may return multiple rows for single employee if there are rows with same date_time you might need a CASE statement and another attribute to decide which row should be picked to handle this kind of situation
Demo

Ordering issue when using SQL variable

I run this query:
SELECT stockcarddetail.id, stockcarddetail.date, stockcarddetail.quantity, stockcarddetail.pricePerItem
FROM Stockcard
LEFT JOIN staff
ON staff.branchId = stockcard.branchId
LEFT JOIN stockcarddetail
ON stockcarddetail.stockcardId = stockcard.id
WHERE staff.username = 'jemmy.h'
AND stockcarddetail.quantity > 0
AND stockcard.productId = '98924a5f-6afb-11e7-8dd4-2c56dcbcb038'
ORDER BY date ASC
and get the result below:
id | date | quantity| pricePerItem
50 | 2017-10-15 | 10.00 | 10000.00
1 | 2017-10-18 | 20.00 | 10000.00
Then, I need to calculate the cumulative of quantity based on the order above, so I run this query:
SELECT a.*, #tot:=#tot + a.quantity FROM
(SELECT #tot:= 0)b
JOIN
(SELECT stockcarddetail.id, stockcarddetail.date, stockcarddetail.quantity, stockcarddetail.pricePerItem
FROM Stockcard
LEFT JOIN staff
ON staff.branchId = stockcard.branchId
LEFT JOIN stockcarddetail
ON stockcarddetail.stockcardId = stockcard.id
WHERE staff.username = 'jemmy.h'
AND stockcarddetail.quantity > 0
AND stockcard.productId = '98924a5f-6afb-11e7-8dd4-2c56dcbcb038'
ORDER BY date ASC) a
Then I got this result:
id | date | quantity| pricePerItem | #tot
1 | 2017-10-18 | 20.00 | 10000.00 | 20
50 | 2017-10-15 | 10.00 | 10000.00 | 30
However, the result that I want is like this:
id | date | quantity| pricePerItem | #tot
50 | 2017-10-15 | 10.00 | 10000.00 | 10
1 | 2017-10-18 | 20.00 | 10000.00 | 30
How can I get the expected result?
EDIT
Simplified version of the problem can be found here: http://sqlfiddle.com/#!9/f6ad91/3
From what I understand from you, you want the cumulative total for each entry.
I suggest ditching the variable and relying on a subquery instead:
SELECT
scd.id,
scd.date,
scd.quantity,
scd.pricePerItem,
(SELECT SUM(scd1.quantity) FROM StockcardDetail AS scd1 WHERE scd1.stockcardId = scd.stockcardId AND scd1.date <= scd.date) AS total
FROM Stockcard
LEFT JOIN staff ON staff.branchId = stockcard.branchId
LEFT JOIN stockcarddetail AS scd ON scd.stockcardId = stockcard.id
WHERE staff.username = 'jemmy.h'
AND scd.quantity > 0
AND stockcard.productId = '98924a5f-6afb-11e7-8dd4-2c56dcbcb038'
ORDER BY scd.date ASC
The idea behind this is to make it select the sum of all entries prior (including the current one) for each entry.
As per my understanding, you should get the expected output from your query. But, you aren't getting your expected output, then other possible solution is (WITHOUT JOIN)
SET #tot:= 0;
SELECT
stockcarddetail.id,
stockcarddetail.date,
stockcarddetail.quantity,
stockcarddetail.pricePerItem,
#tot:=#tot + stockcarddetail.quantity as Total
FROM Stockcard
LEFT JOIN staff ON staff.branchId = stockcard.branchId
LEFT JOIN stockcarddetail ON stockcarddetail.stockcardId = stockcard.id
WHERE staff.username = 'jemmy.h' AND stockcarddetail.quantity > 0 AND stockcard.productId = '98924a5f-6afb-11e7-8dd4-2c56dcbcb038'
ORDER BY date ASC

MySQL count daily new users VS returned users (cohort analysis)

The table structure is: user_id, Date (I'm used to work with timestamp)
for example
user id | Date (TS)
A | '2014-08-10 14:02:53'
A | '2014-08-12 14:03:25'
A | '2014-08-13 14:04:47'
B | '2014-08-13 04:04:47'
...
and for the next week I have
user id | Date (TS)
A | '2014-08-17 09:02:53'
B | '2014-08-17 10:04:47'
B | '2014-08-18 10:04:47'
A | '2014-08-19 10:04:22'
C | '2014-08-19 11:04:47'
...
and for today I have
user id | Date (TS)
A | '2015-05-27 09:02:53'
B | '2015-05-27 10:04:47'
C | '2015-05-27 10:04:22'
D | '2015-05-27 17:04:47'
I need to know how to perform a single query to find the number of users which are a "returned" user from the very beginning of their activity.
Expected results :
date | New user | returned User
2014-08-10 | 1 | 0
2014-08-11 | 0 | 0
2014-08-12 | 0 | 1 (A was active on 08/11)
2014-08-13 | 1 | 1 (A was active on 08/12 & 08/11)
...
2014-08-17 | 0 | 2 (A & B were already active )
2014-08-18 | 0 | 1
2014-08-19 | 1 | 1
...
2015-05-27 | 1 | 3 (D is a new user)
After some long search on Stackoverflow I found some material provided by https://meta.stackoverflow.com/users/107744/spencer7593 here : Weekly Active Users for each day from log but I didn't succeed to change his query to output my expected results.
Thanks for your help
Assuming you have a date table somewhere (and using t-sql syntax because I know it better...) the key is to calculate the mindate for each user separately, calculate the total number of users on that day, and then just declaring a returning user to be a user who wasn't new:
SELECT DateTable.Date, NewUsers, NumUsers - NewUsers AS ReturningUsers
FROM
DateTable
LEFT JOIN
(
SELECT MinDate, COUNT(user_id) AS NewUsers
FROM (
SELECT user_id, min(CAST(date AS Date)) as MinDate
FROM Table
GROUP BY user_id
) A
GROUP BY MinDate
) B ON DateTable.Date = B.MinDate
LEFT JOIN
(
SELECT CAST(date AS Date) AS Date, COUNT(DISTINCT user_id) AS NumUsers
FROM Table
GROUP CAST(date AS Date)
) C ON DateTable.Date = C.Date
Thanks to Stephen, I made a short fix on his query, which works well even it's a bit time consuming on large database :
SELECT
DATE(Stats.Created),
NewUsers,
NumUsers - NewUsers AS ReturningUsers
FROM
Stats
LEFT JOIN
(
SELECT
MinDate,
COUNT(user_id) AS NewUsers
FROM (
SELECT
user_id,
MIN(DATE(Created)) as MinDate
FROM Stats
GROUP BY user_id
) A
GROUP BY MinDate
) B
ON DATE(Stats.Created) = B.MinDate
LEFT JOIN
(
SELECT
DATE(Created) AS Date,
COUNT(DISTINCT user_id) AS NumUsers
FROM Stats
GROUP BY DATE(Created)
) C
ON DATE(Stats.Created) = C.Date
GROUP BY DATE(Stats.Created)

nested query & transaction

Update #1: query gives me syntax error on Left Join line (running the query within the left join independently works perfectly though)
SELECT b1.company_id, ((sum(b1.credit)-sum(b1.debit)) as 'Balance'
FROM MyTable b1
JOIN CustomerInfoTable c on c.id = b1.company_id
#Filter for Clients of particular brand, package and active status
where c.brand_id = 2 and c.status = 2 and c.package_id = 3
LEFT JOIN
(
SELECT b2.company_id, sum(b2.debit) as 'Current_Usage'
FROM MyTable b2
WHERE year(b2.timestamp) = '2012' and month(b2.timestamp) = '06'
GROUP BY b2.company_id
)
b3 on b3.company_id = b1.company_id
group by b1.company_id;
Original Post:
I keep track of debits and credits in the same table. The table has the following schema:
| company_id | timestamp | credit | debit |
| 10 | MAY-25 | 100 | 000 |
| 11 | MAY-25 | 000 | 054 |
| 10 | MAY-28 | 000 | 040 |
| 12 | JUN-01 | 100 | 000 |
| 10 | JUN-25 | 150 | 000 |
| 10 | JUN-25 | 000 | 025 |
As my result, I want to to see:
| Grouped by: company_id | Balance* | Current_Usage (in June) |
| 10 | 185 | 25 |
| 12 | 100 | 0 |
| 11 | -54 | 0 |
Balance: Calculated by (sum(credit) - sum(debits))* - timestamp does not matter
Current_Usage: Calculated by sum(debits) - but only for debits in JUN.
The problem: If I filter by JUN timestamp right away, it does not calculate the balance of all time but only the balance of any transactions in June.
How can I calculate the current usage by month but the balance on all transactions in the table. I have everything working, except that it filters only the JUN results into the current usage calculation in my code:
SELECT b.company_id, ((sum(b.credit)-sum(b.debit))/1024/1024/1024/1024) as 'BW_remaining', sum(b.debit/1024/1024/1024/1024/28*30) as 'Usage_per_month'
FROM mytable b
#How to filter this only for the current_usage calculation?
WHERE month(a.timestamp) = 'JUN' and a.credit = 0
#Group by company in order to sum all entries for balance
group by b.company_id
order by b.balance desc;
what you will need here is a join with sub query which will filter based on month.
SELECT T1.company_id,
((sum(T1.credit)-sum(T1.debit))/1024/1024/1024/1024) as 'BW_remaining',
MAX(T3.DEBIT_PER_MONTH)
FROM MYTABLE T1
LEFT JOIN
(
SELECT T2.company_id, SUM(T2.debit) T3.DEBIT_PER_MONTH
FROM MYTABLE T2
WHERE month(T2.timestamp) = 'JUN'
GROUP BY T2.company_id
)
T3 ON T1.company_id-T3.company_id
GROUP BY T1.company_id
I havn't tested the query. The point here i am trying to make is how you can join your existing query to get usage per month.
alright, thanks to #Kshitij I got it working. In case somebody else is running into the same issue, this is how I solved it:
SELECT b1.company_id, ((sum(b1.credit)-sum(b1.debit)) as 'Balance',
(
SELECT sum(b2.debit)
FROM MYTABLE b2
WHERE b2.company_id = b1.company_id and year(b2.timestamp) = '2012' and month(b2.timestamp) = '06'
GROUP BY b2.company_id
) AS 'Usage_June'
FROM MYTABLE b1
#Group by company in order to add sum of all zones the company is using
group by b1.company_id
order by Usage_June desc;