Cursor? Loop? Aggregate up rows data along with row results - sql

I have the following table & data
Table name = MyTable
Description | Partition | Total
------------|---------------|--------------
CASH | Reconciled | 25
CASH | Adjustm | 50
CASH | Balanc | 120
LOANS | Adjustm | 44
LOANS | Balanc | 32
CARDS | Adjustm | 81
CARDS | Balanc | 67
MTG | Adjustm | 14
MTG | Balanc | 92
The requirement is simple enough - it's a straight select from the table, but for each unique description, I need to sum up the totals of all the partitions, such that the user will see
Description | Partition | Total
------------|---------------|--------------
CASH | TOTAL | 195 <
CASH | Reconciled | 25
CASH | Adjustm | 50
CASH | Balanc | 120
LOANS | TOTAL | 76 <
LOANS | Adjustm | 44
LOANS | Balanc | 32
CARDS | TOTAL | 148 <
CARDS | Adjustm | 81
CARDS | Balanc | 67
MTG | TOTAL | 106 <
MTG | Adjustm | 14
MTG | Balanc | 92
It's a stored proc I'm writing - I don't have the option of pulling this into a MT to perform this so I need to perform it in the body of the stored proc. Am I looking at some while Loop or Cursor to provide the roll up I need, or is there another glaringly obvious and easy solution that I'm just not seeing? Aside from the roll up, it's a straight
select * from MyTable
DB is Sybase.
Thanks

You can do this by using the GROUPING SETS extension of the GROUP BY clause:
SELECT Description,
COALESCE(Parition, 'Total') AS Partition,
SUM(Total) AS Total
FROM MyTable
GROUP BY GROUPING SETS ((Description, Partition), (Description));
or you could use:
SELECT Description,
COALESCE(Parition, 'Total') AS Partition,
SUM(Total) AS Total
FROM MyTable
GROUP BY ROLLUP (Description, Partition);
Without ROLLUP, you can do this using UNION ALL:
SELECT Description,
Parition,
Total
FROM MyTable
UNION ALL
SELECT Description,
'Total' AS Partition,
SUM(Total) AS Total
FROM MyTable
GROUP BY Description;

Related

How to trace back a record all the way to origin using SQL

We are a table called ticketing that tracks all the service tickets. One ticket can lead to another ticket which leads to another ticket indicated by the replaced_by_ticket_id field below
| ticket_id | is_current | replaced_by_ticket_id |
|-----------|------------|-----------------------|
| 134 | 0 | 240 |
| 240 | 0 | 321 |
| 321 | 1 | Null |
| 34 | 0 | 93 |
| 25 | 0 | 16 |
| 16 | 0 | 25 |
| 93 | 1 | Null |
How do I write a query to get the number of tickets leading to the current ones (321 & 93)? I mean I could join the table by itself, but there is no way of knowing how many times to join. Plus different tickets have different number of levels.
Here is the expected result of the query
| ticket_id | total_tickets |
|-----------|---------------|
| 321 | 3 |
| 93 | 4 |
What is the best way to do it?
You can use a recursive query; the trick is to keep track of the original "current" ticket, so you can aggregate by that in the outer query.
So:
with cte as (
select ticket_id, ticket_id as parent_id from ticketing where is_current = 1
union all
select c.ticket_id, t.ticket_id
from ticket t
inner join cte c on c.parent_id = t.replaced_by_ticket_id
)
select ticket_id, count(*) total_tickets
from cte
group by ticket_id

Subtract constant across database tables

I need to subtract a value, found in a different table, from values across different rows.
For example, the tables I have are:
ProductID | Warehouse | Locator | qtyOnHand
-------------------------------------------
100 | A | 123 | 12
100 | A | 124 | 12
100 | A | 124 | 8
101 | A | 126 | 6
101 | B | 127 | 12
ProductID | Sold
----------------
100 | 26
101 | 16
Result:
ProductID | Warehouse | Locator | qtyOnHand | available
-------------------------------------------------------
100 | A | 123 | 12 | 0
100 | A | 123 | 12 | 0
100 | A | 124 | 8 | 6
101 | A | 126 | 6 | 0
101 | B | 127 | 12 | 12
The value should only be subtracted from those in warehouse A.
Im using postgresql. Any help is much appreciated!
If I understand correctly, you want to compare the overall stock to the cumulative amounts in the first table. The rows in the first table appear to be ordered from largest to smallest. Note: This is an interpretation and not 100% consistent with the data in the question.
Use JOIN to bring the data together and then cumulative sums and arithmetic:
select t1.*,
(case when running_qoh < t2.sold then 0
when running_qoh - qtyOnHand < t2.sold then (running_qoh - t2.sold)
else qtyOnHand
end) as available
from (select t1.*,
sum(qtyOnHand) over (partition by productID order by qtyOnHand desc) as running_qoh
from table1 t1
) t1 join
table2 t2
using (ProductID)

Unable to calculate median - SQL Server 2017

