Creating an accumulating rollup - sql

I have records in a table that have codes specific to a certain level and an amount attached to that level. They do not add up and that is not the issue.
I wish to create a query that sums up all the values by the level code plus those in the levels below it. I would also like the amount per level in the same query, but it is not necessary. I have create a sample table and output below. Does anyone have a good way of doing this? Also, is there an actual definition for this kind of roll up?
CREATE TABLE LEVEL_AMOUNTS(
LEVEL_CODE char(1)
AMOUNT integer
)
INSERT INTO LEVEL_AMOUNTS VALUES
('A',1),('A',1),('A',1),('A',1),
('B',1),('B',1),('B',1),('B',1),
('C',1),('C',1),('C',1),('C',1)
Output:
A | 12
B | 8
C | 4

with cte as (
select distinct level_code
from level_amounts
)
select l.level_code, sum(l.amount)
from cte
inner join level_amounts l on l.level_code <= cte.level_code
group by l.level_code
or
select l.level_code, sum(l.amount)
from level_amounts l
inner join (select distinct level_code
from level_amounts) l1
on l.level_code <= l1.level_code
group by l.level_code;
sqlfiddle

SQL Server 2008 does not have cumulative sums. You can do this with subqueries or joins:
with cte as (
select level_code, sum(amount) as amount
from amounts
group by level_code
)
select level_code,
(select sum(amount) from cte cte2 where cte.level_code <= cte2.level_code) as cumamount
from cte;

Related

How to aggregate different CTEs in outer query SQL

i am trying to join two ctes to get the difference in performance of different countries and group on id here is my example
every campaign can be done in different countries, so how can i group by at the end to have 1 row per campaign id ?
CTE 1: (planned)
select
country
, campaign_id
, sum(sales) as planned_sales
from table x
group by 1,2
CTE 2: (Actual)
select
country
, campaign_id
, sum(sales) as actual_sales
from table y
group by 1,2
outer select
select
country,
planned_sales,
actual_sales
planned - actual as diff
from cte1
join cte2
on campaign_id = campaign_id
This should do it:
select
cte1.campaign_id,
sum(cte1.planned_sales),
sum(cte2.actual_sales)
sum(cte1.planned_sales) - sum(cte2.actual_sales) as diff
from cte1
join cte2
on cte1.campaign_id = cte2.campaign_id and cte1.country = cte2.country
group by 1
I would suggest using full join, so all data is included in both tables, not just data in one or the other. Your query is basically correct but it needs a group by.
select campaign_id,
sum(cte1.planned_sales) as planned_sales
sum(cte2.actual_sales) as actual_sales,
(coalesce(sum(cte1.planned_sales), 0) -
coalesce(sum(cte2.actual_sales), 0)
) as diff
from cte1 full join
cte2
using (campaign_id, country)
group by campaign_id;
That said, there is no reason why the CTEs should aggregate by both campaign and country. They could just aggregate by campaign id -- simplifying the query and improving performance.

SQL Select unique combinations of rows for other column value

