SSRS - Group Consecutive Rows of Same Time Spans - vb.net

Related to this SQL question - Group consecutive rows of same value using time spans
I want to convert this table:
╔═══════════╦════════════╦═══════════╦═══════════╦═════════╗
║ Classroom ║ CourseName ║ Lesson ║ StartTime ║ EndTime ║
╠═══════════╬════════════╬═══════════╬═══════════╬═════════╣
║ 1001 ║ Course 1 ║ Lesson 1 ║ 0800 ║ 0900 ║
║ 1001 ║ Course 1 ║ Lesson 2 ║ 0900 ║ 1000 ║
║ 1001 ║ Course 1 ║ Lesson 3 ║ 1000 ║ 1100 ║
║ 1001 ║ Course 2 ║ Lesson 10 ║ 1100 ║ 1200 ║
║ 1001 ║ Course 2 ║ Lesson 11 ║ 1200 ║ 1300 ║
║ 1001 ║ Course 1 ║ Lesson 4 ║ 1300 ║ 1400 ║
║ 1001 ║ Course 1 ║ Lesson 5 ║ 1400 ║ 1500 ║
╚═══════════╩════════════╩═══════════╩═══════════╩═════════╝
To this table:
╔═══════════╦════════════╦═══════════╦═════════╗
║ Classroom ║ CourseName ║ StartTime ║ EndTime ║
╠═══════════╬════════════╬═══════════╬═════════╣
║ 1001 ║ Course 1 ║ 0800 ║ 1100 ║
║ 1001 ║ Course 2 ║ 1100 ║ 1300 ║
║ 1001 ║ Course 1 ║ 1300 ║ 1500 ║
╚═══════════╩════════════╩═══════════╩═════════╝
The SQL solution from the related question works but the query takes forever because I have a lot of data in my tables and the SQL Query is using 2 sub queries.
Actually the original table is a query with 3 joins in itself so the complexity is even bigger.
I am looking for an SSRS solution.
Is it possible using some "VB Magic" or other kind of magic in SSRS 2008 R2 to do this ?

Related

Find percentage ratio within sum values in same table

I have a list of retail transactions in a SQL table. The table contains details for the customer number, product number, transaction type, transaction date and amount.
Ultimately, I need to produce a record that contains the customer number, product number, transaction type, sum of transaction amounts and a %. The percentage represents what proportion of all transactions for that customer/product combination were of that given transaction type.
For example my table has data like this:
╔══════════╦═════════╦═════════╦════════════╦═══════════╗
║ Customer ║ Product ║ TxnType ║ TxnDate ║ TxnAmount ║
╠══════════╬═════════╬═════════╬════════════╬═══════════╣
║ Smith ║ 1234 ║ Cash ║ 01/01/2018 ║ 10 ║
║ Smith ║ 1234 ║ Credit ║ 02/01/2018 ║ 20 ║
║ Smith ║ 1234 ║ Cash ║ 03/01/2018 ║ 10 ║
║ Smith ║ 1234 ║ Cash ║ 04/01/2018 ║ 20 ║
║ Smith ║ 3456 ║ Cash ║ 01/01/2018 ║ 10 ║
║ Smith ║ 3456 ║ Credit ║ 02/01/2018 ║ 20 ║
║ Smith ║ 3456 ║ Cash ║ 03/01/2018 ║ 10 ║
║ Jones ║ 3456 ║ Credit ║ 01/01/2018 ║ 10 ║
║ Jones ║ 3456 ║ Cash ║ 02/01/2018 ║ 10 ║
║ Jones ║ 3456 ║ Credit ║ 01/01/2018 ║ 20 ║
║ Jones ║ 1234 ║ Credit ║ 01/01/2018 ║ 10 ║
║ Jones ║ 1234 ║ Credit ║ 02/01/2018 ║ 20 ║
║ Jones ║ 1234 ║ Credit ║ 03/01/2018 ║ 20 ║
║ Jones ║ 1234 ║ Credit ║ 04/01/2018 ║ 40 ║
╚══════════╩═════════╩═════════╩════════════╩═══════════╝
And I need a result of this:
╔══════════╦═════════╦═════════╦══════════════╦════════════╗
║ Customer ║ Product ║ TxnType ║ SumTxnAmount ║ %ofTxnType ║
╠══════════╬═════════╬═════════╬══════════════╬════════════╣
║ Smith ║ 1234 ║ Cash ║ 40 ║ 66% ║
║ Smith ║ 1234 ║ Credit ║ 20 ║ 33% ║
║ Smith ║ 3456 ║ Cash ║ 20 ║ 50% ║
║ Smith ║ 3456 ║ Credit ║ 20 ║ 50% ║
║ Jones ║ 3456 ║ Cash ║ 10 ║ 25% ║
║ Jones ║ 3456 ║ Credit ║ 30 ║ 75% ║
║ Jones ║ 1234 ║ Credit ║ 90 ║ 100% ║
╚══════════╩═════════╩═════════╩══════════════╩════════════╝
You can try below
DEMO
select
customer,product,TxnType,
sum(TxnAmount) as SumTxnAmount,cast((sum(TxnAmount)*100.00)/(select
sum(TxnAmount) from cte1 b where a.customer=b.customer and a.product=b.product) as decimal(16,2)) as '%ofTxnType'
from cte1 a
group by customer,product,TxnType
OUTPUT:
customer product TxnType SumTxnAmount %ofTxnType
Smith 1234 Cash 40 66.67
Smith 1234 Credit 20 33.33