I am trying to computer the median number of transactions in each category.
A few notes (as the dataset below is a small snippet of a much larger dataset):
An employee can belong to multiple categories
Each transaction's median should be > 0
Not every person appears in every category
The data is set up like this:
| Person | Category | Transaction |
|:-------:|:--------:|:-----------:|
| PersonA | Sales | 27 |
| PersonB | Sales | 75 |
| PersonC | Sales | 87 |
| PersonD | Sales | 36 |
| PersonE | Sales | 70 |
| PersonB | Buys | 60 |
| PersonC | Buys | 92 |
| PersonD | Buys | 39 |
| PersonA | HR | 59 |
| PersonB | HR | 53 |
| PersonC | HR | 98 |
| PersonD | HR | 54 |
| PersonE | HR | 70 |
| PersonA | Other | 46 |
| PersonC | Other | 66 |
| PersonD | Other | 76 |
| PersonB | Other | 2 |
An ideal output would look like:
| Category | Median | Average |
|:--------:|:------:|:-------:|
| Sales | 70 | 59 |
| Buys | 60 | 64 |
| HR | 59 | 67 |
| Other | 56 | 48 |
I can get the average by:
SELECT
Category,
AVG(Transaction) AS Average_Transactions
FROM
table
GROUP BY
Category
And that works great!
This post tried to help me find the median. What I wrote was:
SELECT
Category,
PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY Transaction) OVER (PARTITION BY Category) AS Median_Transactions
FROM
table
GROUP BY
Category
But I get an error:
Msg 8120: Column 'Transactions' is invalid in the select list because it is not contained in either an aggregate function or the **GROUP BY** clause
How can I fix this?
You can do what you want using SELECT DISTINCT:
SELECT DISTINCT Category,
PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY Transaction) OVER (PARTITION BY Category) AS Median_Transactions
FROM table;
Unfortunately, SQL Server doesn't offer the PERCENTILE_ functions as window functions and doesn't have a MEDIAN() aggregation function. You can also do this using subqueries and counts.
It's not optimal but this is your solution
SELECT DISTINCT
category,
PERCENTILE_DISC(0.5)WITHIN GROUP(ORDER BY val) OVER (PARTITION BY category) AS Median_Transactions,
AVG(val) OVER (PARTITION BY d.category) [AVG]
FROM #data d;
I don't think this is pretty but it works. I didn't spend time on polishing it
with
avg_t as
( select category, avg(sales) as avg_sales
from sample
group by 1),
mn as
( select category, avg(sales) as median_sales
from (
select category, sales ,
row_number() over (partition by category order by sales asc) as r ,
count(person) over (partition by category) as total_count
from sample
) mn_sub
where (total_count % 2 = 0 and r in ( (total_count/2), ((total_count/2)+1)) ) or
(total_count % 2 <> 0 and r = ((total_count+1)/2))
group by 1
)
select avg_t.category, avg_t.avg_sales, mn.median_sales
from avg_t
inner join mn
on avg_t.category=mn.category

Aggregate columns based on different conditions?

I have a Teradata query that generates:
customer | order | amount | days_ago
123 | 1 | 50 | 2
123 | 1 | 50 | 7
123 | 2 | 10 | 19
123 | 3 | 100 | 35
234 | 4 | 20 | 20
234 | 5 | 10 | 10
With performance in mind, what’s the most efficient way to produce an output per customer where orders is the number of distinct orders a customer had within the last 30 days and total is the sum of the amount of the distinct orders regardless of how many days ago the order was placed?
Desired output:
customer | orders | total
123 | 2 | 160
234 | 2 | 30
Given your rules, maybe it takes two steps - de-duplicate first then aggregate:
SELECT customer,
SUM(CASE WHEN days_ago <=30 THEN 1 ELSE 0 END) AS orders,
SUM(amount) AS total
FROM
(SELECT customer, order, MAX-or-MIN(amount) AS amount, MIN-or-MAX(days_ago) AS days_ago
FROM your_relation
GROUP BY 1, 2) AS DistinctCustOrder
GROUP BY 1;

Subtract the value of a row from grouped result

I have a table supplier_account which has five coloumns supplier_account_id(pk),supplier_id(fk),voucher_no,debit and credit. I want to get the sum of debit grouped by supplier_id and then subtract the value of credit of the rows in which voucher_no is not null. So for each subsequent rows the value of sum of debit gets reduced. I have tried using 'with' clause.
with debitdetails as(
select supplier_id,sum(debit) as amt
from supplier_account group by supplier_id
)
select acs.supplier_id,s.supplier_name,acs.purchase_voucher_no,acs.purchase_voucher_date,dd.amt-acs.credit as amount
from supplier_account acs
left join supplier s on acs.supplier_id=s.supplier_id
left join debitdetails dd on acs.supplier_id=dd.supplier_id
where voucher_no is not null
But here the debit value will be same for all rows. After subtraction in the first row I want to get the result in second row and subtract the next credit value from that.
I know it is possible by using temporary tables. The problem is I cannot use temporary tables because the procedure is used to generate reports using Jasper Reports.
What you need is an implementation of the running total. The easiest way to do it with a help of a window function:
with debitdetails as(
select id,sum(debit) as amt
from suppliers group by id
)
select s.id, purchase_voucher_no, dd.amt, s.credit,
dd.amt - sum(s.credit) over (partition by s.id order by purchase_voucher_no asc)
from suppliers s
left join debitdetails dd on s.id=dd.id
order by s.id, purchase_voucher_no
SQL Fiddle
Results:
| id | purchase_voucher_no | amt | credit | ?column? |
|----|---------------------|-----|--------|----------|
| 1 | 1 | 43 | 5 | 38 |
| 1 | 2 | 43 | 18 | 20 |
| 1 | 3 | 43 | 8 | 12 |
| 2 | 4 | 60 | 5 | 55 |
| 2 | 5 | 60 | 15 | 40 |
| 2 | 6 | 60 | 30 | 10 |