Create a sub query for sum data as a new column in SQL Server - sql

Suppose that I have a table name as tblTemp which has data as below:
| ID | AMOUNT |
----------------
| 1 | 10 |
| 1-1 | 20 |
| 1-2 | 30 |
| 1-3 | 40 |
| 2 | 50 |
| 3 | 60 |
| 4 | 70 |
| 4-1 | 80 |
| 5 | 90 |
| 6 | 100 |
ID will be format as X (without dash) if it's only one ID or (X-Y) format if new ID (Y) is child of (X).
I want to add a new column (Total Amount) to output as below:
| ID | AMOUNT | Total Amount |
---------------------------------
| 1 | 10 | 100 |
| 1-1 | 20 | 100 |
| 1-2 | 30 | 100 |
| 1-3 | 40 | 100 |
| 2 | 50 | 50 |
| 3 | 60 | 60 |
| 4 | 70 | 150 |
| 4-1 | 80 | 150 |
| 5 | 90 | 90 |
| 6 | 100 | 100 |
The "Total Amount" column is the calculate column which sum value in Amount column that the (X) in ID column is the same.
In order to get parent ID (X), I use the following SQL:
SELECT
ID, SUBSTRING (ID, 1,
IIF (CHARINDEX('-', ID) = 0,
len(ID),
CHARINDEX('-', ID) - 1)
), Amount
FROM
tblTemp
How Can I query like this in SQL Server 2012?
You can use sqlfiddle here to test it.
Thank You
Pengan

You have already done most of the work. To get the final result you can use your existing query and make it a subquery or use a CTE, then use sum() over() to get the result:
;with cte as
(
SELECT
ID,
SUBSTRING (ID, 1,
IIF (CHARINDEX('-', ID) = 0,
len(ID),
CHARINDEX('-', ID) - 1)
) id_val, Amount
FROM tblTemp
)
select id, amount, sum(amount) over(partition by id_val) total
from cte
See SQL Fiddle with Demo

You can do this using the sum() window function:
select id, amount,
SUM(amount) over (partition by (case when id like '%-%'
then left (id, charindex('-', id) - 1)
else id
end)
) as TotalAmount
from tblTemp t

Related

Combine PARTITION BY and GROUP BY

I have a (mssql) table like this:
+----+----------+---------+--------+--------+
| id | username | date | scoreA | scoreB |
+----+----------+---------+--------+--------+
| 1 | jim | 01/2020 | 100 | 0 |
| 2 | max | 01/2020 | 0 | 200 |
| 3 | jim | 01/2020 | 0 | 150 |
| 4 | max | 02/2020 | 150 | 0 |
| 5 | jim | 02/2020 | 0 | 300 |
| 6 | lee | 02/2020 | 100 | 0 |
| 7 | max | 02/2020 | 0 | 200 |
+----+----------+---------+--------+--------+
What I need is to get the best "combined" score per date. (With "combined" score I mean the best scores per user and per date summarized)
The result should look like this:
+----------+---------+--------------------------------------------+
| username | date | combined_score (max(scoreA) + max(scoreB)) |
+----------+---------+--------------------------------------------+
| jim | 01/2020 | 250 |
| max | 02/2020 | 350 |
+----------+---------+--------------------------------------------+
I came this far:
I can group the scores by user like this:
SELECT
username, (max(scoreA) + max(scoreB)) AS combined_score,
FROM score_table
GROUP BY username
ORDER BY combined_score DESC
And I can get the best score per date with PARTITION BY like this:
SELECT *
FROM
(SELECT t.*, row_number() OVER (PARTITION BY date ORDER BY scoreA DESC) rn
FROM score_table t) as tmp
WHERE tmp.rn = 1
ORDER BY date
Is there a proper way to combine these statements and get the result I need? Thank you!
Btw. Don't care about possible ties!
You can combine window functions and aggregation functions like this:
SELECT s.*
FROM (SELECT username, date, (max(scoreA) + max(scoreB)) AS combined_score,
ROW_NUMBER() OVER (PARTITION BY date ORDER BY max(scoreA) + max(scoreB) DESC) as seqnum
FROM score_table
GROUP BY username, date
) s
ORDER BY combined_score DESC;
Note that date needs to be part of the aggregation.

Get range (min - max) of values concatenated in a single row

