SQL Server SSRS Multiple lookup values - sql

I have a huge set of data. Some pf the data has multiple values, kinda looking like this:
Column 1 Column 2
A 1
A 10
A 1E
B 2F
B 1BH
C WBH
D 3X
D 2
D 1
D 10
D 11
I would like to select the unique values in Column 1 and display all relevant values of Column 2 in as string separated by comma (using SSRS). i.e.
Column 1 Column 2
A 01, 10, 1E
B 2F, 1BH
C WBH
D 02, 01, 10, 11
In addition, any value in Column 1 that is less than 10, I would like it to be preceded by a zero.
I know I can use SELECT DISTINCT to get all unique values of Column 1. But I am unsure how to go around Column 2?
With regards to having a zero preceding numbers less than 10, I can do this:
SELECT RIGHT('0' + convert(varchar(2), value()), 2)
I am unsure how to put it all together to get the result I want.
Thank you.

I think this is what you want.
DECLARE #Input TABLE
(
ProductID INT,
Price INT
)
INSERT INTO #Input VALUES (6,22), (6,35), (6,77), (6, 88), (6,55),(6,200),(7,6),(7,4),(8,5),(8,5)
;WITH CTE AS
(SELECT ProductID, MAX(Price) AS Max_Price, MIN(Price) AS Min_Price
FROM #Input
GROUP BY ProductID
)
SELECT ProductID, CASE WHEN Max_Price > Min_Price THEN CONVERT(VARCHAR(10), Min_Price) + ', ' + CONVERT(VARCHAR(10),Max_Price)
ELSE CONVERT(VARCHAR(10), Min_Price) END AS Price_Range
FROM CTE

Related

Remove duplicates from single field only in rollup query

I have a table of data for individual audits on inventory. Every audit has a location, an expected value, a variance value, and some other data that aren't really important here.
I am writing a query for Cognos 11 which summarizes a week of these audits. Currently, it rolls everything up into sums by location class. My problem is that there may be multiple audits for individual locations and while I want the variance field to sum the data from all audits regardless of whether it's the first count on that location, I only want the expected value for distinct locations (i.e. only SUM expected value where the location is distinct).
Below is a simplified version of the query. Is this even possible or will I have to write a separate query in Cognos and make it two reports that will have to be combined after the fact? As you can likely tell, I'm fairly new to SQL and Cognos.
SELECT COALESCE(CASE
WHEN location_class = 'A'
THEN 'Active'
WHEN location_class = 'C'
THEN 'Active'
WHEN location_class IN (
'R'
,'0'
)
THEN 'Reserve'
END, 'Grand Total') "Row Labels"
,SUM(NVL(expected_cost, 0)) "Sum of Expected Cost"
,SUM(NVL(variance_cost, 0)) "Sum of Variance Cost"
,SUM(ABS(NVL(variance_cost, 0))) "Sum of Absolute Cost"
,COUNT(DISTINCT location) "Count of Locations"
,(SUM(NVL(variance_cost, 0)) / SUM(NVL(expected_cost, 0))) "Variance"
FROM audit_table
WHERE audit_datetime <= #prompt('EndDate') # audit_datetime >= #prompt('StartDate') #
GROUP BY ROLLUP(CASE
WHEN location_class = 'A'
THEN 'Active'
WHEN location_class = 'C'
THEN 'Active'
WHEN location_class IN (
'R'
,'0'
)
THEN 'Reserve'
END)
ORDER BY 1 ASC
This is what I'm hoping to end up with:
Thanks for any help!
Have you tried taking a look at the OVER clause in SQL? It allows you to use windowed functions within a result set such that you can get aggregates based on specific conditions. This would probably help since you seem to trying to get a summation of data based on a different grouping within a larger grouping.
For example, let's say we have the below dataset:
group1 group2 val dateadded
----------- ----------- ----------- -----------------------
1 1 1 2020-11-18
1 1 1 2020-11-20
1 2 10 2020-11-18
1 2 10 2020-11-20
2 3 100 2020-11-18
2 3 100 2020-11-20
2 4 1000 2020-11-18
2 4 1000 2020-11-20
Using a single query we can return both the sums of "val" over "group1" as well as the summation of the first (based on datetime) "val" records in "group2":
declare #table table (group1 int, group2 int, val int, dateadded datetime)
insert into #table values (1, 1, 1, getdate())
insert into #table values (1, 1, 1, dateadd(day, 1, getdate()))
insert into #table values (1, 2, 10, getdate())
insert into #table values (1, 2, 10, dateadd(day, 1, getdate()))
insert into #table values (2, 3, 100, getdate())
insert into #table values (2, 3, 100, dateadd(day, 1, getdate()))
insert into #table values (2, 4, 1000, getdate())
insert into #table values (2, 4, 1000, dateadd(day, 1, getdate()))
select t.group1, sum(t.val) as group1_sum, group2_first_val_sum
from #table t
inner join
(
select group1, sum(group2_first_val) as group2_first_val_sum
from
(
select group1, val as group2_first_val, row_number() over (partition by group2 order by dateadded) as rownumber
from #table
) y
where rownumber = 1
group by group1
) x on t.group1 = x.group1
group by t.group1, x.group2_first_val_sum
This returns the below result set:
group1 group1_sum group2_first_val_sum
----------- ----------- --------------------
1 22 11
2 2200 1100
The most inner subquery in the joined table numbers the rows in the data set based on "group2", resulting in the records either having a "1" or a "2" in the "rownum" column since there's only 2 records in each "group2".
The next subquery takes that data and filters out any rows that are not the first (rownum = 1) and sums the "val" data.
The main query gets the sum of "val" in each "group1" from the main table and then joins on the subqueried table to get the "val" sum of only the first records in each "group2".
There are more efficient ways to write this such as moving the summation of the "group1" values to a subquery in the SELECT statement to get rid of one of the nested tabled subqueries, but I wanted to show how to do it without subqueries in the SELECT statement.
Have you tried to put the distinct at the bottom like this ?
(SUM(NVL(variance_cost,0)) / SUM(NVL(expected_cost,0))) "Variance",
COUNT(DISTINCT location) "Count of Locations"
FROM audit_table