I’m trying to do an analysis of the different combinations of taxes per invoice to identify how many scenarios exist.
In the tax table, column 1 is invoiceNo, column 2 is taxType. These form the composite key. There can be 1 or more taxType per invoiceNo. Example of data:
https://i.imgur.com/bcQc7vY_d.jpg?maxwidth=640&shape=thumb&fidelity=medium (Sorry but i’m new so can’t add picture).
I want to be able to report on unique taxType for any invoiceNo. Ie, 1 A is unique comb 1, 2 AB is unique comb 2, 3 A is disregarded as already returned for 1, and 4 BC is unique comb 3.
Not sure if this makes sense! Finding it hard to articulate what I’m after!
Expected output would be:
A
AB
BC
The original version of this question was tagged MySQL, so this answers the question.
If I understand correctly, you can use group_concat():
select distinct group_concat(taxtype order by taxtype)
from t
group by invoiceno;
This works with the table you have given and would work with those combinations of Tax types even if they repeat but if there are more tax codes, or there is an AC combination, or if some of the given combinations are omitted then it might get little different! You could develop this to suit the conditions, or you could give some more info: Do invoices have three codes (ABC)? do invoices have just B or just C codes? I notice that the BC invoice etc
WITH CTE (RN,InvoiceNo,TT1,TT2)
AS
(
SELECT ROW_NUMBER() OVER (ORDER BY a.InvoiceNo),a.InvoiceNo,a.TaxType,b.TaxType
FROM UniqueCombo a INNER JOIN UniqueCombo b ON a.InvoiceNo=b.InvoiceNo
)
,
CTE2 (RN,InvoiceNo,TT1,TT2)
AS
(
SELECT * FROM CTE WHERE RN IN
(
SELECT MAX(RN) FROM CTE WHERE TT1=TT2 GROUP BY InvoiceNo HAVING COUNT(InvoiceNo)=1
)
)
SELECT TT1 FROM CTE2 WHERE RN IN
(
SELECT MAX(RN) FROM CTE WHERE TT1=TT2 GROUP BY TT1,TT2 HAVING COUNT(InvoiceNo)>1
)
UNION
SELECT TT1+''+TT2 FROM CTE WHERE RN IN
(
SELECT MAX(RN)-1 FROM CTE WHERE TT1<>TT2 GROUP BY InvoiceNo
)
You can try STRING_AGG. Something like:
SELECT DISTINCT TaxTypeString
FROM
(
SELECT InvoiceNo, STRING_AGG(TaxType, '') AS TaxTypeString
FROM t
GROUP BY InvoiceNo
) x
ORDER BY TaxTypeString
The nested query, called x, should give you one row per invoice number, in the format you want. Then you have to select the distinct tax types from there.

In T-SQL, how can I collate positive and negative actions in order that they happened?

