Selecting Rows within Separate IDs until a first occurrence of given value - tsql

I am try to get all rows within each separate ID until the first occurrence of a given value, in this case "CR" but have to reverse the order of the rows. Here is how the data is stored on the table:
ID DebtMth YearMth Status Balance
1 5 2015-02 DR 10.00
1 4 2015-03 DR 10.00
1 3 2015-04 CR 00.00
1 2 2015-06 DR 10.00
1 1 2015-07 DR 10.00
2 10 2011-01 DR 20.00
2 9 2011-02 DR 20.00
2 8 2011-03 CR 20.00
3 11 2012-02 DR 30.00
3 10 2012-03 DR 30.00
3 8 2012-05 CR 00.00
3 7 2012-06 CR 00.00
3 6 2012-07 DR 30.00
I need to reverse the order so the last row within each ID group becomes the first and so on. So the table would be sorted as follows.
ID DebtMth YearMth Status Balance
1 1 2015-07 DR 10.00
1 2 2015-06 DR 10.00
1 3 2015-04 CR 00.00
1 4 2015-03 DR 10.00
1 5 2015-02 DR 10.00
2 8 2011-03 CR 20.00
2 9 2011-02 DR 20.00
2 10 2011-01 DR 20.00
3 6 2012-07 DR 30.00
3 7 2012-06 CR 00.00
3 8 2012-05 CR 00.00
3 10 2012-03 DR 30.00
3 11 2012-02 DR 30.00
Now I need to select rows within each ID group up until the Status is 'CR' and exclude any ID whose first row is 'CR'. So the output would look like this.
ID DebtMth YearMth Status Balance
1 1 2015-07 DR 10.00
1 2 2015-06 DR 10.00
3 6 2012-07 DR 30.00
I am using the Query Designer in Report Builder 3 connecting to an Microsoft SQL2012 Server.
I would very much appreciate any suggestions.
Martin

SELECT
id , DebtMth , YearMth , Status , Balance
FROM
(
SELECT
*
, MAX(CASE WHEN status = 'CR' THEN YearMth END) OVER(PARTITION BY id) AS first_cr_yearMth
FROM YourTable
) AS T
WHERE YearMth > first_cr_yearMth OR first_cr_yearMth IS NULL

Related

postgres group by category but require 1 of each value