T-SQL Select to compute a result row on preceeding group/condition

How to achieve this result using a T-SQL select query.
Given this sample table :
create table sample (a int, b int)
insert into sample values (999, 10)
insert into sample values (16, 11)
insert into sample values (10, 12)
insert into sample values (25, 13)
insert into sample values (999, 20)
insert into sample values (14, 12)
insert into sample values (90, 45)
insert into sample values (18, 34)
I'm trying to achieve this output:
a b result
----------- ----------- -----------
999 10 10
16 11 10
10 12 10
25 13 10
999 20 20
14 12 20
90 45 20
18 34 20
The rule is fairly simple: if column 'a' has the special value of 999 the result for that row and following rows (unless the value of 'a' is again 999) will be the value of column 'b'. Assume the first record will have 999 on column 'a'.
Any hint how to implement, if possible, the select query without using a stored procedure or function?
Thank you.
António
You can do what you want if you add a column to specify the ordering:
create table sample (
id int identity(1, 1),
a int,
b int
);
Then you can do what you want by finding the "999" version that is most recent and copying that value. Here is a method using window functions:
select a, b, max(case when a = 999 then b end) over (partition by id_999) as result
from (select s.*,
max(case when a = 999 then id end) over (order by id) as id_999
from sample s
) s;
You need to have an id column
select cn.id, cn.a
, (select top (1) b from sample where sample.id <= cn.id and a = 999 order by id desc)
from sample as cn
order by id

SQL Server Sum a specific number of rows based on another column

Here are the important columns in my table
ItemId RowID CalculatedNum
1 1 3
1 2 0
1 3 5
1 4 25
1 5 0
1 6 8
1 7 14
1 8 2
.....
The rowID increments to 141 before the ItemID increments to 2. This cycle repeats for about 122 million rows.
I need to SUM the CalculatedNum field in groups of 6. So sum 1-6, then 7-12, etc. I know I end up with an odd number at the end. I can discard the last three rows (numbers 139, 140 and 141). I need it to start the SUM cycle again when I get to the next ItemID.
I know I need to group by the ItemID but I am having trouble trying to figure out how to get SQL to SUM just 6 CalculatedNum's at a time. Everything else I have come across SUMs based on a column where the values are the same.
I did find something on Microsoft's site that used the ROW_NUMBER function but I couldn't quite make sense of it. Please let me know if this question is not clear.
Thank you
You need to group by (RowId - 1) / 6 and ItemId. Like this:
drop table if exists dbo.Items;
create table dbo.Items (
ItemId int
, RowId int
, CalculatedNum int
);
insert into dbo.Items (ItemId, RowId, CalculatedNum)
values (1, 1, 3), (1, 2, 0), (1, 3, 5), (1, 4, 25)
, (1, 5, 0), (1, 6, 8), (1, 7, 14), (1, 8, 2);
select
tt.ItemId
, sum(tt.CalculatedNum) as CalcSum
from (
select
*
, (t.RowId - 1) / 6 as Grp
from dbo.Items t
) tt
group by tt.ItemId, tt.Grp
You could use integer division and group by.
SELECT ItemId, (RowId-1)/6 as Batch, sum(CalculatedNum)
FROM your_table GROUP BY ItemId, Batch
To discard incomplete batches:
SELECT ItemId, (RowId-1)/6 as Batch, sum(CalculatedNum), count(*) as Cnt
FROM your_table GROUP BY ItemId, Batch HAVING Cnt = 6
EDIT: Fix an off by one error.
To ensure you're querying 6 rows at a time you can try to use the modulo function : https://technet.microsoft.com/fr-fr/library/ms173482(v=sql.110).aspx
Hope this can help.
Thanks everyone. This was really helpful.
Here is what we ended up with.
SELECT ItemID, MIN(RowID) AS StartingRow, SUM(CalculatedNum)
FROM dbo.table
GROUP BY ItemID, (RowID - 1) / 6
ORDER BY ItemID, StartingRow
I am not sure why it did not like the integer division in the select statement but I checked the results against a sample of the data and the math is correct.