Find start date of similar contiguous records

I have a table called activities that stores the activities that employees are doing. It stores simple information such as if they are working or if they are on various types of leave, e.g. annual leave, sick leave, compassionate leave etc. The table stores the employee number, the type of activity and the date that the activity is on. Only 1 type of activity can occur on a single day and only days that are normally worked will have an activity attributed to them. For example if an employee is a Monday to Friday worker and is on annual leave for a week, the weekend dates are not included in the table as they are not days the employee normally works.
Below is a sample table:
╔══════════╦════════════╦══════════════╗
║ Employee ║ Date ║ Activity ║
╠══════════╬════════════╬══════════════╣
║ 12345 ║ 25/11/2016 ║ Work ║
║ 12345 ║ 24/11/2016 ║ Work ║
║ 12345 ║ 23/11/2016 ║ Work ║
║ 12345 ║ 22/11/2016 ║ Work ║
║ 12345 ║ 21/11/2016 ║ Work ║
║ 12345 ║ 18/11/2016 ║ Work ║
║ 12345 ║ 17/11/2016 ║ Work ║
║ 12345 ║ 16/11/2016 ║ Work ║
║ 12345 ║ 15/11/2016 ║ Sick Leave ║
║ 12345 ║ 14/11/2016 ║ Sick Leave ║
║ 12345 ║ 11/11/2016 ║ Sick Leave ║
║ 12345 ║ 10/11/2016 ║ Work ║
║ 12345 ║ 9/11/2016 ║ Work ║
║ 12345 ║ 8/11/2016 ║ Work ║
║ 12345 ║ 7/11/2016 ║ Work ║
║ 12345 ║ 4/11/2016 ║ Work ║
║ 12345 ║ 3/11/2016 ║ Sick Leave ║
║ 12345 ║ 2/11/2016 ║ Sick Leave ║
║ 12345 ║ 1/11/2016 ║ Work ║
║ 12345 ║ 31/10/2016 ║ Work ║
║ 67890 ║ 25/11/2016 ║ Annual Leave ║
║ 67890 ║ 24/11/2016 ║ Annual Leave ║
║ 67890 ║ 23/11/2016 ║ Annual Leave ║
║ 67890 ║ 22/11/2016 ║ Annual Leave ║
║ 67890 ║ 21/11/2016 ║ Annual Leave ║
║ 67890 ║ 18/11/2016 ║ Work ║
║ 67890 ║ 17/11/2016 ║ Work ║
║ 67890 ║ 16/11/2016 ║ Work ║
║ 67890 ║ 15/11/2016 ║ Sick Leave ║
║ 67890 ║ 14/11/2016 ║ Sick Leave ║
║ 67890 ║ 11/11/2016 ║ Sick Leave ║
║ 67890 ║ 10/11/2016 ║ Work ║
║ 67890 ║ 9/11/2016 ║ Work ║
║ 67890 ║ 8/11/2016 ║ Work ║
║ 67890 ║ 7/11/2016 ║ Work ║
║ 67890 ║ 4/11/2016 ║ Work ║
║ 67890 ║ 3/11/2016 ║ Annual Leave ║
║ 67890 ║ 2/11/2016 ║ Annual Leave ║
║ 67890 ║ 1/11/2016 ║ Work ║
║ 67890 ║ 31/10/2016 ║ Work ║
╚══════════╩════════════╩══════════════╝
For a given employee, date and activity, I need to work backwards from that date and find the start date of the most recent block of that given activity. A 'block' is any group of the same activity, so it could be 1 day or many days.
As an example, using the table above, let's say I need to find the start date of the most recent 'Sick Leave' for employee 12345 working backwards from a date of 20/11/2016. In this case I would be looking to get a value of '11/11/2016' as this was the start date for the most recent block of sick leave.
As another example, using the table above, let's say I need to find the start date of the most recent 'Annual Leave' for employee 67890 working backwards from a date of 20/11/2016. In this case I would be looking to get a value of '21/11/2016' as this was the start date for the most recent block of annual leave.
This is an example of a "gaps-and-islands" problem. You can get the periods of activity for an employee using the difference of row numbers approach:
select employee, activity, min(date), max(date)
from (select t.*,
row_number() over (partition by employee order by date) as seqnum_e,
row_number() over (partition by employee, activity order by date) as seqnum_ea
from t
) t
group by employee, activity, (seqnum_e - seqnum_ea);
You can then use this to answer your questions. For instance:
with ea as (
select employee, activity, min(date) as date_from, max(date) as date_to
from (select t.*,
row_number() over (partition by employee order by date) as seqnum_e,
row_number() over (partition by employee, activity order by date) as seqnum_ea
from t
) t
group by employee, activity, (seqnum_e - seqnum_ea)
)
select top 1 ea.*
from ea
where employee = 12345 and activity = 'Sick Leave'
order by date_from desc;
There are other solutions for particular questions, but this is likely to be the most general.

