Sum values from multiple tables grouping by a common column - sql-server

I have three tables in MS SQL Server 2014. Each of them holds a couple of numeric values, a description and a date. For the sake of brevety, let's assume the following tables:
table "beverages"
day beverage amount
---------- -------- ------
2018-12-01 water 2
2018-12-01 tea 1
2018-12-01 coffee 7
2018-12-02 water 4
2018-12-02 tea 2
table "meals"
day meal amount
---------- ------ ------
2018-12-01 burger 1
2018-12-01 bread 2
2018-12-02 steak 1
table "fruit"
day fruit amount
---------- ------ ------
2018-12-01 apple 4
2018-12-01 banana 1
2018-12-02 apple 2
Then I have another table holding only a list of dates.
table "dates"
day
----------
2018-12-01
2018-12-02
What I need is a query that returns one row for each of the rows in the dates table, and in each row has the date, the total amount of beverages, the total amount of meals and the total amount of fruit for that day. I do not care for the different types of beverages, meals and fruit, just the sum. The result should be:
expected result
day beverages meals fruit
---------- ----------- ----------- -----------
2018-12-01 10 3 5
2018-12-02 6 1 2
But instead I receive
received result
day beverages meals fruit
---------- ----------- ----------- -----------
2018-12-01 40 18 30
2018-12-02 6 2 4
I already know what the problem is, just not how to fix it. Even worse, I'm sure that I knew the answer once, but now I can't even figure the right search terms to make Google tell me...
When I do the query like this (I used table variables for testing)
SELECT
[d].[day]
,SUM([b].[amount]) AS [beverages]
,SUM([m].[amount]) AS [meals]
,SUM([f].[amount]) AS [fruit]
FROM #dates AS [d]
LEFT OUTER JOIN #beverages AS [b]
ON [d].[day] = [b].[day]
LEFT OUTER JOIN #meals AS [m]
ON [d].[day] = [m].[day]
LEFT OUTER JOIN #fruit AS [f]
ON [d].[day] = [f].[day]
GROUP BY [d].[day]
it sums each row from the different tables more than once, because it returns every possible combination of the three tables. Removing the SUM() and GROUP BY proves that:
day beverages meals fruit
---------- ----------- ----------- -----------
2018-12-01 2 1 4
2018-12-01 2 1 1
2018-12-01 2 2 4
2018-12-01 2 2 1
2018-12-01 1 1 4
2018-12-01 1 1 1
2018-12-01 1 2 4
2018-12-01 1 2 1
2018-12-01 7 1 4
2018-12-01 7 1 1
2018-12-01 7 2 4
2018-12-01 7 2 1
2018-12-02 4 1 2
2018-12-02 2 1 2
So, what do I need to change in the query to make it sum the values for each of the three tables without multiplying it with the number of the rows in the other tables?

