Missing records from one table in SQL Server 2008R2 - sql-server-2008

Table 1:
Date PlacementID CampaignID Impressions
04/01/2014 100 10 1000
04/01/2014 101 10 1500
04/01/2014 100 11 500
Table 2:
Date PlacementID CampaignID Cost
04/01/2014 100 10 5000
04/01/2014 101 10 6000
04/01/2014 100 11 7000
04/01/2014 103 10 8000
When I have joined this table using Full Join and Left Join statement, I am not able to get uncommon record which is last row in table2 that display PlacementID 103 and campaignID 10 and Cost 8000. However I have searched all raw data and file but this missing records are not common between two sources. However, I want to include this records in final table. How can I do that? This two table are two different source and I have got results only common records.
Moreover, when I found out that missing value is exact value that are required in final figure so want to include every thing. I am including my SQL script below:
SELECT A.palcementid,
A.campaignid,
A.date,
Sum(A.impressions) AS Impressions,
Sum(CASE
WHEN C.placement_count > 1 THEN ( B.cost / C.placement_count )
ELSE B.cost
END) AS Cost
FROM table1 A
FULL JOIN table2 B
ON A.placementid = B.placementid
AND A.campaignid = B.campaignid
AND A.date = B.date
LEFT JOIN (SELECT Count(A.placementid) AS Placement_Count,
placementid. campaignid,
date
FROM table1
GROUP BY placementid,
campaignid,
date) c
ON A.placementid = C.placementid
AND A.campaignid = C.campaignid
AND A.date = C.date
GROUP BY A.placementid,
A.campaignid,
A.date
I am dividing Cost by placement because in source the cost was allocated for one placement only and one time so I have to divide those because in actual table the same Placementid repeat more than 1 times on same date.

As you didn't provide any expected output I guessing here but if the result you want is this:
PlacementID CampaignID Date Impressions Cost
----------- ----------- ----------------------- ----------- -----------
100 10 2014-04-01 02:00:00.000 1000 5000
100 11 2014-04-01 02:00:00.000 500 7000
101 10 2014-04-01 02:00:00.000 1500 6000
103 10 2014-04-01 02:00:00.000 NULL 8000
Then the following query should do it:
SELECT COALESCE(A.PlacementID,b.placementid) AS PlacementID,
COALESCE(A.campaignid, b.campaignid) AS CampaignID,
COALESCE(A.date, b.date) AS [Date],
SUM(A.impressions) AS Impressions,
SUM(CASE
WHEN C.placement_count > 1 THEN ( B.cost / C.placement_count )
ELSE B.cost
END ) AS Cost
FROM table1 A
FULL JOIN table2 B
ON A.[PlacementID] = B.placementid
AND A.campaignid = B.campaignid
AND A.date = B.date
LEFT JOIN (SELECT COUNT(PlacementID) AS Placement_Count,
placementid, campaignid,
date
FROM table1
GROUP BY placementid,
campaignid,
date) c
ON A.[PlacementID] = C.placementid
AND A.campaignid = C.campaignid
AND A.date = C.date
GROUP BY COALESCE(A.PlacementID, B.PlacementID),
COALESCE(A.campaignid, b.campaignid),
COALESCE(A.date, b.date)
Sample SQL Fiddle

Related

Creating an overdraft statement