given the following table
+-----------------------------+
| id | type | price | item_id |
|-----------------------------|
| 1 | 1 | 20 | 22 |
|-----------------------------|
| 2 | 1 | 22 | 22 |
|-----------------------------|
| 3 | 2 | 19 | 22 |
|-----------------------------|
| 4 | 2 | 11 | 22 |
|-----------------------------|
| 5 | 1 | 08 | 22 |
|-----------------------------|
| 6 | 2 | 25 | 22 |
+-----------------------------+
I am trying to select the data to create a view as follows in a single row
+-------------------------------------+
| type1_range | type2_range | item_id |
|-------------------------------------|
| 08 - 22 | 11 - 25 | 22 |
+-------------------------------------+
type1_range and type2_range are the minimum and maximum price for each types.
I can get the data in couple of rows using
SELECT type, MAX (price) , MIN (price)
FROM table
where item_id=22 GROUP BY type;
+----------------------------+
| type | max | min | item_id |
|----------------------------|
| 1 | 22 | 08 | 22 |
|----------------------------|
| 2 | 25 | 11 | 22 |
+----------------------------+
But I am trying to concat the rows like this:
+-------------------------------------+
| type1_range | type2_range | item_id |
|-------------------------------------|
| 08 - 22 | 11 - 25 | 22 |
+-------------------------------------+
What would be sql required for this?
Something like this:
SELECT
CONCAT(
MIN(CASE WHEN type = 1 THEN price END),
' - ',
MAX(CASE WHEN type = 1 THEN price END)
) as type1range,
CONCAT(
MIN(CASE WHEN type = 2 THEN price END),
' - ',
MAX(CASE WHEN type = 2 THEN price END)
) as type2range.
item_id
FROM table
WHERE item_id = 22
GROUP BY item_id
You've tagged two different database systems (please avoid doing this) but I believe they do both support CONCAT() for string concatenation
If you want to omit the item_id from the select list (you already know it's item 22) you can remove the GROUP BY. Alternatively if you remove the WHERE and leave the group by you'll get a row for each item_id
To get more of an idea as to how it works, remove the concat and the min/max - you'll see that the case when causes the price to show up only if the type is 1 (in the type 1 range column) otherwise it's null. It's the. Trivial for the min and max to work on just type 1 or just type 2 data for each column. It's actually a form of pivot query if you want to read up on them more
A straight forward approach would be having type1_range and type2_range as two sub-queries and join with the distinct id's like shown below,
SELECT t.item_id,type1_range,type2_range
FROM (Select distinct item_id from table) t
LEFT join
(SELECT item_id,type, concat(MIN(price),'-' ,MAX(price) ) as type1_range
FROM table
where type=1
GROUP BY item_id,type)type1 on type1.item_id=t.item_id
LEFT join
(SELECT item_id,type, concat(MIN(price),'-' ,MAX(price) ) as type2_range
FROM table
where type=2
GROUP BY item_id,type)type2 on type2.item_id=t.item_id

Loop over one table, subselect another table and update values of first table with SQL/VBA

I have a source table that has a few different prices for each product (depending on the order quantity). Those prices are listed vertically, so each product could have more than one row to display its prices.
Example:
ID | Quantity | Price
--------------------------
001 | 5 | 100
001 | 15 | 90
001 | 50 | 80
002 | 10 | 20
002 | 20 | 15
002 | 30 | 10
002 | 40 | 5
The other table I have is the result table in which there is only one row for each product, but there are five columns that each could contain the quantity and price for each row of the source table.
Example:
ID | Quantity_1 | Price_1 | Quantity_2 | Price_2 | Quantity_3 | Price_3 | Quantity_4 | Price_4 | Quantity_5 | Price_5
---------------------------------------------------------------------------------------------------------------------------
001 | | | | | | | | | |
002 | | | | | | | | | |
Result:
ID | Quantity_1 | Price_1 | Quantity_2 | Price_2 | Quantity_3 | Price_3 | Quantity_4 | Price_4 | Quantity_5 | Price_5
---------------------------------------------------------------------------------------------------------------------------
001 | 5 | 100 | 15 | 90 | 50 | 80 | | | |
002 | 10 | 20 | 20 | 15 | 30 | 10 | 40 | 5 | |
Here is my Python/SQL solution for this (I'm fully aware that this could not work in any way, but this was the only way for me to show you my interpretation of a solution to this problem):
For Each result_ID In result_table.ID:
Subselect = (SELECT * FROM source_table WHERE source_table.ID = result_ID ORDER BY source_table.Quantity) # the Subselect should only contain rows where the IDs are the same
For n in Range(0, len(Subselect)): # n (index) should start from 0 to last row - 1
price_column_name = 'Price_' & (n + 1)
quantity_column_name = 'Quantity_' & (n + 1)
(UPDATE result_table
SET result_table.price_column_name = Subselect[n].Price, # this should be the price of the n-th row in Subselect
result_table.quantity_column_name = Subselect[n].Quantity # this should be the quantity of the n-th row in Subselect
WHERE result_table.ID = Subselect[n].ID)
I honestly have no idea how to do this with only SQL or VBA (those are the only languages I'd be able to use -> MS-Access).
This is a pain in MS Access. If you can enumerate the values, you can pivot them.
If we assume that price is unique (or quantity or both), then you can generate such a column:
select id,
max(iif(seqnum = 1, quantity, null)) as quantity_1,
max(iif(seqnum = 1, price, null)) as price_1,
. . .
from (select st.*,
(select count(*)
from source_table st2
where st2.id = st.id and st2.price >= st.price
) as seqnum
from source_table st
) st
group by id;
I should note that another solution would use data frames in Python. If you want to take that route, ask another question and tag it with the appropriate Python tags. This question is clearly a SQL question.

Select dynamic couples of lines in SQL (PostgreSQL)

My objective is to make dynamic group of lines (of product by TYPE & COLOR in fact)
I don't know if it's possible just with one select query.
But : I want to create group of lines (A PRODUCT is a TYPE and a COLOR) as per the number_per_group column and I want to do this grouping depending on the date order (Order By DATE)
A single product with a NB_PER_GROUP number 2 is exclude from the final result.
Table :
-----------------------------------------------
NUM | TYPE | COLOR | NB_PER_GROUP | DATE
-----------------------------------------------
0 | 1 | 1 | 2 | ...
1 | 1 | 1 | 2 |
2 | 1 | 2 | 2 |
3 | 1 | 2 | 2 |
4 | 1 | 1 | 2 |
5 | 1 | 1 | 2 |
6 | 4 | 1 | 3 |
7 | 1 | 1 | 2 |
8 | 4 | 1 | 3 |
9 | 4 | 1 | 3 |
10 | 5 | 1 | 2 |
Results :
------------------------
GROUP_NUMBER | NUM |
------------------------
0 | 0 |
0 | 1 |
~~~~~~~~~~~~~~~~~~~~~~~~
1 | 2 |
1 | 3 |
~~~~~~~~~~~~~~~~~~~~~~~~
2 | 4 |
2 | 5 |
~~~~~~~~~~~~~~~~~~~~~~~~
3 | 6 |
3 | 8 |
3 | 9 |
If you have another way to solve this problem, I will accept it.
What about something like this?
select max(gn.group_number) group_number, ip.num
from products ip
join (
select date, type, color, row_number() over (order by date) - 1 group_number
from (
select op.num, op.type, op.color, op.nb_per_group, op.date, (row_number() over (partition by op.type, op.color order by op.date) - 1) % nb_per_group group_order
from products op
) sq
where sq.group_order = 0
) gn
on ip.type = gn.type
and ip.color = gn.color
and ip.date >= gn.date
group by ip.num
order by group_number, ip.num
This may only work if your nb_per_group values are the same for each combination of type and color. It may also require unique dates, but that could probably be worked around if required.
The innermost subquery partitions the rows by type and color, orders them by date, then calculates the row numbers modulo nb_per_group; this forms a 0-based count for the group that resets to 0 each time nb_per_group is exceeded.
The next-level subquery finds all of the 0 values we mapped in the lower subquery and assigns group numbers to them.
Finally, the outermost query ties each row in the products table to a group number, calculated as the highest group number that split off before this product's date.

sql - select row from group based on multiple values

I have a table like:
| ID | Val |
+-------+-----+
| abc-1 | 10 |
| abc-2 | 30 |
| cde-1 | 10 |
| cde-2 | 10 |
| efg-1 | 20 |
| efg-2 | 11 |
and would like to get the result based on the substring(ID, 1, 3) and minimum value and ist must be only the first in case the Val has duplicates
| ID | Val |
+-------+-----+
| abc-1 | 10 |
| cde-1 | 10 |
| efg-2 | 11 |
the problem is that I am stuck, because I cannot use group by substring(id,1,3), ID since it will then have again 2 rows (each for abc-1 and abc-2)
WITH
sorted
AS
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY substring(id,1,3) ORDER BY val, id) AS sequence_id
FROM
yourTable
)
SELECT
*
FROM
sorted
WHERE
sequence_id = 1
SELECT SUBSTRING(id,1,3),MIN(val) FROM Table1 GROUP BY SUBSTRING(id,1,3);
You were grouping the columns using both SUBSTRING(id,1,3),id instead of just SUBSTRING(id,1,3). It works perfectly fine.Check the same example in this below link.
http://sqlfiddle.com/#!3/fd9fc/1