Edit
i've created a db fiddle to show the current db schema
https://www.db-fiddle.com/f/vwnrjuBHKYYDsXr3kAAvWE/1
I'm struggling with grouping sets unique values in a gift table.
There are 4 gifts per category which I want to count as 1 set. Kind of like a loyalty card where the set is complete when you have all 4 of the gifts.
I want to query how many sets each user has of each category.
So the source table looks like (simplified):
row
user_id
gift_id
gift_category
1
123
1
perfume
2
123
2
perfume
3
123
3
perfume
4
123
4
perfume
5
123
1
perfume
6
123
2
perfume
7
123
4
perfume
8
123
6
drink
desired outcome should be:
user_id
gift_category
set_count
123
perfume
1
because category drink doesn't have all 4 gifts (based on 4 unique gift_id's) this one doesn't count. Also because the user misses 1 perfume (gift_id 3) of the second set this also doesn't count.
Is there a way to to query the desired outcome?
*EDIT
should have at least one of each gift_id 1,2,3,4 for perfume and 5,6,7,8 for drinks to have 1 set.
the gift table looks like this (denormalized version):
gift_id
gift_category
name
1
perfume
chanel
2
perfume
olly
3
perfume
christine
4
perfume
lacoste
5
drink
beer
6
drink
wine
7
drink
vodka
8
drink
rum
so,to have 1 set of each, you have to have 1 gift id in each category
We need to compute a list of all the possible matches between Users and Gifts then outer join on the Sale table (User_Gifts).
Then GROUP BY the gift_category and but only return the groups that do not have any missing Sales
SELECT Users.user_id, gifts.gift_category_id, gift_categories.name as gift_category
FROM Users
CROSS JOIN Gifts
INNER JOIN gift_categories ON Gifts.gift_category_id = gift_categories.gift_category_id
LEFT OUTER JOIN user_gifts ON user_gifts.gift_id = gifts.gift_id and user_gifts.user_id = Users.user_id
GROUP BY Users.user_id, gifts.gift_category_id, gift_categories.name
HAVING COUNT(CASE WHEN user_gifts.gift_Id IS NULL THEN 1 END) = 0
Have a look at this fiddle for proof: https://www.db-https://www.db-fiddle.com/f/vwnrjuBHKYYDsXr3kAAvWE/2
user_id
gift_category_id
gift_category
123
1
perfume
NOTE:
In the Fiddle, there was no Users table, so I used a CTE to construct one out of the unique user_id in the user_gifts table data.
Step 1, Construct the matrix of all options and those that have been taken, we accept duplicates here:
SELECT Users.user_id
, Gifts.*
, gift_categories.name as gift_category
, user_gifts.*
FROM Users
CROSS JOIN Gifts
INNER JOIN gift_categories ON Gifts.gift_category_id = gift_categories.gift_category_id
LEFT OUTER JOIN user_gifts ON user_gifts.gift_id = gifts.gift_id and user_gifts.user_id = Users.user_id
user_id
gift_id
name
gift_category_id
name
user_gift_id
gift_id
user_id
123
1
chanel
1
perfume
1
1
123
123
1
chanel
1
perfume
5
1
123
123
2
olly
1
perfume
6
2
123
123
2
olly
1
perfume
2
2
123
123
3
christine
1
perfume
3
3
123
123
4
lacoste
1
perfume
4
4
123
123
4
lacoste
1
perfume
7
4
123
123
5
beer
2
drinks
null
null
null
123
6
wine
2
drinks
8
6
123
123
7
vodka
2
drinks
null
null
null
123
8
rum
2
drinks
null
null
null
Step 2: Group the results and Count the zeros and non-zeros (we only need the zeros but its helpful to see that it works)
SELECT Users.user_id, gifts.gift_category_id, gift_categories.name as gift_category
, COUNT(CASE WHEN user_gifts.gift_Id IS NULL THEN 1 END) as MissingGifts
, COUNT(CASE WHEN user_gifts.gift_Id IS NOT NULL THEN 1 END) as SoldGifts
FROM Users
CROSS JOIN Gifts
INNER JOIN gift_categories ON Gifts.gift_category_id = gift_categories.gift_category_id
LEFT OUTER JOIN user_gifts ON user_gifts.gift_id = gifts.gift_id and user_gifts.user_id = Users.user_id
GROUP BY Users.user_id, gifts.gift_category_id, gift_categories.name
user_id
gift_category_id
gift_category
missinggifts
soldgifts
123
1
perfume
0
7
123
2
drinks
3
0
Step 3: Use HAVING to ensure we only select the rows that have Zero, 0s, or in this set, zero missinggifts
HAVING COUNT(CASE WHEN user_gifts.gift_Id IS NULL THEN 1 END) = 0

PostgreSQL window function & difference between dates

Suppose I have data formatted in the following way (FYI, total row count is over 30K):
customer_id order_date order_rank
A 2017-02-19 1
A 2017-02-24 2
A 2017-03-31 3
A 2017-07-03 4
A 2017-08-10 5
B 2016-04-24 1
B 2016-04-30 2
C 2016-07-18 1
C 2016-09-01 2
C 2016-09-13 3
I need a 4th column, let's call it days_since_last_order which, in the case where order_rank = 1 then 0 else calculate the number of days since the previous order (with rank n-1).
So, the above would return:
customer_id order_date order_rank days_since_last_order
A 2017-02-19 1 0
A 2017-02-24 2 5
A 2017-03-31 3 35
A 2017-07-03 4 94
A 2017-08-10 5 38
B 2016-04-24 1 0
B 2016-04-30 2 6
C 2016-07-18 1 79
C 2016-09-01 2 45
C 2016-09-13 3 12
Is there an easier way to calculate the above with a window function (or similar) rather than join the entire dataset against itself (eg. on A.order_rank = B.order_rank - 1) and doing the calc?
Thanks!
use the lag window function
SELECT
customer_id
, order_date
, order_rank
, COALESCE(
DATE(order_date)
- DATE(LAG(order_date) OVER (PARTITION BY customer_id ORDER BY order_date))
, 0)
FROM <table_name>