I'm currently stuck on how to create a statement that shows daily overdraft statements for a particular council.
I have the following, councils, users, markets, market_transactions, user_deposits.
market_transaction run daily reducing user's account balance. When the account_balance is 0 the users go into overdraft (negative). When users make a deposit their account balance increases.
I Have put the following tables to show how transactions and deposits are stored.
if I reverse today's transactions I'm able to get what account balance a user had yesterday but to formulate a query to get the daily OD amount is where the problem is.
USERS
user_id
name
account_bal
1
Wells
-5
2
James
100
3
Joy
10
4
Mumbi
-300
DEPOSITS
id
user_id
amount
date
1
1
5
2021-04-26
2
3
10
2021-04-26
3
3
5
2021-04-25
4
4
5
2021-04-25
TRANSACTIONS
id
user_id
amount_tendered
date
1
1
5
2021-04-27
2
2
10
2021-04-26
3
3
15
2021-04-26
4
4
50
2021-04-25
The Relationships are as follows,
COUNCILS
council_id
name
1
a
2
b
3
c
MARKETS
market_id
name
council_id
1
x
3
2
y
1
3
z
2
MARTKET_USER_LINK
id
market_id
user_id
1
1
3
2
2
2
3
3
1
I'm running this SQL query to get the total amount users have spent and subtracting with the current user account balance.
Don't know If I can use this to figure out the account_balance for each day.
SELECT u.user_id, total_spent, total_deposits,m.council_id
FROM users u
JOIN market_user_link ul ON ul.user_id= u.user_id
LEFT JOIN markets m ON ul.market_id =m.market_id
LEFT JOIN councils c ON m.council_id =c.council_id
LEFT JOIN (
SELECT user_id, SUM(amount_tendered) AS total_spent
FROM transactions
WHERE DATE(date) BETWEEN DATE('2021-02-01') AND DATE(NOW())
GROUP BY user_id
) t ON t.user_id= u.user_id
ORDER BY user_id, total_spent ASC
// looks like this when run
| user_id | total_spent | council_id |
|-------------|----------------|------------|
| 1 | 50.00 | 1 |
| 2 | 2.00 | 3 |
I was hoping to reverse transactions and deposits done to get the account balance for a day then get the sum of users with an account balance < 0... But this has just failed to work.
The goal is to produce a query that shows daily overdraft (Only SUM the total account balance of users with account balance below 0 ) for a particular council.
Expected Result
date
council_id
o_d_amount
2021-04-24
1
-300.00
2021-04-24
2
-60.00
2021-04-24
3
-900.00
2021-04-25
1
-600.00
2021-04-25
2
-100.00
2021-04-25
3
-1200.00
This is actually not that hard, but the way you asked makes it hard to follow.
Also, your expected result should match the data you provided.
Edited: Previous solution was wrong - It counted withdraws and deposits more than once if you have more than one event for each user/date.
Start by having the total exchanged on each day, like
select user_id, date, sum(amount) exchanged_on_day from (
select user_id, date, amount amount from deposits
union all select user_id, date, -amount_tendered amount from transactions
) d
group by user_id, date
order by user_id, date;
What follows gets the state of the account only on days that had any deposits or withdraws.
To get the results of all days (and not just those with account movement) you just have to change the cross join part to get a table with all dates you want (like Get all dates between two dates in SQL Server) but I digress...
select dates.date, c.council_id, u.name username
, u.account_bal - sum(case when e.date >= dates.date then e.exchanged_on_day else 0 end) as amount_on_start_of_day
, u.account_bal - sum(case when e.date > dates.date then e.exchanged_on_day else 0 end) as amount_on_end_of_day
from councils c
inner join markets m on c.council_id=m.council_id
inner join market_user_link mul on m.market_id=mul.market_id
inner join users u on mul.user_id=u.user_id
left join (
select user_id, date, sum(amount) exchanged_on_day from (
select user_id, date, amount amount from deposits
union all select user_id, date, -amount_tendered amount from transactions
) d group by user_id, date
) e on u.user_id=e.user_id --exchange on each Day
cross join (select distinct date from (select date from deposits union select date from transactions) datesInternal) dates --all days that had a transaction
group by dates.date, c.council_id, u.name, u.account_bal
order by dates.date desc, c.council_id, u.name;
From there you can rearrange to get the result you want.
select date, council_id
, sum(case when amount_on_start_of_day<0 then amount_on_start_of_day else 0 end) o_d_amount_start
, sum(case when amount_on_end_of_day<0 then amount_on_end_of_day else 0 end) o_d_amount_end
from (
select dates.date, c.council_id, u.name username
, u.account_bal - sum(case when e.date >= dates.date then e.exchanged_on_day else 0 end) as amount_on_start_of_day
, u.account_bal - sum(case when e.date > dates.date then e.exchanged_on_day else 0 end) as amount_on_end_of_day
from councils c
inner join markets m on c.council_id=m.council_id
inner join market_user_link mul on m.market_id=mul.market_id
inner join users u on mul.user_id=u.user_id
left join (
select user_id, date, sum(amount) exchanged_on_day from (
select user_id, date, amount amount from deposits
union all select user_id, date, -amount_tendered amount from transactions
) d group by user_id, date
) e on u.user_id=e.user_id --exchange on each Day
cross join (select distinct date from (select date from deposits union select date from transactions) datesInternal) dates --all days that had a transaction
group by dates.date, c.council_id, u.name, u.account_bal
) result
group by date, council_id
order by date;
You can check it on https://www.db-fiddle.com/f/msScT6B5F7FjU2aQXVr2da/6
Basically the query maps users to councils, caculates periods of overdrafts for users, them aggregates over councils. I assume that starting balance is dated start of the month '2021-04-01' (it could be ending balance as well, see below), change it as needed. Also that negative starting balance counts as an overdraft. For simplicity and debugging the query is divided into a number of steps.
with uc as (
select distinct m.council_id, mul.user_id
from markets m
join market_user_link mul on m.market_id = mul.market_id
),
user_running_total as (
select user_id, date,
coalesce(lead(date) over(partition by user_id order by date) - interval 1 day, date) nxt,
sum(sum(s)) over(partition by user_id order by date) rt
from (
select user_id, date, -amount_tendered s
from transactions
union all
select user_id, date, amount
from deposits
union all
select user_id, se.d, se.s
from users
cross join lateral (
select date(NOW() + interval 1 day) d, 0 s
union all
select '2021-04-01' d, account_bal
) se
) t
group by user_id, date
),
user_overdraft as (
select user_id, date, nxt, least(rt, 0) ovd
from user_running_total
where date <= date(NOW())
),
dates as (
select date
from user_overdraft
union
select nxt
from user_overdraft
),
council__overdraft as (
select uc.council_id, d.date, sum(uo.ovd) total_overdraft, lag(sum(uo.ovd), 1, sum(uo.ovd) - 1) over(partition by uc.council_id order by d.date) prev_ovd
from uc
cross join dates d
join user_overdraft uo on uc.user_id = uo.user_id and d.date between uo.date and uo.nxt
group by uc.council_id, d.date
)
select council_id, date, total_overdraft
from council__overdraft
where total_overdraft <> prev_ovd
order by date, council_id
Really council__overdraft is quite usable, the last step just compacts output excluding intermidiate dates when overdraft is not changed.
With following sample data:
users
user_id name account_bal
1 Wells -5
2 James 100
3 Joy 10
4 Mumbi -300
deposits, odered by date, extra row added for the last date
id user_id amount date
3 3 5 2021-04-25
4 4 5 2021-04-25
1 1 5 2021-04-26
2 3 10 2021-04-26
5 3 73 2021-05-06
transactions, odered by date (note the added row, to illustrate running total in action)
id user_id amount_tendered date
5 4 50 2021-04-25
2 2 10 2021-04-26
3 3 15 2021-04-26
1 1 5 2021-04-27
4 3 17 2021-04-27
councils
council_id name
1 a
2 b
3 c
markets
market_id name council_id
1 x 3
2 y 1
3 z 2
market_user_link
id market_id user_id
1 1 3
2 2 2
3 3 1
4 3 4
the query ouput is
council_id
date
overdraft
1
2021-04-01
0
2
2021-04-01
-305
3
2021-04-01
0
2
2021-04-25
-350
2
2021-04-26
-345
2
2021-04-27
-350
3
2021-04-27
-7
3
2021-05-06
0
Alternatively, provided the users table is holding a closing (NOW()) balance, replace user_running_total CTE with the following code
user_running_total as (
select user_id, date,
coalesce(lead(date) over(partition by user_id order by date) - interval 1 day, date) nxt,
coalesce(sum(sum(s)) over(partition by user_id order by date desc
rows between unbounded preceding and 1 preceding), sum(s)) rt
from (
select user_id, date, amount_tendered s
from transactions
union all
select user_id, date, -amount
from deposits
union all
select user_id, se.d, se.s
from users
cross join lateral (
select date(NOW() + interval 1 day) d, account_bal s
union all
select '2021-04-01' d, 0
) se
) t
where DATE(date) between date '2021-04-01' and date(NOW() + interval 1 day)
group by user_id, date
),
This way the query starts with closing balance dated next date after now and rollouts a running total in the reverse order till '2021-04-01' as a starting date.
Output
council_id
date
overdraft
1
2021-04-01
0
2
2021-04-01
-260
3
2021-04-01
-46
2
2021-04-25
-305
3
2021-04-25
-41
2
2021-04-26
-300
3
2021-04-26
-46
2
2021-04-27
-305
3
2021-04-27
-63
3
2021-05-06
0
db-fiddle both versions

