Find max of multiple columns (row-wise) and columns' names where max. values occurred (T-SQL) - sql-server

I am fairly new to T-SQL and advanced concepts/queries and I have difficulty trying to figure out the situation described further. Lets say, I have a table like this:
--------------------
| K | C1 | C2 | C3 |
--------------------
| 1 | 14 | 24 | 48 |
| 2 | 10 | 65 | 37 |
| 3 | 44 | 90 | 22 |
--------------------
I need to find maximums row-wise, which has already been asked many times on SO, AND what makes difference, in addition, I also want the columns' names where found maximums occurred. The result has to be like this:
-- how to get Col column?
-----------------
| K | Max | Col |
| 1 | 48 | C3 |
| 2 | 65 | C2 |
| 3 | 90 | C2 |
-----------------
I learned how to find maximum rows-wise based on this and this threads, but I cannot understand how I could add another column containing columns' names where max. values occurred. I know I kinda have to show efforts but actually I cannot show anything since I just don't quite understand how to do it and all my attempts just return errors. Any ideas, hints? Thanks in advance!

The simplest way in SQL Server is to use apply:
select t.k, v.col, v.val
from t cross apply
(select top 1 v.col, v.val
from (values ('C1', t.C1), ('C2', t.C2), ('C3', t.C3)
) v(col, val)
order by val desc
) v(col, val)

I would approach this problem by joining a temporary maximum table back to the original and then writing a long "case when", assuming you have a reasonable number of columns. It would look like the following.
Select a.K
, b.Max_Value
, Case When a.Column_1 = b.Max_Value Then C1
When a.Column_2 = b.Max_Value Then C2
When a.Column_3 = b.Max_Value Then C3
... As Col
From Your_Table as a Left Join Max_Table as b on a.K = b.K
I believe this should get you what you want if I understood the question correctly. If you have a lot of columns where this would get too long then there's probably a better approach.

Related

Excel: Sum up values of a table based on a condition or grouping from another table

I could not find exactly this problem, though it seems to be similar to this one: Conditional SUM using multiple tables in EXCEL except the fact that the rows will not always have the same sequence in the tables.
My results table (RES) shall be populated based on the Groups (A, B, C) of the Group Table and the values of Value Table.
I am grouping several Products into Groups A, B, C according to the
Group Table:
Products | Group
------------------------
P1 | A
P2 | A
P3 | B
P4 | B
P5 | A
P6 | C
For several periods I have the values that these products generate in different tables. These tables have the same column structure, for example:
Value Table Period 1 # Value Table Period 2
Products | Value # Products | Value
------------------------ # ------------------------
P3 | 40 # P2 | 60
P5 | 10 # P5 | 20
P2 | 60 # P1 | 10
P6 | 50 # P3 | 30
P1 | 20 # P6 | 40
P4 | 30 # P4 | 50
These values I am trying to consolidate into a Results table, which would sum up the different products according to their groups within the respective periods, as follows:
Result | Period 1 | Period 2
-----------------------------------
A | 90 | 90
B | 70 | 80
C | 50 | 40
I tried to apply the array concept of the other post, but I was not successful in adapting it to my tables, which differ in some structural aspects. I´d be happy if I could avoid the use of VBA.
Thanks a lot in advance!
after some additional efforts I found the right array formula, which should be put into the B2 cell of the Result table:
=SUM(SUMIF(Period1!$A$2:$A$7;IF(Result!$I$17:$I$22=Result!$A2;Result!$H$17:$H$22);Period1!$B$2:$B$7))
confirm with CTRL+SHIFT+ENTER and copy down and across.
I hope that this helps you, too ;-)
Here's another way...
=SUM(IF(ISNUMBER(MATCH(Period1!$A$2:$A$7,IF(Result!$I$17:$I$22=Result!$A2,Result!$H$17:$H$22),0)),Period1!$B$2:$B$7))
Using the semi-colon as a separator...
=SUM(IF(ISNUMBER(MATCH(Period1!$A$2:$A$7;IF(Result!$I$17:$I$22=Result!$A2;Result!$H$17:$H$22);0));Period1!$B$2:$B$7))
Note that the formula needs to be confirmed with CONTROL+SHIFT+ENTER.

SSRS row group shows duplicate values for certain column group