Group the Tables before joining like so:
SELECT
[d].[day]
,[b].[amount] AS [beverages]
,[m].[amount] AS [meals]
,[f].[amount] AS [fruit]
FROM #dates AS [d]
LEFT OUTER JOIN (SELECT day, SUM(amount) as amount FROM #beverages GROUP BY day) AS [b]
ON [d].[day] = [b].[day]
LEFT OUTER JOIN (SELECT day, SUM(amount) as amount FROM #meals GROUP BY day) AS [m]
ON [d].[day] = [m].[day]
LEFT OUTER JOIN (SELECT day, SUM(amount) as amount FROM #fruit GROUP BY day) AS [f]
ON [d].[day] = [f].[day]

How about a PIVOT instead?
Example
Select *
From (
Select day,Item='beverage',amount from beverages
Union All
Select day,Item='meals' ,amount from meals
Union All
Select day,Item='fruit' ,amount from fruit
) src
Pivot ( sum(amount) for Item in ([beverages],[meals],[fruit]) ) pvt

Related

DAX - Divide a column over itself with different filters to get percentages

In power Pivot I have tables along the lines of:
Table 1
Year
Month
Branch_ID
Store_ID
Article
Value
2022
10
1
1
Sales
100
2022
10
1
2
Sales
200
2022
10
1
2
Operating expenses
50
2022
10
1
1
Operating expenses
80
2022
10
1
2
Cost of Sales
20
2022
10
1
1
Cost of Sales
30
Table 2
Year
Month
Branch_ID
Store_ID
Article
Value
2022
10
1
1
Sales_Ecomm
20
2022
10
1
2
Sales_Ecomm
15
Table 3
| Article |
|--------------------|
| Sales |
| Operating expenses |
| Cost of Sales |
| Sales_Ecomm |
There are multiple branches and months, so these columns may not be ignored.
Table 1 and table 2 are separate. Table 3 is connected to both so that I could build a pivot table.
In the pivot table I want to have all articles re-evaluated as percentage of Sales, i.e. I am trying to get a pivot table along the lines of:
Store ID
Sales
Operating expenses
Cost of Sales
Sales_Ecomm
Value
% of sales
Value
% of sales
Value
% of sales
Value
% of sales
1
100
100.00%
80
80.00%
30
30.00%
20
20.00%
2
200
100.00%
50
25.00%
20
10.00%
15
7.50%
I have a measure
Val. := sum(table1[Value]) + sum(table2[value])
which seems to be working for absolute values of the articles.
However, I can't seem to come up with an appropriate DAX measure for percentages. I have tried:
%_of_Sales := [Val.] / calculate([Val.], filter(table3; table3[Article]="Sales"))
but it only counts Sales as percentage of Sales (100%), yielding #NUM! for other articles in the pivot table.
How do I define a ratio measure so that every article is evaluated against Sales?
You're missing a crucial ALL:
=
DIVIDE(
[Val.],
CALCULATE(
[Val.],
FILTER(
ALL( table3 ),
table3[Article] = "Sales"
)
)
)
which is equivalent to:
=
DIVIDE(
[Val.],
CALCULATE(
[Val.],
table3[Article] = "Sales"
)
)

Sql-Get time ranges from million+ rows for particular condition

I am working with SQL Server 2012, I have a table with approx 35 column and 10+ million rows.
I need to find time ranges from across the data where the value of any particular column is matching
E.g.
The sample data is as below
Datetime col1 col2 col3
2018-05-31 0:00 1 2 1
2018-05-31 13:00 2 2 2
2018-05-31 14:30 3 2 1
2018-05-31 15:00 4 3 1
2018-05-31 16:00 4 5 1
2018-05-31 17:00 3 2 2
2018-05-31 17:30 3 2 4
2018-05-31 18:00 2 2 4
2018-05-31 20:00 1 2 6
2018-05-31 21:00 2 2 3
2018-05-31 21:10 2 2 1
2018-05-31 22:00 1 6 3
2018-05-31 22:00 4 5 1
2018-05-31 23:59 4 7 2
Find the time range from data where col2 value =< 2, accordingly my expected result set is as below
Start Time End time Time Diff
2018-05-31 0:00 2018-05-31 14:30 14:30:00
2018-05-31 17:00 2018-05-31 21:10 4:10:00
I can achieved the same with below logic, but it's extremely slow
I get all rows and then
Order by date_Time
Scan the rows get the first row where exactly value is matching and record that timestamp as start time.
Scan further rows till i get the row where condition is breaking and record that timestamp as end time.
But as i have to play with huge no. Of rows, overall this will make my operation slow, any inputs or pseudo code to improve the same.
We can use a slightly modified difference in row number method here. The purpose of the first CTE labelled cte1 is to add a computed column which labels islands we want, having a col2 values <= 2, as 1 and everything else as 0. Then, we can compute the difference of two row numbers, and aggregate over the islands to find the starting and ending times, and the difference between those times.
WITH cte1 AS (
SELECT *,
CASE WHEN col2 <= 2 THEN 1 ELSE 0 END AS class
FROM yourTable
),
cte2 AS (
SELECT *,
ROW_NUMBER() OVER (ORDER BY Datetime) -
ROW_NUMBER() OVER (PARTITION BY class ORDER BY Datetime) rn
FROM cte1
)
SELECT
MIN(Datetime) AS [Start Time],
MAX(Datetime) AS [End Time],
CONVERT(TIME, MAX(Datetime) - MIN(Datetime)) AS [Time Diff]
FROM cte2
WHERE class = 1
GROUP BY rn
ORDER BY MIN(Datetime);
Demo

SQL Server, finding the SUM of a foreign key column

I currently have 3 tables.
Table 1: customers
id(PK) name surname
----------------------------------
1 name1 surname1
2 name2 surname2
3 name3 surname3
4 name4 surname4
Table 2: sales
id(FK) game(FK) price(FK)
-----------------------------
1 1 1
2 4 4
3 4 4
4 3 3
1 3 3
2 3 3
3 2 2
Table 3: stock
id(FK) game price
-----------------------------
1 game1 20
2 game2 30
3 game3 40
4 game4 50
What I'm looking to do is find the sum of all the sales listed in the sales table (table 2).
So far, I can display a table showing how much money each game has made in total but cannot get the overall total of sales to display.
I have tried
select sum(sales.price)
from sold
However, this is just calculating the sum of the foreign key (in this case it would be 20). However, I want it to display 270.
You need to join the stock and sales tables to get the correct price of each of the items sold.
Select sum(stock.price) from sales
inner join stock on sales.game = stock.id

Query that shows data depending of 2 columns

I'm working on a Report in Visual Basic. It has a query to show the information below.
So, I need a query that sets all the rows that are between 2 values that the user choose. The problem is that the value exists several times.
To explain better:
ID Category_ID SubCategory_ID Description Period_ID Data
----------- ----------- -------------- ------------- ----------- --------
1 1 1 PRUEBA 1 100.00
2 2 5 Total 1 2.00
3 1 1 sgsdg 2 25.00
4 1 1 fsdf 2 5.00
5 1 1 sdf 2 54.00
There will be a lot more of Period_ID. So, if the user chooses Period 1 and Period 5, it will show all the data between Period 1 and Period 5 (i.e. Period1, Period2, Period3, Period4 and Period5).
Is there a query which can do this?
I appreciate your help!
I would do:
Select *
FROM SAMPLE_TABLE
WHERE SAMPLE_TABLE.PERIOD_ID
IN (SELECT ID FROM PERIOD
WHERE ID >= 1 AND ID <= 5)
This is called a subquery. This relies on you have a period table, which I assume you have

Zone wise and line item wise display of amedments using SQL Server

I have a table in which i have columns like SO number, line item number, amendment number and zone.
I need to display the total number of amendments zone wise.
I have 6 zones - a,b,c,d,e,f
so numbers like 1,2 for zone a; 3,4 for zone b; similarly for other zones.
in each so number there might be 1 or more than one line item numbers like 10,20,30 etc.
for each so number there will be just one amendment (i.e., even if an so number has many line item numbers, it will have just one amendment)
now, say in zone a, there are 4 so numbers 1,2,3,4. and in so number 1 there is one line item number 10 and its amendment number is 2. say for so number 2 there are 4 line item numbers 10,20,30,40 and the amendment number is 0. so for this zone a, total number of amendments would be 2 (desired result). how do i do it?
Zone so_number line_item_number amendment_number
a---------- 1 ---------------- 10 ---------------- 2
a---------- 2 ---------------- 10 ---------------- 0
a---------- 2 ---------------- 20 ---------------- 0
a---------- 2 ---------------- 30 ---------------- 0
a ---------- 2 ---------------- 40 ---------------- 0
a ---------- 3 ---------------- 10---------------- 1
a ---------- 4 ---------------- 10 ---------------- 3
a----------- 4 ----------------- 20 ---------------- 3
b ----------- 6 ---------------- 60 ----------------- 6
c------------ 7 ---------------- 80 ----------------- 0
c ---------- 8 ------------------ 10 ----------------- 0
d ------------ 9 ----------------- 10 ----------------- 2
e ------------ 10----------------- 20 ----------------- 3
f ------------ 11----------------- 30 ----------------- 1
f ------------- 11 --------------- 10 ------------------ 1
f ------------- 11 --------------- 20 ------------------- 1
f ------------ 12 ---------------- 10 ------------------ 2
For zone a total number of amendments would be 6, for zone b it would be 6, for c - 0, for d - 2, e - 3, f - 3
a zone may have any number of so numbers, an so number may have any number of line item numbers, but, an so number may have only one amendment number
How do I do it?
i really don't understand your question and your result.
It could seems very simple from my understands
select zone,
sum(amendment_number)
from tablea
group by zone
now my result would be : 9-6-0-2-3-5
Do you only want the last so_number?
select zone,
sumamend
from
(select zone,
sum(amendment_number) as sumamend,
row_number over (partition by zone order by so_number desc) as ord
from tablea
group by zone, so_number) b
where ord = 1
But again i don't have the same result : 6-6-0-2-3-2
if i correctly understand your comment and hoping you have all case in your example you want something like this :
select zone,
sum(amendment_number)
from (select distinct zone,so_number, amendment_number from #test) p
group by zone
it gives the following result : 6-6-0-2-3-3

Resources