MySQL How to get the row with max date when joining multiple tables?

My goal is the get a list of current prices and prices at the time of whatever date is given. The price as of today is always product.price. Each time a new price is set, an entry is added to product_audit and revinfo.
If we are looking for what the prices were on 2020-11-31, it would return:
num CurrentPrice OldPrice
--------------------------------------
1001 100 175
1030 110 100
2010 150 130
EDIT FOR CLARIFICATION: My intention is to get what the price was on a specific day. So OldPrice is actually the newest entry in Product_aud/revinfo that is before or on the set date (in this case, 2020-11-31). Looking specifically at code 1001, the price was changed on 2020-08-02, 2020-09-26, and 2020-01-08. If we are looking at 2020-11-31, that means it should grab 2020-09-26 because it is the soonest date before then. This means the price of 1001 on 2020-11-31 was 175.
There are three tables: Product, product_audit, revinfo
Everytime the price is changed, an entry is added to product_audit with the new price and a reference to a new entry in revinfo that has the date/time. Revinfo contains entries for other audit tables mixed in.
product.id = product_audit.id
product_audit.rev = revinfo.id
product
id num price
------------------------
1 1001 100
2 1030 110
3 2010 150
product_audit
id rev price
------------------------
1 1 200
1 3 175
1 6 100
2 2 100
2 7 110
3 4 130
3 5 120
3 8 150
revinfo
id timestamp
-------------------
1 2020-08-02
2 2020-09-25
3 2020-09-26
4 2020-11-12
5 2020-12-20
6 2021-01-08
7 2021-01-09
8 2021-01-23
Of course this just returns the oldest price from product_audit:
SELECT product.num, product.price AS CurrentPrice, product_audit.price AS OldPrice
FROM product
LEFT JOIN product_audit ON product_audit.id = product.id
LEFT JOIN revinfo ON revinfo.id = product_audit.rev
WHERE rev.timestamp <= "2020-11-31"
GROUP BY product.id
I tried nesting joins like this based on some stuff I was reading, but quickly realized it still wasn't going to get the right price:
SELECT product.id, product.num, product.price AS CurrentPrice, revisions.price AS OldPrice
FROM product
LEFT JOIN (SELECT product_audit.id AS id, product_audit.price AS price, MAX(revinfo.timestamp) AS timestamp
FROM product_audit
LEFT JOIN revinfo ON product_audit.rev = revinfo.id
WHERE revinfo.timestamp <= $DATE{Date}
GROUP BY product_aud.id) AS revisions ON revisions.id = product.id
I can't seem to think of how to get to that last step. Some sort of WHERE timestamp = (SELECT...) maybe? But I haven't been able to figure that out.
Also, just a heads up, I'm limited to statements that start with SELECT because of permissions. I can't add functions or anything like that.
I had to assume how we were getting the "old" price, and my assumption was that you wanted the "earliest" revision record, so I used Row_number and a derived table to get that record and then use it in the join constraint for the revision table... not exactly sure what your logic is, but here is a fiddle with the resultset that matches your "desired results"
SELECT product.num, product.price AS CurrentPrice, product_audit.price AS OldPrice
FROM product
LEFT JOIN (select p.price, p.id, p.rev,
ROW_NUMBER() over (partition by p.id order by p.rev asc) as rn
From product_audit p
) AS product_audit ON product_audit.id = product.id
and product_audit.rn = 1
LEFT JOIN revinfo ON revinfo.id = product_audit.rev
WHERE revinfo.timestamp <= '2020-11-31';
https://www.db-fiddle.com/f/fbvrgo2gRLoBPhgwQnuvY9/3
WITH cte AS ( SELECT product.num,
product.price CurrentPrice,
product_audit.price OldPrice,
ROW_NUMBER() OVER (PARTITION BY product.num
ORDER BY revinfo.`timestamp` DESC) rn
FROM product
JOIN product_audit ON product_audit.id = product.id
JOIN revinfo ON revinfo.id = product_audit.rev
WHERE revinfo.`timestamp` <= #date
)
SELECT num, CurrentPrice, OldPrice
FROM cte
WHERE rn = 1;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=a276ec8ad89e3c2f3aaeee411072fa3e