I have a table where I am using SSRS matrix to display the information in row groups and column groups. For simplicity, I use the following table as an example.
+-----------+------+-------+-------+
| Date | Name | Test | Score |
+-----------+------+-------+-------+
| 9/11/2016 | John | Test1 | 91 |
| 9/11/2016 | John | Test2 | 78 |
| 9/11/2016 | John | Test3 | 84 |
| 9/11/2016 | John | Test4 | 62 |
| 9/11/2016 | Adam | Test1 | 88 |
| 9/11/2016 | Adam | Test2 | 74 |
| 9/11/2016 | Adam | Test3 | 92 |
+-----------+------+-------+-------+
I created a SSRS report of matrix using Name as column group and Test as row group and I get the following using the sample data above.
+-------+------+------+
| | John | Adam |
+-------+------+------+
| Test1 | 91 | 88 |
| Test2 | 78 | 74 |
| Test3 | 84 | 92 |
| Test4 | 62 | 88 |
+-------+------+------+
Because Adam did not take Test 4, I wanted to show it as a blank or - to indicate no such value. However, what I end up having is that the row for Test4 for Adam is a repeat of the value of Test1 for Adam, which is 88.
If Adam missed multiple tests, then the same value will be repeated for Adam. I've searched online and inside SSRS but I could not find an option to specify show null if a column group doesn't have any value for some row group.
I created this report by using the Wizard where when asked to choose between Table and Matrix, I chose Matrix. After that, I selected Name to be the column group and Test to be the row group and the Score to be the Detail group.
Your help is greatly appreciated!
I tried to create the matrix again after Hannover Fist mentioned it worked with the report wizard. It turned out that the problem was with the Expression for the Value in the Text Box Properties of the cell.
By default the Expression is something like [SUM(Score)] but after I created my report, I thought I should not need the summation because the value is distinct, which resulted in having [Score] instead.
Once I added the SUM back to my report, it started working as expected.
I've had this issue before a couple of years ago.
I think I searched for a solution but never found one. IIRC, I just created a new matrix which worked correctly. The wizard might have set some property that
Usually these kind of undiscoverable issues are in charts not tables.
I made a matrix with the report wizard using your data and it worked correctly.

Using Data from One Table in Another Table in Access

Hallo StackOverflow Users
I am struggling with transferring values between Access database tables which I will use in a Delphi program to tally election votes and determine the winning candidates. I have a total of six tables. One is my overall table, tblCandidates which identifies each candidate and contains the amount of votes they received from each party, namely the Grade Heads, the Teachers and the Learners. When it comes to the Learners we have four participating grades, namely the grade 8’s, 9’s, 10’s and 11’s, and each grade again has multiple participating classes, namely class A, B, C, etc.
Now, I have set up tables for each grade that contains all the classes in that grade. I named these tables tblGrX with X being the grade represented by 8 through 11. Each one of these tables has two extra fields, namely a field to identify a candidate and a field that will add up all the votes that candidate received from each of the classes in that grade. Lastly I have another table, tblGrTotals with fields Total_GrX (once again with X being the grade), that will contain all the total votes a candidate received from each grade, adding them up in another field for my tblCandidates table to use in its Total_Learners field.
So in short, I want, for example, tblGrTotals to use the value in the field Total of tblGr8 in its Total_Gr8 field, and then tblCandidates to use the value in field Total of tblGrTotals in its Total_Learners field. Is there any way to keep these values updated between tables like cells are updated in Excel the moment a change is made?
Thank you in advance!
You need to rethink your table design. I guess your background is Excel, and your tables are laid out like you would do in Excel sheets, but a relational database works differently.
Think about the objects you are modelling.
Candidates - that's easy. ID, Name, perhaps additional info that belongs to each candidate. But nothing about votes here.
"Groups that are voting" or Parties. Not so trivial, due to the different types of parties. Still I would put them in one table, with Grade and Class only set for Learners, NULL for Heads and Teachers.
e.g.
+----------+------------+-------+-------+
| Party_ID | Party_Type | Grade | Class |
+----------+------------+-------+-------+
| 1 | Head | | |
| 2 | Teacher | | |
| 3 | Learner | 8 | A |
| 4 | Learner | 8 | B |
| 5 | Learner | 8 | C |
| 6 | Learner | 9 | A |
| 7 | Learner | 9 | B |
| 8 | Learner | 10 | A |
+----------+------------+-------+-------+
Votes: they are a Junction Table between Candidates and Parties.
e.g.
+----------+--------------+-----------+
| Party_ID | Candidate_ID | Num_Votes |
+----------+--------------+-----------+
| 1 | 1 | 5 |
| 1 | 2 | 17 |
| 3 | 1 | 2 |
| 3 | 2 | 6 |
| 3 | 3 | 10 |
+----------+--------------+-----------+
Now: if you want to know the votes of Class 8A:
SELECT Candidate_ID, SUM(Num_Votes)
FROM Parties p INNER JOIN Votes v
ON p.Party_ID = v.Party_ID
WHERE p.Party_Type = 'Learner'
AND p.Grade = 8
AND p.Class = 'A'
GROUP BY Candidate_ID
Or of all Grade 8? Simply omit the p.Class criteria.
For the votes per candidate you join Candidates with Votes.
Edit:
for the votes counting differently, this is an attribute of Party_Type.
We don't have a table for them yet, so create one:
+------------+---------------+
| Party_Type | Multiplicator |
+------------+---------------+
| Head | 4 |
| Teacher | 3 |
| Learner | 1 |
+------------+---------------+
and to count all votes:
SELECT c.Candidate_ID, c.Candidate_Name, SUM(v.Num_Votes * t.Multiplicator) AS SumVotes
FROM Parties p
INNER JOIN Votes v ON p.Party_ID = v.Party_ID
INNER JOIN Party_Types t ON p.Party_Type = t.Party_Type
INNER JOIN Candidates c ON v.Candidate_ID = c.Candidate_ID
GROUP BY c.Candidate_ID, c.Candidate_Name
With a design like this, you don't need to keep updating data from one table into another - you calculate it when and how you need it, and it's always current.
The magic of databases. :)

