I am implementing the multiple separate logics between 2 tables by joining them in a view. I need to have minimal number of views with all logics implemented. I am struck with the following issue while implementing and need your expertise.
I had arrived to the base logic of the view as it serves as the base for most of the logics and I am to stick to it;
SELECT Acct_no, max(txn_date),......
FROM ACCT_CRD ac
INNER JOIN TRNSCTN txn ON ( ac.crd_no = txn.crd_no)
GROUP BY ACCT_NO, TO_CHAR(TXN_DATE,'YYYYMM');
Table_name: ACCT_CRD (This table has account and the credit card numbers with UPI on credit card numbers and a single account number can have multiple card_numbers)
Data:
Acct_no | Crd_no | biz_date | Status
--------+--------+------------+--------
acct1 | crd11 | 2015-10-01 | A
--------+--------+------------+--------
acct1 | crd12 | 2015-10-02 | A
--------+--------+------------+--------
acct1 | crd13 | 2015-10-03 | A
Table_name: TRNSCTN (This table has transactions done through the credit cards; data doesn't reflect any actual meaning) Please note that this table has dates with 5 years, for sample, I have took for only 1 month;
Data:
Crd_no | Txn_date | Txn_code | crd_limit | crd_commit
--------+-------------+----------+-------------+------------
crd11 | 2015-10-02 | 10 | 10000 | 9000
--------+-------------+----------+-------------+------------
crd11 | 2015-10-02 | 10 | 10000 | 14000
--------+-------------+----------+-------------+------------
crd11 | 2015-10-02 | 20 | 10000 | 16000
--------+-------------+----------+-------------+------------
crd11 | 2015-10-03 | 20 | 10000 | 12000
--------+-------------+----------+-------------+------------
crd11 | 2015-10-05 | 20 | 10000 | 15000
--------+-------------+----------+-------------+------------
crd12 | 2015-10-03 | 10 | 20000 | 5000
--------+-------------+----------+-------------+------------
crd12 | 2015-10-03 | 20 | 20000 | 22000
--------+-------------+----------+-------------+------------
crd12 | 2015-10-04 | 30 | 20000 | 25000
--------+-------------+----------+-------------+------------
crd12 | 2015-10-04 | 30 | 20000 | 5000
--------+-------------+----------+-------------+------------
crd13 | 2015-10-04 | 30 | 25000 | 10000
Here, in TRNSCTN table, for each card on each day if CRD_COMMIT > CRD_LIMIT, then take the count as 1 even if there are more records with same card_no and txn_date with crd_commit >= crd_limit or crd_commit < crd_limit;
Order is not important on a given day transactions;
SELECT crd_no, txn_date,
MAX(case when crd_commit > crd_limit then 1 else 0 end) day_overlimit_cnt
FROM TRNSCTN group by crd_no, txn_date;
Essentially, the above data in the table transforms to
Crd_no | txn_date | day_overlimit_cnt
-------+-------------+-------------------
crd11 | 2015-10-02 | 1
-------+-------------+-------------------
crd11 | 2015-10-03 | 1
-------+-------------+-------------------
crd11 | 2015-10-05 | 1
-------+-------------+-------------------
crd12 | 2015-10-03 | 1
-------+-------------+-------------------
crd12 | 2015-10-04 | 1
-------+-------------+-------------------
crd13 | 2015-10-04 | 0
Then, I need to find for each card in a given month, how many times it has exceeded the day_overlimit_cnt;
SELECT crd_no, to_char(txn_date,'yyymm') as txn_mnth,
SUM(day_overlimit_cnt) sum_month_ovrlmt from (select crd_no, txn_date,
MAX(case when crd_commit > crd_limit then 1 else 0 end) day_overlimit_cnt
FROM TRNSCTN group by crd_no, txn_date) dt_check
GROUP BY crd_no, to_char(txn_date,'yyymm');
The data from the above query will be
Crd_no | txn_mnth | sum_month_ovrlmt
-------+----------+-----------------
crd11 | 201510 | 3
-------+----------+-----------------
crd12 | 201510 | 2
-------+----------+-----------------
crd13 | 201510 | 0
And then finally find the max(sum_month_ovrlmt) at account level by joining the above one to ACCT_CRD;
SELECT acct_no, MAX(sum_month_ovrlmt) acct_mnth_ovrlmt
FROM ACCT_CRD ac
JOIN (SELECT crd_no, to_char(txn_date,'yyymm') as txn_mnth,
SUM(day_overlimit_cnt) sum_month_ovrlmt FROM (SELECT crd_no, txn_date, MAX(case when crd_commit > crd_limit then 1 else 0 end) day_overlimit_cnt FROM TRNSCTN group by crd_no, txn_date) dt_check GROUP BY crd_no, to_char(txn_date,'yyymm')) dt_dt_check dt on (ac.cr_no = dt.crd_no) GROUP BY acct_no;
Final output:
Acct_no | acct_mnth_ovrlmt
--------+-----------------
acct1 | 3
How to embed the above logic into the following base query; That is how to derive acct_mnth_ovrlmt without affecting the other columns data in the select part.
SELECT Acct_no, max(txn_date),......
FROM ACCT_CRD ac
INNER JOIN TRNSCTN txn ON ( ac.crd_no = txn.crd_no)
GROUP BY ACCT_NO, TO_CHAR(TXN_DATE,'YYYYMM');
Thanks in advance for your time. As a last resort, I will try to embed the above derived code until aggregation of cards into the base query and will try it out.
Greetings Gordon Linoff,
Thank you for your post. I need the distinct conditional count as you mentioned at card_number level. And as account_number can have more than 1 card_number, I need to find out the max(overlimit_cnt) at account_level;
i.e., say if the distinct conditional count is as
crd11 | 3
crd12 | 2
crd13 | 0
As all these card_numbers belong to acct1, need to get the max(overlimit_cnt) of the above card_numbers; i.e.,
acct | 3
I guess I need to again have another join to the same table with group by on different columns as
SELECT Acct_no, max(txn_date),......,MAX(day_overlimit_cnt)
FROM ACCT_CRD ac
INNER JOIN TRNSCTN txn ON ( ac.crd_no = txn.crd_no)
INNER JOIN ( SELECT CRD_NO, TO_CHAR(TXN_DATE,'YYYYMM') AS TXN_DATE_Y,
COUNT(DISTINCT (CASE WHEN crd_commit > crd_limit then TXN_DATE end)) day_overlimit_cnt from TRNSCTN GROUP BY CRD_NO, TO_CHAR(TXN_DATE,'YYYYMM')) TRNSCTN_OVRLMT ON (TRNSCTN.CRD_NO=TRNSCTN_OVRLMT.CRD_NO AND TO_CHAR(TRNSCTN.TXN_DATE,'YYYYMMDD')=TRNSCTN_OVRLMT.TXN_DATE_Y) GROUP BY ACCT_NO, TO_CHAR(TXN_DATE,'YYYYMM');
Can I avoid new join TRNSCTN_OVRLMT and derive above value.
Your logic is a little hard to follow, but I think you just want conditional count distinct:
SELECT Acct_no, max(txn_date),
COUNT(DISTINCT (CASE WHEN crd_commit > crd_limit THEN txn_date END)) as DaysOverLimit
FROM ACCT_CRD ac INNER JOIN
TRNSCTN txn
ON ac.crd_no = txn.crd_no
GROUP BY ACCT_NO, TO_CHAR(TXN_DATE,'YYYYMM');
Related
Accounts (Master List of Accounts with link to their parent (same table) )
(Accounts table is maintained using nested_set plugin, so the left, right, depth are available in the table and are maintained while adding/editing accounts)
| id | name | parent_id |
|----|----------------|-----------|
| 1 | Assets | null |
| 2 | Current Assets | 1 |
| 3 | Fixed Assets | 1 |
| 4 | Bank | 2 |
| 5 | Bank One | 4 |
| 6 | Bank Two | 4 |
| 7 | Revenue | null |
| 8 | Sales | 7 |
| 9 | Expenses | null |
| 10 | Rent | 9 |
Entries (where the date and description of each transaction stored)
| entry_id | date | description |
|----------|------------|--------------------|
| 1 | Mar 3 2020 | Cash Sales |
| 2 | Mar 3 2020 | Cash Paid For Rent |
| 3 | Apr 1 2020 | Owner Withdrawal |
Amounts (where the double entry transactions are stored)
| entry_id | account_id | type | amount |
|----------|------------|--------|--------|
| 1 | 5 | debit | 10000 |
| 1 | 8 | credit | 10000 |
| 2 | 10 | debit | 1000 |
| 2 | 5 | credit | 1000 |
| | | | |
Given the above structure, here is my requirements
Arrange the accounts in Tree(hierarchical) structure and calculate the individual account balances (balances can be debit_balance or credit_balance)
Hierarchical account balances, rolling up child balances to the parent accounts
PS:
I do have the solution for the req 1 above using a combination of
WITH RECURSIVE sql function on the accounts table and arranging the rows hierarchially and then joining the result set with amounts table that is summed up on amount column (after grouping on type) for each accounts.
I am keen to see how the folks over here will solve this. (lemme know if you would like to see what I got so far though)
here is the query that gets me the first result set. (i've omitted the details like normal_credit_blance flag etc for brevity in the original question)
select id, parent_id, name, newdepth as depth, debit_amount, credit_amount, type,
CASE WHEN normal_credit_balance = true THEN credit_amount - debit_amount END as credit_balance,
CASE WHEN normal_credit_balance = false THEN debit_amount - credit_amount END as debit_balance
from
(
WITH RECURSIVE children AS (
SELECT id, parent_id, display_name, lft, rgt, type, normal_credit_balance, 0 as newdepth
FROM accounts
WHERE parent_id is null
UNION
SELECT op.id, op.parent_id, op.display_name, op.lft, op.rgt, op.type, op.normal_credit_balance, newdepth + 1
FROM accounts op
JOIN children c ON op.parent_id = c.id
)
SELECT *
FROM children
) accounts_tbl
left join
( SELECT account_id,
SUM( CASE WHEN am.type = 'debit' THEN COALESCE( AMOUNT , 0.0 ) ELSE 0.0 END ) AS debit_amount ,
SUM( CASE WHEN am.type = 'credit' THEN COALESCE( AMOUNT , 0.0 ) ELSE 0.0 END ) AS credit_amount
FROM amounts am
join accounts ac on ac.id = am.account_id
group by account_id, ac.name, ac.type )
as amount_tbl
on accounts_tbl.id = amount_tbl.account_id order by lft
sample result based on the amounts table entries, the rollup should look like this:
| id | name | balance |
|----|----------------|-----------|
| 1 | Assets | 9000 |
| 2 | Current Assets | 9000 |
| 3 | Fixed Assets | 0 |
| 4 | Bank | 9000 |
| 5 | Bank One | 9000 |
| 6 | Bank Two | 0 |
| 7 | Revenue | 10000 |
| 8 | Sales | 10000 |
| 9 | Expenses | 1000 |
| 10 | Rent | 1000 |
I would start by computing the "direct" balance of each account, with a left join and aggregation. Then goes the recursive query: you just need to traverse the tree from the leafs to the root, conslidating the balance as you go. The final step is aggregation.
with recursive
data (id, name, parent_id, balance) as (
select
ac.*,
coalesce(sum(case am.type when 'debit' then - amount when 'credit' then amount end), 0) balance
from accounts ac
left join amounts am on am.account_id = ac.id
group by ac.id
),
cte (id, name, parent_id, balance) as (
select d.* from data d
union all
select d.id, d.name, d.parent_id, d.balance + c.balance
from cte c
inner join data d on d.id = c.parent_id
)
select id, name, sum(balance) from cte group by id, name
I don't get how all the accounts in your resultset end up with a positive balance, while some have more debits than credit (and vice-versa). The query treats debits as negative amounts and credits as positive.
Demo on DB Fiddle:
id | name | sum
-: | :------------- | ----:
1 | Assets | -9000
2 | Current Assets | -9000
3 | Fixed Assets | 0
4 | Bank | -9000
5 | Bank One | -9000
6 | Bank Two | 0
7 | Revenue | 10000
8 | Sales | 10000
9 | Expenses | -1000
10 | Rent | -1000
If you were using a closure table instead of nested sets (like I do in https://stackoverflow.com/a/38701519/5962802) then you could use simple JOINs like
SELECT
accounts.id,
accounts.title,
SUM(COALESCE(debits.amount,0)) AS debit,
SUM(COALESCE(credit.amount,0)) AS credit
FROM account_tree
LEFT JOIN accounts ON ancestor_id = accounts.id
LEFT JOIN balances AS debits ON account_id = child_id AND type = 'debit'
LEFT JOIN balances AS credits ON account_id = child_id AND type = 'credit'
GROUP BY accounts.id,accounts.title
As a side note I would recommend you to keep debits and credits on the same row - as 2 columns in table BALANCES.
I need to create a list of the 2 rating hotels in the UK that have increased their rating by at least 3 points from the beginning.
Month | Hotel | Rating | Region |
---------------------------------------
01-Jan-19 | A | 1 | US |
01-Feb-19 | B | 2 | UK |
01-Mar-19 | C | 3 | EU |
01-Apr-19 | A | 1 | US |
01-May-19 | B | 4 | UK |
01-Jun-19 | C | 3 | EU |
01-Jul-19 | A | 1 | US |
01-Aug-19 | B | 5 | UK |
01-Sep-19 | C | 4 | EU |
Like this, the query must produce Hotel B only.
It sounds like you want the first and last entries. One method uses conditional aggregation. I am going to assume that month is really a date or number and not a string:
select t.hotel
from (select t.*,
row_number() over (partition by hotel order by month asc) as seqnum_asc,
row_number() over (partition by hotel order by month desc) as seqnum_desc
from t
) t
group by t.hotel
having max(rating) filter (where seqnum_asc = 1) >= max(rating) filter (where seqnum_desc = 1) + 3;
This also works
I have tried it
Select "Hotel"
From T
Where "Region" = 'UK'
Group by "Hotel"
Having
Min ("Rating") = 2
And
Max ("Rating") >= 5
The Link to test:
https://www.db-fiddle.com/f/6TVgrC5WRjqdyPwdGSvWGN/8
I am still very new to SQL and Tableau however I am trying to work myself towards achieving a personal project of mine.
Table A; shows a table which contains the defect quantity per product category and when it was raised
+--------+-------------+--------------+-----------------+
| Issue# | Date_Raised | Category_ID# | Defect_Quantity |
+--------+-------------+--------------+-----------------+
| PCR12 | 11-Jan-2019 | Product#1 | 14 |
| PCR13 | 12-Jan-2019 | Product#1 | 54 |
| PCR14 | 5-Feb-2019 | Product#1 | 5 |
| PCR15 | 5-Feb-2019 | Product#2 | 7 |
| PCR16 | 20-Mar-2019 | Product#1 | 76 |
| PCR17 | 22-Mar-2019 | Product#2 | 5 |
| PCR18 | 25-Mar-2019 | Product#1 | 89 |
+--------+-------------+--------------+-----------------+
Table B; shows the consumption quantity of each product by month
+-------------+--------------+-------------------+
| Date_Raised | Category_ID# | Consumed_Quantity |
+-------------+--------------+-------------------+
| 5-Jan-2019 | Product#1 | 100 |
| 17-Jan-2019 | Product#1 | 200 |
| 5-Feb-2019 | Product#1 | 100 |
| 8-Feb-2019 | Product#2 | 50 |
| 10-Mar-2019 | Product#1 | 100 |
| 12-Mar-2019 | Product#2 | 50 |
+-------------+--------------+-------------------+
END RESULT
I would like to create a table/bar chart in tableau that shows that Defect_Quantity/Consumed_Quantity per month, per Category_ID#, so something like this below;
+----------+-----------+-----------+
| Month | Product#1 | Product#2 |
+----------+-----------+-----------+
| Jan-2019 | 23% | |
| Feb-2019 | 5% | 14% |
| Mar-2019 | 89% | 10% |
+----------+-----------+-----------+
WHAT I HAVE TRIED SO FAR
Unfortunately i have not really done anything, i am struggling to understand how do i get rid of the duplicates upon joining the tables based on Category_ID#.
Appreciate all the help I can receive here.
I can think of doing left joins on both product1 and 2.
select to_char(to_date(Date_Raised,'d-mon-yyyy'),'mon-yyyy')
, (p2.product1 - sum(case when category_id='Product#1' then Defect_Quantity else 0 end))/p2.product1 * 100
, (p2.product2 - sum(case when category_id='Product#2' then Defect_Quantity else 0 end))/p2.product2 * 100
from tableA t1
left join
(select to_char(to_date(Date_Raised,'d-mon-yyyy'),'mon-yyyy') Date_Raised
, sum(Comsumed_Quantity) as product1 tableB
where category_id = 'Product#1'
group by to_char(to_date(Date_Raised,'d-mon-yyyy'),'mon-yyyy')) p1
on p1.Date_Raised = t1.Date_Raised
left join
(select to_char(to_date(Date_Raised,'d-mon-yyyy'),'mon-yyyy') Date_Raised
, sum(Comsumed_Quantity) as product2 tableB
where category_id = 'Product#2'
group by to_char(to_date(Date_Raised,'d-mon-yyyy'),'mon-yyyy')) p2
on p2.Date_Raised = t1.Date_Raised
group by to_char(to_date(Date_Raised,'d-mon-yyyy'),'mon-yyyy')
By using ROW_NUMBER() OVER (PARTITION BY ORDER BY ) as RN, you can remove duplicate rows. As of your end result you should extract month from date and use pivot to achieve.
I would do this as:
select to_char(date_raised, 'YYYY-MM'),
(sum(case when product = 'Product#1' then defect_quantity end) /
sum(case when product = 'Product#1' then consumed_quantity end)
) as product1,
(sum(case when product = 'Product#2' then defect_quantity end) /
sum(case when product = 'Product#2' then consumed_quantity end)
) as product2
from ((select date_raised, product, defect_quantity, 0 as consumed_quantity
from a
) union all
(select date_raised, product, 0 as defect_quantity, consumed_quantity
from b
)
) ab
group by to_char(date_raised, 'YYYY-MM')
order by min(date_raised);
(I changed the date format because I much prefer YYYY-MM, but that is irrelevant to the logic.)
Why do I prefer this method? This will include all months where there is a row in either table. I don't have to worry that some months are inadvertently filtered out, because there are missing production or defects in one month.
I'm trying to write a query which gives me the number of patient visits by age, gender and condition(Diabetes, Hypertension etc). Get the visit count for patients having diabetes and group by gender and patients who fall between the age range of 45-54. I used Inner Join to get only the rows which are present in both tables. I get the error:
age.Age is invalid in the select list because it is not contained in
either an aggregate function or the GROUP BY clause.
Do you think I should use partition by age.age?
TABLE_A
+------------+------------+------------+
| Member_Key | VisitCount | date |
+------------+------------+------------+
| 4000 | 1 | 2014-05-07 |
| 4000 | 1 | 2014-05-09 |
| 4001 | 2 | 2014-05-08 |
+------------+------------+------------+
TABLE_B
+------------+--------------+
| Member_Key | Condition |
+------------+--------------+
| 4000 | Diabetes |
| 4000 | Diabetes |
| 4001 | Hypertension |
+------------+--------------+
TABLE_C
+------------+---------------+------------+
| Member_Key | Member_Gender | Member_DOB |
+------------+---------------+------------+
| 4000 | M | 1970-05-21 |
| 4001 | F | 1968-02-19 |
+------------+---------------+------------+
Query
SELECT c.conditions,
age.gender,
CASE
WHEN age.age BETWEEN 45 AND 54
THEN SUM(act.visitcount)
END AS age_45_54_years
FROM table_a act
INNER JOIN
(
SELECT DISTINCT
member_key,
conditions
FROM table_b
) c ON c.member_key = act.member_key
INNER JOIN
(
SELECT DISTINCT
member_key,
member_gender,
DATEPART(year, '2017-10-16')-DATEPART(year, member_dob) AS Age
FROM [table_c]
) AS age ON age.member_key = c.member_key
GROUP BY c.conditions,
age.member_gender;
Expected Output
+--------------+--------+-------------+
| Condition | Gender | TotalVisits |
+--------------+--------+-------------+
| Diabetes | M | 2 |
| Hypertension | F | 2 |
+--------------+--------+-------------+
You can simplify your query filtering the age on the WHERE condition
And as Sean Lange said, use DATEDADD and GETDATE() to calculate the age more accurately.
SQL DEMO
SELECT [Condition],
[Member_Gender] as [Gender],
SUM([VisitCount]) as [VisitCount]
FROM TableA A
JOIN (SELECT DISTINCT [Member_Key], [Condition]
FROM TableB) B
ON A.[Member_Key] = B.[Member_Key]
JOIN TableC C
ON A.[Member_Key] = C.[Member_Key]
WHERE [Member_DOB] BETWEEN DATEADD(year, -50 , GETDATE())
AND DATEADD(year, -45 , GETDATE())
GROUP BY [Condition], [Member_Gender]
EDIT
Have to change the WHERE condition to solve the age precision and allow index use.
I need to retrieve the records from dbo.transaction (transaction of all users-more than one transaction for each user) that having timestamp which is closest to the time in dbo.bal (current balance details of each user-only one record for each user)
ie, the resultant records should equal to the no of records in the dbo.bal
Here i tried the below query, am getting only the records less than the time in dbo.bal. But there are some record having timestamp greater than and closest to dbo.bal.time
SELECT dbo.bal.uid,
dbo.bal.userId,
dbo.bal.balance,
dbo.bal.time,
(SELECT TOP 1 transactionBal
FROM dbo.transaction
WHERE TIMESTAMP <= dbo.bal.time
ORDER BY TIMESTAMP DESC) AS newBal
FROM dbo.bal
WHERE dbo.bal.time IS NOT NULL
ORDER BY dbo.bal.time DESC
here is my table structure,
dbo.transaction
---------------
| uid| userId | description| timestamp | credit | transactionBal
-------------------------------------------------------------------------
| 1 | 101 | buy credit1| 2012-01-25 03:23:31.624 | 100 | 500
| 2 | 102 | buy credit5| 2012-01-18 03:13:12.657 | 500 | 700
| 3 | 103 | buy credit3| 2012-01-15 02:16:34.667 | 300 | 300
| 4 | 101 | buy credit2| 2012-01-13 05:34:45.637 | 200 | 300
| 5 | 101 | buy credit1| 2012-01-12 07:45:21.457 | 100 | 100
| 6 | 102 | buy credit2| 2012-01-01 08:18:34.677 | 200 | 200
dbo.bal
-------
| uid| userId | balance | time |
-----------------------------------------------------
| 1 | 101 | 500 | 2012-01-13 05:34:45.645 |
| 2 | 102 | 700 | 2012-01-01 08:18:34.685 |
| 3 | 103 | 300 | 2012-01-15 02:16:34.672 |
And the result should be like,
| Id | userId | balance | time | credit | transactionBal
-----------------------------------------------------------------------------
| 1 | 101 | 500 | 2012-01-13 05:34:45.645 | 200 | 300
| 2 | 102 | 700 | 2012-01-01 08:18:34.685 | 200 | 200
| 3 | 103 | 300 | 2012-01-15 02:16:34.672 | 300 | 300
Please help me.. Any help is must appreciated...Thankyou
It would be helpful if you posted your table structures, but ...
I think your inner query needs a join condition. (That is not actually in your question)
Your ORDER BY clause in the inner query could be ABS(TIMESTAMP - DB0.BAL.TIME). That should give you the smallest difference between the 2.
Does that help ?
Based on the follwing Sql Fiddle http://sqlfiddle.com/#!3/7a900/15 I came up with ...
SELECT
bal.uid,
bal.userId,
bal.balance,
bal.time,
trn.timestamp,
trn.description,
datediff(ms, bal.time, trn.timestamp)
FROM
money_balances bal
JOIN money_transaction trn on
trn.userid = bal.userid and
trn.uid =
(
select top 1 uid
from money_transaction trn2
where trn2.userid = trn.userid
order by abs(datediff(ms, bal.time, trn2.timestamp))
)
WHERE
bal.time IS NOT NULL
ORDER BY
bal.time DESC
I cannot vouch for its performance because I know nothing of your data, but I believe it works.
I have simplified my answer - I believe what you need is
SELECT
bal.uid as baluid,
(
select top 1 uid
from money_transaction trn2
where trn2.userid = bal.userid
order by abs(datediff(ms, bal.time, trn2.timestamp))
) as tranuid
FROM
money_balances bal
and from that you can derive all the datasets you need.
for example :
with matched_credits as
(
SELECT
bal.uid as baluid,
(
select top 1 uid
from money_transaction trn2
where trn2.userid = bal.userid
order by abs(datediff(ms, bal.time, trn2.timestamp))
) as tranuid
FROM
money_balances bal
)
select
*
from
matched_credits mc
join money_balances mb on
mb.uid = mc.baluid
join money_transaction trn on
trn.uid = mc.tranuid
Try:
SELECT dbo.bal.uid,
dbo.bal.userId,
dbo.bal.balance,
dbo.bal.time,
(SELECT TOP 1 transactionBal
FROM dbo.transaction
ORDER BY abs(datediff(ms, dbo.bal.time, TIMESTAMP))) AS newBal
FROM dbo.bal
WHERE dbo.bal.time IS NOT NULL
ORDER BY dbo.bal.time DESC