SQL Sum Calculation Confusion

I am new with mysql and working to change a store application to make it have two stock. I created a table to store stock quantity:
Then I plan to create a view with stock quantity, per store, per SKU. I using the following query:
SELECT
`stockList`.`sku`,
SUM(A.`stockQty`) AS 'store1',
SUM(B.`stockQty`) AS 'store2',
SUM(`stockList`.`stockQty`) AS 'total'
FROM `stockList`
LEFT JOIN (
SELECT * FROM `stockList` WHERE `idStock`=1
) AS A
ON `stockList`.`sku`=A.`sku`
LEFT JOIN (
SELECT * FROM `stockList` WHERE `idStock`=2
) AS B
ON `stockList`.`sku`=B.`sku`
GROUP BY `stockList`.`sku`
Per resulting table, calculation is not proper and I could not identify the logic:
SKU 43 should show for store1 = 9 and for store2 = 10, total = 19. This is what they show if I execute the select queries alone. Please, let me know if I misunderstood how this sum logic works.
You might to use SUM on subquery to calculate Totle price by sku
LEFT JOIN may make some fields not match causing NULL so use IFNULL to preset value 0
You can try this.
SELECT
T.sku,
SUM(T.stockQty) as totle,
IFNULL(A.`store1`,0) AS `store1`,
IFNULL(B.`store2`,0) AS `store2`
FROM `stockList` AS T
LEFT JOIN
(
SELECT sku,SUM(`stockQty`) as `store1`
FROM `stockList`
WHERE `idStock`=1
GROUP BY sku
) as A ON A.sku = T.sku
LEFT JOIN
(
SELECT sku,SUM(`stockQty`) as `store2`
FROM `stockList`
WHERE `idStock`=2
GROUP BY sku
) AS B ON T.sku =B.sku
GROUP BY T.sku
sqlfiddle
Your query is much more complicated than it needs to be. You can just do this:
SELECT
sku,
SUM(stockQty) as total,
SUM(IF(idStock=1,stockQty,0)) AS `store1`,
SUM(IF(idStock=2,stockQty,0)) AS `store2`
FROM `stockList`
GROUP BY sku
Output:
sku total store1 store2
36 10 10 0
37 3 3 0
38 4 4 0
39 3 3 0
40 10 10 0
41 12 12 0
42 12 12 0
43 19 9 10