SQL query/functions to flatten a multiple table item and hierarchical item links

I have the data structure below, storing items and links between them in parent-child relashionship.
I need to display the result as show below, one line by parent, with all children.
Values are the ItemCodes by item type, for ex. C-1 and C-2 are the 2 first items of type C, and so on.
In a previous application version, there were only one C and one H maximum for each P.
So I did a max() and group by mix and the result was there.
But now, parents may be linked to different types and number of children.
I tried several techniques including adding temporary tables, views, use of PIVOT, ROLLUP, CUBE, stored procedures and cursors (!), but nothing worked for this specific problem.
I finally succeeded to adapt the query. However, there are many select from (select ...) clauses, as well as row_number based queries.
Also, the result is not dynamic, meaning the number of columns is fixed (which is acceptable).
My question is: what would be your approach for such issue (if possible in a single query)? Thank you!
The table structure:
Item
-------------------------------
ItemId | ItemCode | ItemType
-------------------------------
1 | P1 | P
2 | C11 | C
3 | H11 | H
4 | H12 | H
5 | P2 | P
6 | C21 | C
7 | C22 | C
8 | C23 | C
9 | H21 | H
ItemLink
---------------------------------------
LinkId | ParentItemId | ChildItemId
---------------------------------------
1 | 1 | 2
2 | 1 | 3
3 | 1 | 4
4 | 2 | 6
5 | 2 | 7
6 | 2 | 8
7 | 2 | 9
Expcted Result
-----------------------------------------------------
P C-1 C-2 ... C-N H1 H2 ... H-N
-----------------------------------------------------
P1 C11 NULL NULL NULL H11 H12 NULL NULL
P2 C21 C22 C23 NULL H21 NULL NULL NULL
...
Part of my current query (which is working):
!http://s12.postimg.org/r64tgjjnh/SOQuestion.png

Fill sequence in sql rows

I have a table that stores a group of attributes and keeps them ordered in a sequence. The chance exists that one of the attributes (rows) could be deleted from the table, and the sequence of positions should be compacted.
For instance, if I originally have these set of values:
+----+--------+-----+
| id | name | pos |
+----+--------+-----+
| 1 | one | 1 |
| 2 | two | 2 |
| 3 | three | 3 |
| 4 | four | 4 |
+----+--------+-----+
And the second row was deleted, the position of all subsequent rows should be updated to close the gaps. The result should be this:
+----+--------+-----+
| id | name | pos |
+----+--------+-----+
| 1 | one | 1 |
| 3 | three | 2 |
| 4 | four | 3 |
+----+--------+-----+
Is there a way to do this update in a single query? How could I do this?
PS: I'd appreciate examples for both SQLServer and Oracle, since the system is supposed to support both engines. Thanks!
UPDATE: The reason for this is that users are allowed to modify the positions at will, as well as adding or deleting new rows. Positions are shown to the user, and for that reason, these should show a consistence sequence at all times (and this sequence must be stored, and not generated on demand).
Not sure it works, But with Oracle I would try the following:
update my_table set pos = rownum;
this would work but may be suboptimal for large datasets:
SQL> UPDATE my_table t
2 SET pos = (SELECT COUNT(*) FROM my_table WHERE id <= t.id);
3 rows updated
SQL> select * from my_table;
ID NAME POS
---------- ---------- ----------
1 one 1
3 three 2
4 four 3
Do you really need the sequence values to be contiguous, or do you just need to be able to display the contiguous values? The easiest way to do this is to let the actual sequence become sparse and calculate the rank based on the order:
select id,
name,
dense_rank() over (order by pos) as pos,
pos as sparse_pos
from my_table
(note: this is an Oracle-specific query)
If you make the position sparse in the first place, this would even make re-ordering easier, since you could make each new position halfway between the two existing ones. For instance, if you had a table like this:
+----+--------+-----+
| id | name | pos |
+----+--------+-----+
| 1 | one | 100 |
| 2 | two | 200 |
| 3 | three | 300 |
| 4 | four | 400 |
+----+--------+-----+
When it becomes time to move ID 4 into position 2, you'd just change the position to 150.
Further explanation:
Using the above example, the user initially sees the following (because you're masking the position):
+----+--------+-----+
| id | name | pos |
+----+--------+-----+
| 1 | one | 1 |
| 2 | two | 2 |
| 3 | three | 3 |
| 4 | four | 4 |
+----+--------+-----+
When the user, through your interface, indicates that the record in position 4 needs to be moved to position 2, you update the position of ID 4 to 150, then re-run your query. The user sees this:
+----+--------+-----+
| id | name | pos |
+----+--------+-----+
| 1 | one | 1 |
| 4 | four | 2 |
| 2 | two | 3 |
| 3 | three | 4 |
+----+--------+-----+
The only reason this wouldn't work is if the user is editing the data directly in the database. Though, even in that case, I'd be inclined to use this kind of solution, via views and instead-of triggers.

Resources