Selecting unique values from self-referencing table

Suppose we have the following data in the table named My_Tabel:
╔═══════════╦═════════════╦════════════╗
║ ID ║ Person_Name ║ Partner_ID ║
╠═══════════╬═════════════╬════════════╬
║ 101 ║ John ║ 3 ║
║ 100 ║ Miller ║ 0 ║
║ 3 ║ Ruby ║ 101 ║
║ 180 ║ Jack ║ 0 ║
║ 199 ║ George ║ 65 ║
║ 23 ║ Joseph ║ 0 ║
║ 34 ║ Fredrick ║ 117 ║
║ 117 ║ Jinan ║ 34 ║
║ 122 ║ Verena ║ 0 ║
║ 65 ║ Mary ║ 199 ║
╚═══════════╩═════════════╩════════════╝
Where 0 values in Partner_ID Column indicates that he/she is single.
We need to display partnered persons without repeating or duplication, the desired result should look like:
╔═════════════╦══════════════╗
║ Person_Name ║ Partner_Name ║
╠═════════════╬══════════════╬
║ John ║ Ruby ║
║ George ║ Mary ║
║ Fredrick ║ Jinan ║
╚═════════════╩══════════════╝
what is the best SQL query that returns the above results?
I'm using this code:
SELECT
t1.Name, t2.Name
FROM My_Tabel t1
INNER JOIN My_Tabel t2 ON (t2.ID = t1.Partner_ID)
but it the returned result is:
╔═════════════╦══════════════╗
║ Person_Name ║ Partner_Name ║
╠═════════════╬══════════════╬
║ John ║ Ruby ║
║ Ruby ║ John ║
║ George ║ Mary ║
║ Mary ║ George ║
║ Fredrick ║ Jinan ║
║ Jinan ║ Fredrick ║
╚═════════════╩══════════════╝
how the SQL statement should be updated (or replaced with another) to get the desired results?
Just add a condition to get one side of each pair:
SELECT t1.Name, t2.Name
FROM My_Table t1 INNER JOIN
My_Table t2
ON (t2.ID = t1.Partner_ID)
WHERE t1.ID < t2.ID;

H2, how to update with nested selects?