Multiple Row and Column via Count Statements

So I intend to Query data for a couple of charts in MySQL,
The data for the charts come from the same table, but work for multiple Time ranges. So group by doesn't work.
The Date itself I want in the format
Total Oranges Mangoes Bananas Tomatoes
----------------------------------------------
19 5 6 7 1
24 3 7 2 12
14 3 4 5 2
20 5 7 6 2
Now, I can get the Initial row using either
SELECT
sum(CASE WHEN Some_Criteria and (created between '2016-03-28' AND '2016-03-29')) THEN 1 ELSE 0 END ) Total ,
sum(CASE WHEN Some_Other_Criteria and (created between '2016-03-28' AND '2016-03-29')) THEN 1 ELSE 0 END ) Oranges
...
from SomeTable.Table1
left join OtherTable ON SomeId = OtherId
OR via
SELECT
(SELECT count(distinct Field) FROM from SomeTable.Table1 left join OtherTable ON SomeId = OtherId WHERE SomeCriteria and (DateRange)) AS Total
(SELECT count(distinct Field) FROM from SomeTable.Table1 left join OtherTable ON SomeId = OtherId WHERE SomeCriteria and (DateRange)) AS Oranges
...
Getting it in the same row would be easy as I just use the above method, but what if I want the data to be in multiple rows but based on the different date ranges?
You'll need those date ranges in a resultset (either a subquery, a temporary table, or a permanent table of some sort). Once you have that you can use your first query with a JOIN to your ranges table and then using GROUP BY on those ranges.
For example:
SELECT
DR.start_date,
DR.end_date,
SUM(CASE WHEN Some_Criteria AND (created BETWEEN DR.start_date AND DR.end_date)) THEN 1 ELSE 0 END ) Total,
SUM(CASE WHEN Some_Other_Criteria AND (created BETWEEN DR.start_date AND DR.end_date)) THEN 1 ELSE 0 END ) Oranges,
...
FROM
Date_Ranges DR
INNER JOIN SomeTable.Table1 T1 ON
T1.created >= DR.start_date AND
T1.created < DR.end_date
LEFT OUTER JOIN OtherTable OT ON OT.SomeId = T1.OtherId
GROUP BY
DR.start_date,
DR.end_date