T-SQL Combine Ranges Based On Value

I am using SQL Server 2012 and have been struggling with this query for hours. I am trying to aggregate mile post ranges based off the value in the Value column. The results should have unique segments with the highest value from the Value field for each segment. Here's an example:
Mile_Marker_Start | Mile_Marker_End | Value
0 100 5
50 150 6
100 200 10
75 300 9
150 200 7
And here's the result I'm looking for:
Mile_Marker_Start | Mile_Marker_End | Value
0 50 5
50 75 6
75 100 9
100 200 10
200 300 9
As you can see, the row with a value of 9 got split into 2 rows because Value 10 was bigger. Also, the row with Value 7 does not display because Value 10 was bigger. Can this be done without using a cursor? Any help would be much appreciated.
Thanks
I believe the following now does what you need. I'd recommend running all the parts separately so you can see what they do and how they work.
DECLARE #input AS TABLE
(Mile_Marker_Start int, Mile_Marker_End int, Value int)
INSERT INTO #input VALUES
(0,100,5), (50,150,6), (100,200,10), (75,300,9), (150,200,7)
DECLARE #staging as table
(Mile_Marker int)
INSERT INTO #staging
SELECT Mile_Marker_Start from #input
UNION -- this will remove duplicates
SELECT Mile_Marker_End from #input
; -- we need semi-colon for the following CTE
-- this CTE gets the right values, but the rows aren't "collapsed"
WITH all_markers AS
(
SELECT
groups.Mile_Marker_Start,
groups.Mile_Marker_End,
max(i3.Value) Value
FROM
(
SELECT
s1.Mile_Marker Mile_Marker_Start,
min(s2.Mile_Marker) Mile_Marker_End
FROM
#staging s1
JOIN #staging s2 ON
s1.Mile_Marker < s2.Mile_Marker
GROUP BY
s1.Mile_Marker
) as groups
JOIN #input i3 ON
i3.Mile_Marker_Start < groups.Mile_Marker_End AND
i3.Mile_Marker_End > groups.Mile_Marker_Start
GROUP BY
groups.Mile_Marker_Start,
groups.Mile_Marker_End
)
SELECT
MIN(collapse.Mile_Marker_Start) as Mile_Marker_Start,
MAX(collapse.Mile_Marker_End) as Mile_Marker_End,
collapse.Value
FROM
(-- Subquery get's IDs for the groups we're collapsing together
SELECT
am.*,
ROW_NUMBER() OVER (ORDER BY am.Mile_Marker_Start) - ROW_NUMBER() OVER (PARTITION BY am.Value ORDER BY am.Mile_Marker_Start) GroupID
FROM
all_markers am
) AS COLLAPSE
GROUP BY
collapse.GroupID,
collapse.Value
ORDER BY
MIN(collapse.Mile_Marker_Start)
Since you are on 2012 you could maybe use LEAD. Here is my code but as noted on your question by #stevelovell , we need clarification on how you are getting your result table.
--test date
declare #tablename TABLE
(
Mile_Marker_Start int,
Mile_Marker_End int,
Value int
);
insert into #tablename
values(0,100, 5),
(50,150, 6),
(100,200,10),
(75,300, 9),
(150,200, 7);
--query
select *
from #tablename
order by Mile_Marker_Start
select Mile_Marker_Start,
case when lead(mile_marker_start) over(order by mile_marker_start) < Mile_Marker_End THEN
lead(mile_marker_start) over(order by mile_marker_start)
ELSE
Mile_marker_end
END
AS MILE_MARKER_END,
Value
from #tablename
order by Mile_Marker_Start
Once you update your notes I will come back and update my answer.
Update: wasn't able to get LEAD and the other windowing functions to work with your requirements. With the way you need to move up and down the table current, and calculated values...