Get Data from 2 Tables using Join

I have 2 tables :
1. transfer
2. data
in table data 2 records :
id name
1. 2 PQR
2. 3 XYZ
in table transfer 5 records :
id to from amount type
1. 1 2 3 100.00 C
2. 2 3 2 200.00 C
3. 3 2 3 150.00 D
4. 4 3 2 150.00 C
5. 5 2 3 300.00 D
now I want to form query that will take 2 in where condition and give me result
from transfer table that when 2 is in to column then from data should be shown
and when 2 is in from column then to data should be print.
And in result I want other columns that are amount and type.
I want data using join (Any), I am totally confused that how to perform this task.
Expected Result :
from/to amount type
3 100.00 C
3 200.00 C
3 150.00 D
3 300.00 D
Any Guidance on this..
Try Like this
select
case when "from"=2 then "to" when "to"=2 then "from" end "from/to"
,amount,type from transfer
Out put is
form/to amount type
3 100 C
3 200 C
3 150 D
3 150 C
3 100 D
OR
select case when "from"=2 then d.name when "to"=2 then data.name end "from/to",
amount,type from transfer inner join data on ("to"=data.id)
inner join data as d on("from"=d.id)
Out put is
form/to amount type
XYZ 100 C
XYZ 200 C
XYZ 150 D
XYZ 150 C
XYZ 100 D
ADDITION:
prove of working query: http://ideone.com/64kIov

Return all records regardless if there is a match

In my Table 1, It may have AND have a null entry in the address column to corresponding record OR not have a matching entry in Table 2.
I want to present all the records in Table 1 but also present corresponding entries from Table 2. My RESULT is what I am trying to achieve.
Table 1
ID First Last
1 John Smith
2 Bob Long
3 Bill Davis
4 Sam Bird
5 Tom Fenton
6 Mary Willis
Table 2
RefID ID Address
1 1 123 Main
2 2 555 Center
3 3 626 Smith
4 4 412 Walnut
5 1
6 2 555 Center
7 3
8 4 412 Walnut
Result
Id First Last Address
1 John Smith 123 Main
2 Bob Long 555 Center
3 Bill Davis 626 Smith
4 Sam Bird 412 Walnut
5 Tom Fenton
6 Mary Willis
You need an outer join for this:
SELECT * FROM Table1 t1 LEFT OUTER JOIN Table2 t2 ON t1.ID = t2.RefID
How do you join those two tables? If table 2 have more than 1 matched address, how do you want display them? Please clarify in your question.
Here is a query based on my assumptions.
SELECT
ID, First, Last,
Address = (SELECT MAX(Address) FROM Table2 t2 WHERE t1.ID = t2.ID)
FROM Table1 t1

TSQL, remove negative values in a group

I have a table with following data:
Id Name Value
1 John 100
2 John -500
3 John 500
4 Smith 10
5 Smith 20
6 Smith -20
7 Stuart -10
8 Wills 25
I am looking for an efficient TSQL query which can remove John -500 and Smith -20 (i.e. records with negative value if they have a similar positive value in the same group [group by names]).
I think this is what you need. (SQL DEMO)
delete y
from mytable y join (
select id,name, value
from mytable x
where value > 0) z on y.name = z.name and y.value = -1 * z.value
select * from mytable
--SELECT RESULTS AFTER DELETING
ID NAME VALUE
1 John 100
3 John 500
4 Smith 10
5 Smith 20
7 Stuart -10
8 Wills 25
delete a
from mytable a
join mytable b
on b.name = a.name
and a.value < 0
and b.value = -1 * z.value
Almost the same as Kaf +1