mySQL Query - select join if latest record in join table contains a specific value

I am trying to write a select statement with a right join (to clients), that will find a specific value in the join table - but ONLY if that is the most recent value for each client (ignoring blanks and nulls).
Clients
Id Name
0 John Doe
1 Frank Smith
2 Sue Smith
3 John Smith
Activity (join table)
ClientId Type Date
0 500 2013-01-01 00:00:08
1 900 2013-01-01 00:00:07
2 NULL 2013-01-01 00:00:06
3 2013-01-01 00:00:05
4 500 2013-01-01 00:00:05
0 800 2013-01-01 00:00:04
1 500 2013-01-01 00:00:03
2 500 2013-01-01 00:00:02
3 500 2013-01-01 00:00:01
4 800 2013-01-01 00:00:00
So this query will at least give me only the client records that have an activity type of 500 (in this case I would get back client 0 and 4):
select * from clients right join activity on activity.clientid = clients.id
where activity.type = 500
HOWEVER, I need to figure out how to make this return ONLY the first record in the above list of records. The logic there is Client #0 is the only client that has 500 as it's latest activity type = 500. The other 3 clients have NULL, blank, or 900 for example as their 'latest' activity type.
I am thinking some magic with ordering (the date would normally be pretty accurate), a 'top' and/or 'limit' and possibly union? Just cant quite wrap my head around it.
Please try this
SELECT activity.id AS activityid
, activity.type
, activity.date
, clients.id AS clientid
, clients.name
FROM activity
LEFT JOIN activity AS other_activities
ON activity.ClientID = other_activities.ClientID
AND activity.date < other_activities.date
LEFT JOIN clients
ON activity.ClientID = clients.id
WHERE activity.type = 500
AND other_activities.ClientID IS NULL;
SELECT * from Activity
INNER JOIN (SELECT MIN(Date) as min_date, clientID
FROM Activity
GROUP BY clientID) temp
ON Activity.clientID = temp.clientID
WHERE date = min_date and type = 500
This will return all clientID's whose most recent activity was of type 500.
This will get you the most recent Activity of type 500 and the client of that activity
SELECT * FROM
(SELECT *
FROM activity
WHERE type=500
ORDER BY date DESC
LIMIT 1) a
LEFT JOIN
clients c
ON (a.clientid = c.id)
of if you only want the result if it's the most recent activity and the type is 500 you can use
SELECT * FROM
(SELECT *
FROM activity
ORDER BY date DESC
LIMIT 1) a
LEFT JOIN
clients c
ON (a.clientid = c.id)
WHERE a.type = 500;
sqlFiddle here to get clients who have the latest activity of type 500
SELECT a1.ClientID,c.name,a1.Type,a1.Date
FROM activity a1
LEFT JOIN clients c ON (c.id = a1.clientid)
WHERE NOT EXISTS (SELECT 1
FROM activity a
WHERE a.clientid = a1.clientid
and a.date > a1.date)
AND a1.type = 500;