I have two tables, EVENT and EVENT_REV
EVENT:
╔══════════╦════════════════════╗
║ EVENT_ID ║ SENT_INTO_WF_BY_ID ║
╠══════════╬════════════════════╣
║ 1 ║ null ║
║ 2 ║ null ║
║ 3 ║ null ║
║ 4 ║ null ║
║ 5 ║ null ║
╚══════════╩════════════════════╝
and EVENT_REV:
╔══════════════╦══════════╦═════════╦════════╦════════════╦══════════╗
║ EVENT_REV_ID ║ EVENT_ID ║ USER_ID ║ STATUS ║ VALID_FROM ║ VALID_TO ║
╠══════════════╬══════════╬═════════╬════════╬════════════╬══════════╣
║ 1 ║ 1 ║ 54 ║ 0 ║ 1000 ║ 1001 ║
║ 2 ║ 1 ║ 55 ║ 100 ║ 2000 ║ 2001 ║
║ 3 ║ 1 ║ 56 ║ 200 ║ 3000 ║ 3001 ║
║ 4 ║ 2 ║ 57 ║ 0 ║ 4000 ║ 4001 ║
║ 5 ║ 3 ║ 58 ║ 0 ║ 5000 ║ 5001 ║
║ 6 ║ 3 ║ 59 ║ 100 ║ 6000 ║ null ║
║ 7 ║ 4 ║ 60 ║ 0 ║ 7000 ║ null ║
║ 8 ║ 5 ║ 61 ║ 500 ║ 8000 ║ 8001 ║
║ 9 ║ 5 ║ 62 ║ 600 ║ 9000 ║ 9001 ║
╚══════════════╩══════════╩═════════╩════════╩════════════╩══════════╝
I want to update the EVENT table and set the SENT_INTO_WF_BY_ID
The rule for this is:
event_ids should match (EVENT.EVENT_ID = EVENT_REV.EVENT_ID)
take the row where STATUS is not equal to the STATUS with the lowest VALID_FROM. Which should be the row with the second lowest VALID_FROM
From that row, take the USER_ID
For example:
For the EVENT_ID = 1 it should select the 2nd row from EVENT_REV and put the USER_ID 55 into the SENT_INTO_WF_BY_ID
Because inner joins are not allowed for H2, my query looks like this:
UPDATE event ltm
SET ltm.sent_into_wf_by_id =
(SELECT top 1 ltmRev.user_id
FROM event_rev ltmRev
WHERE ltmRev.event_id = ltm.event_id
AND ltmRev.status !=
(SELECT top 1 EVENT_REV.status
FROM EVENT_REV
ORDER BY valid_from ASC nulls LAST)
ORDER BY ltmRev.valid_to ASC nulls LAST)
The result should look like:
╔══════════╦════════════════════╗
║ EVENT_ID ║ SENT_INTO_WF_BY_ID ║
╠══════════╬════════════════════╣
║ 1 ║ 55 ║
║ 2 ║ null ║
║ 3 ║ 59 ║
║ 4 ║ null ║
║ 5 ║ 62 ║
╚══════════╩════════════════════╝
but it's actually:
╔══════════╦════════════════════╗
║ EVENT_ID ║ SENT_INTO_WF_BY_ID ║
╠══════════╬════════════════════╣
║ 1 ║ 55 ║
║ 2 ║ null ║
║ 3 ║ 59 ║
║ 4 ║ null ║
║ 5 ║ 61 <-- wrong ║
╚══════════╩════════════════════╝
Could solve it with the following query:
UPDATE ltm_op_risk_event ltm
SET ltm.sent_into_wf_by_id =
(SELECT ltmRev.adm_user_id
FROM ltm_op_risk_event_rev ltmRev
WHERE ltmRev.ltm_op_risk_event_id = ltm.ltm_op_risk_event_id
AND ltmRev.status !=
(SELECT ltmRev2.status
FROM LTM_OP_RISK_EVENT_REV ltmRev2
WHERE valid_from IS NOT NULL
AND ltmRev.ltm_op_risk_event_id = ltmRev2.ltm_op_risk_event_id
ORDER BY valid_from ASC LIMIT 1)
ORDER BY ltmRev.valid_to ASC LIMIT 1)
WHERE ltm.sent_into_wf_by_id IS NULL;
The missing part was the AND ltmRev.ltm_op_risk_event_id = ltmRev2.ltm_op_risk_event_id in the innermost select. I first tested this connection with the wrong connections...

Return rows only if 2 values both exist

I need to return a list of customer names from a purchase summary table but only if the customer has bought 2 definitive items within the 1 transaction.
For example table 'transaction'
╔══════════════╦════════╦══════════════╦════════╗
║ CustomerName ║ Item ║ Transaction# ║ Amount ║
╠══════════════╬════════╬══════════════╬════════╣
║ Smith ║ Hammer ║ 1 ║ 50.00 ║
║ Smith ║ Nail ║ 1 ║ 4.00 ║
║ Smith ║ Screw ║ 1 ║ 5.00 ║
║ Brown ║ Nail ║ 2 ║ 4.00 ║
║ Brown ║ Screw ║ 2 ║ 4.00 ║
║ Jones ║ Hammer ║ 3 ║ 50.00 ║
║ Jones ║ Screw ║ 3 ║ 4.00 ║
║ Smith ║ Nail ║ 4 ║ 50.00 ║
║ Smith ║ Hammer ║ 4 ║ 4.00 ║
║ Smith ║ Screw ║ 5 ║ 5.00 ║
╚══════════════╩════════╩══════════════╩════════╝
I only want to return customers who have bought a Hammer and a screw in the same transaction. It doesn't matter what other items were bought in the same transaction, I only need the details for the hammer and the screw, and only if both the hammer and screw were present in the same transaction.
So the above only needs to return:
╔══════════════╦════════╦══════════════╦════════╗
║ CustomerName ║ Item ║ Transaction# ║ Amount ║
╠══════════════╬════════╬══════════════╬════════╣
║ Smith ║ Hammer ║ 1 ║ 50.00 ║
║ Smith ║ Screw ║ 1 ║ 5.00 ║
╚══════════════╩════════╩══════════════╩════════╝
Because only transaction 1 contained both a hammer and a screw in the same transaction.
Use a sub-select to find transactions including both Hammer and Screw:
select CustomerName, Item, Transaction#, Amount
from purchase
where Transaction# in (select Transaction# from purchase
where Item in ('Hammer', 'Screw')
group by Transaction#
having count(distinct Item) = 2)
and Item in ('Hammer', 'Screw')
Remove last row if also Nail row should be returned!