I have a table like this:
;WITH CTE AS
( SELECT *
FROM (VALUES(1,'BlueCar',NULL),
(2,'RedCar',NULL),
(3,NULL,'BlueCar'),
(4,'GreenCar',NULL),
(5,NULL,'RedCar'),
(6,'BlueCar',NULL)
) AS ValuesTable(Time,Buy,Sell)
)
SELECT *
FROM CTE
Time Buy Sell
1 BlueCar NULL
2 RedCar NULL
3 NULL BlueCar
4 GreenCar NULL
5 NULL RedCar
6 BlueCar NULL
How can I query this table to get the total number of cars still in stock? The Time column is days since the shop opened. The time that the car was purchased must be preserved
Note: The input data is such that there will never be a situation where there are multiple cars in the inventory.
Expected Output
Time Buy
4 GreenCar
6 BlueCar
In the query below, I do two separate aggregations to obtain the buy and sell counts for each car. I left join buys to sells, which should not run the risk of losing data assuming that the dealer did not short sell any inventory which does not actually exist.
Then I join that result to a CTE which finds the latest time for each car. This would then correspond to the time when the most recent car came into inventory, for each car type.
I also include the inventory count, which you did request, but it may be useful for you if you decide to expand the scope of your query later on.
WITH yourTable AS (
SELECT 1 AS Time, 'BlueCar' AS Buy, NULL AS Sell UNION ALL
SELECT 2,'RedCar',NULL UNION ALL
SELECT 3,NULL,'BlueCar' UNION ALL
SELECT 4,'GreenCar',NULL UNION ALL
SELECT 5,NULL,'RedCar' UNION ALL
SELECT 6,'BlueCar',NULL
),
cte AS (
SELECT Buy, Time
FROM
(
SELECT Buy, Time,
ROW_NUMBER() OVER (PARTITION BY Buy ORDER BY Time DESC) rn
FROM yourTable
) t
WHERE rn = 1
)
SELECT
t1.Buy,
t1.buy_cnt - COALESCE(t2.sell_cnt, 0) AS inventory,
t3.Time
FROM
(
SELECT Buy, COUNT(*) AS buy_cnt
FROM yourTable
GROUP BY Buy
) t1
LEFT JOIN
(
SELECT Sell, COUNT(*) AS sell_cnt
FROM yourTable
GROUP BY Sell
) t2
ON t1.Buy = t2.Sell
LEFT JOIN cte t3
ON t1.Buy = t3.Buy
WHERE
t1.Buy IS NOT NULL AND
t1.buy_cnt - COALESCE(t2.sell_cnt, 0) > 0
ORDER BY
t3.Time;
Output:
Demo here:
Rextester
You can do this with a not exists:
;WITH CTE AS
( SELECT *
FROM (VALUES(1,'BlueCar',NULL),
(2,'RedCar',NULL),
(3,NULL,'BlueCar'),
(4,'GreenCar',NULL),
(5,NULL,'RedCar'),
(6,'BlueCar',NULL)
) AS ValuesTable(Time,Buy,Sell)
)
SELECT
[Time], Buy
FROM CTE as T1
WHERE
NOT EXISTS (SELECT 1 FROM CTE as T2 WHERE T2.TIME > T1.TIME AND T1.Buy = T2.Sell) AND
BUY IS NOT NULL
Presumably, you want:
with cte as (
. . .
)
select count(buy) - count(sell)
from cte;
Note: This does not verify that what you sell is something that has already been bought. It just counts up the non-NULL values in each column and takes the difference.
To get the stock at a certain point in time you can do
SELECT car, SUM(Inc) total FROM
(SELECT ID, Buy car, 1 Inc FROM tbl WHERE Buy>''
UNION ALL
SELECT ID, Sell car, -1 Inc FROM tbl WHERE Sell>'') coll
WHERE ID < 20 -- some cut-off time
GROUP BY car
I combine the two columns Buy and Sell into one (= car) and add another column (inc) with the increment of each action (-1 or 1). The rest is simple: select with a group by [car] and summation over column inc.
Here is a little demo: http://rextester.com/LLQDW60692
It is Good Question. I like that. Time by time your expected outputs changes.Its ok.
check below simple query for your problem.
Using Joins and Rownumber() we can achieve this.
;with CTE as
(
select a.time,a.buy,a.rid,COALESCE(b.rid,0)rid2 ,coalesce(b.sell,a.buy)sell from
( select time,buy,ROW_NUMBER()over( partition by buy order by (select 1)) rid
from #tableName where buy is not null)a left join
( select time,sell, ROW_NUMBER()over( partition by sell order by (select 1)) rid
from #TableName
where sell is not null )b on a.buy=b.sell
)
select Time,Buy from CTE
where rid!=rid2
Sample Demo For All Your Expected outputs.
Demo Link : Click Here
ALL Required Outputs :

Column Sum in Select Query

I have problem in sql server select query. I have following table.
ID-----Name----Quantity
1-------a-----------10
2-------b-----------30
3-------c-----------20
4-------d-----------15
5-------e-----------10
6-------f-----------30
7-------g-----------40
I want to select those record where the sum of Quantity < value. For example if I say select those record where the Quantity sum <65 then the output will be
ID-----Name----Quantity
1-------a-----------10
2-------b-----------30
3-------c-----------20
because if we include the next record then the sum of Quantity will 75.
I want to create this query. Please help me out.
You can simply use a correlated subquery to do so, and it will work fine for both MySQL, and SQL Server. But it is not the best performance wise solution:
SELECT
ID,
Name,
Quantity
FROM
(
SELECT
t1.ID,
t1.Name,
t1.Quantity,
(SELECT SUM(t2.Quantity)
FROM tablename AS t2
WHERE t2.ID <= t1.ID) AS Total
FROM Tablename AS t1
) AS t
WHERE Total < 65;
See it in action:
SQL Fiddle Demo
This will give you:
| ID | NAME | QUANTITY |
------------------------
| 1 | a | 10 |
| 2 | b | 30 |
| 3 | c | 20 |
Best performance wise solution is using recursive CTE.
WITH CTE_Prepare AS
(
SELECT
*,
ROW_NUMBER() OVER (ORDER BY id) AS RN
FROM TableRT
)
, CTE_Recursive AS
(
SELECT ID, Name, Quantity, QUantity AS SumQuantity, RN FROM CTE_Prepare WHERE RN = 1
UNION ALL
SELECT p.ID, p.Name, p.Quantity, r.SumQuantity + p.Quantity AS SumQuantity, r.RN + 1 AS RN FROM CTE_Recursive r
INNER JOIN CTE_Prepare p ON p.RN = r.RN+1
WHERE r.SumQuantity + p.Quantity < 65
)
SELECT *
FROM CTE_Recursive
OPTION (MAXRECURSION 0)
The first CTE is just to calculate ROW_NUMBERS to use instead of your IDs, because it's important not to have gaps and we can't usually be sure with any ID that it would be the case.
Second CTE is two-part recursive, adding Quantity for each next row. You can google about SQL Server recursive CTEs more if needed.
I think this is better then any other approach to find running totals (that's what this concept is called) because it only works with two rows at the time - not adding all previous rows for each calculation and it actually stops as soon it reaches the wanted mark.
SQLFiddle - few rows sample
SQLFiddle - 10000 rows
EDIT: Corrected a few mistakes. In order for this to be fast, WHERE clause needs to be inside CTE and not outside.

SQL query to select distinct row with minimum value

I want an SQL statement to get the row with a minimum value.
Consider this table:
id game point
1 x 5
1 z 4
2 y 6
3 x 2
3 y 5
3 z 8
How do I select the ids that have the minimum value in the point column, grouped by game? Like the following:
id game point
1 z 4
2 y 5
3 x 2
Use:
SELECT tbl.*
FROM TableName tbl
INNER JOIN
(
SELECT Id, MIN(Point) MinPoint
FROM TableName
GROUP BY Id
) tbl1
ON tbl1.id = tbl.id
WHERE tbl1.MinPoint = tbl.Point
This is another way of doing the same thing, which would allow you to do interesting things like select the top 5 winning games, etc.
SELECT *
FROM
(
SELECT ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Point) as RowNum, *
FROM Table
) X
WHERE RowNum = 1
You can now correctly get the actual row that was identified as the one with the lowest score and you can modify the ordering function to use multiple criteria, such as "Show me the earliest game which had the smallest score", etc.
This will work
select * from table
where (id,point) IN (select id,min(point) from table group by id);
As this is tagged with sql only, the following is using ANSI SQL and a window function:
select id, game, point
from (
select id, game, point,
row_number() over (partition by game order by point) as rn
from games
) t
where rn = 1;
Ken Clark's answer didn't work in my case. It might not work in yours either. If not, try this:
SELECT *
from table T
INNER JOIN
(
select id, MIN(point) MinPoint
from table T
group by AccountId
) NewT on T.id = NewT.id and T.point = NewT.MinPoint
ORDER BY game desc
SELECT DISTINCT
FIRST_VALUE(ID) OVER (Partition by Game ORDER BY Point) AS ID,
Game,
FIRST_VALUE(Point) OVER (Partition by Game ORDER BY Point) AS Point
FROM #T
SELECT * from room
INNER JOIN
(
select DISTINCT hotelNo, MIN(price) MinPrice
from room
Group by hotelNo
) NewT
on room.hotelNo = NewT.hotelNo and room.price = NewT.MinPrice;
This alternative approach uses SQL Server's OUTER APPLY clause. This way, it
creates the distinct list of games, and
fetches and outputs the record with the lowest point number for that game.
The OUTER APPLY clause can be imagined as a LEFT JOIN, but with the advantage that you can use values of the main query as parameters in the subquery (here: game).
SELECT colMinPointID
FROM (
SELECT game
FROM table
GROUP BY game
) As rstOuter
OUTER APPLY (
SELECT TOP 1 id As colMinPointID
FROM table As rstInner
WHERE rstInner.game = rstOuter.game
ORDER BY points
) AS rstMinPoints
This is portable - at least between ORACLE and PostgreSQL:
select t.* from table t
where not exists(select 1 from table ti where ti.attr > t.attr);
Most of the answers use an inner query. I am wondering why the following isn't suggested.
select
*
from
table
order by
point
fetch next 1 row only // ... or the appropriate syntax for the particular DB
This query is very simple to write with JPAQueryFactory (a Java Query DSL class).
return new JPAQueryFactory(manager).
selectFrom(QTable.table).
setLockMode(LockModeType.OPTIMISTIC).
orderBy(QTable.table.point.asc()).
fetchFirst();
Try:
select id, game, min(point) from t
group by id