SQL Aggregation for a smaller result set

I have a database for which I need to aggregate records into another smaller set. This result set should contain the difference between maximum and minumum of specific columns of the original records where they add up to certain SUM, a closed interval constant C.
The constant C determines how the original records are aggregated and no entry in the resulting set ever exceeds it. Naturally I am supposed to run this in natural primary key order..
To illustrate: table has:
[key]
[a]
[b]
[minColumn]
[maxColumn]
[N]
...all are int datatype.
I am after a result set that has entries where the MAX(maxColumn) - MIN(minColumn) for that group such that when their difference is summed up it is less or equal to constant C.
Apart from the MAX(maxColumn) and MIN(minColumn) value I also need the FIRST record column [a] and LAST record column [b] values before creating a new entry in this result set. Finally, the N column should be SUMmed for all original records in a group.
Is there an efficient way to do this without cursors?
-----[Trivial Sample]------------------------------------------------------------
I am attempting to group-by a slightly complicated form of a running sum, constant C.
There is only one table, columns are all of int type and sample data
declare #t table (
PK int primary key
, int a, int b, int minColumn, int maxColumn, int N
)
insert #t values (1,5,6,100,200,1000)
insert #t values (2,7,8,210,300,2000)
insert #t values (3,9,10,420,600,3000)
insert #t values (4,11,12,640,800,4000)
Thus for:
key, a, b, minColumn, maxColumn, N
---------------------------------------
1, 5, 6, 100, 200, 1000
2, 7, 8, 210, 300, 2000
3, 9, 10, 420, 600, 3000
4, 11, 12, 640, 800, 4000
I need the result set to look like, for a constant C of 210 :
firstA | lastB | MIN_minColumn | MAX_maxColumn | SUM_N
5 8 100 300 3000
9 10 420 600 3000
11 12 640 800 4000
[ Adding the bounty and sample as discussed below]
For C = 381, It should contain 2 rows:
firstA | lastB | MIN_minColumn | MAX_maxColumn | SUM_N
5 8 100 300 3000
9 12 420 800 7000
Hope this demonstrates the problem better.. and for a constant C say 1000 you would get 1 record in the result:
firstA | lastB | MIN_minColumn | MAX_maxColumn | SUM_N
5 12 100 800 10000
DECLARE #c int
SELECT #c = 210
SELECT MIN(a) firstA,
MAX(b) lastB,
MIN(minColumn) MIN_minColumn,
MAX(maxColumn) MAX_maxColumn,
SUM(N) SUM_N
FROM #t t
JOIN (SELECT key, floor(sum/#c) as rank
FROM (SELECT key,
(SELECT SUM(t2.maxColumn - t2.minColumn)
FROM #t t2
WHERE t2.key <= t1.key
GROUP BY t1.key) as sum
FROM #t t1) A
) B on B.key = t.key
GROUP BY B.rank
/*
Table A: for each key, calculating SUM[maxColumn-minColumn] of all keys below it.
Table B: for each key, using the sum in A, calculating a rank so that:
sum = (rank + y)*#c where 0 <= y < 1.
ex: #c=210, rank(100) = 0, rank(200) = 0, rank(220) = 1, ...
finally grouping by rank, you'll have what you want.
*/
declare #c int
select #c = 210
select firstA = min(a), lastB = max(b), MIN_minColumn = min(minColumn), MAX_maxColumn = max(maxColumn), SUM_N = sum(N)
from #t
where minColumn <= #c
union all
select a, b, minColumn, maxColumn, N
from #t
where minColumn > #c
I am a little confused on the grouping logic for result you are trying to produce, but from the description of what you are looking for, I think you need a HAVING clause. You should be able to do something like:
SELECT groupingA, groupingB, MAX(a) - MIN(b)
FROM ...
GROUP BY groupingA, groupingB
HAVING (MAX(a) - MIN(b)) < C
...in order to filter out the difference between your max and min values, once you've determined your grouping. Hope this is helpful