SQL Server Comparison in same row [duplicate] - sql-server

This question already has answers here:
Function in SQL Server 2008 similar to GREATEST in mysql?
(5 answers)
Closed 5 years ago.
I use Microsoft SQL Server 2014.
I want to get max number in same row.
For example, this table is here
------------------------------------------------------------------------
| Values_A | Amounts_A | Values_B | Amounts_B | Values_C | Amounts_C|
------------------------------------------------------------------------
| 5000 | 50 | 3000 | 100 | 1000 | 200 |
------------------------------------------------------------------------
I want to get maximum Values/Amounts in A or B or C.
In this case, A's Values/Amounts is 5000/50 = 100, B's Values/Amounts is 3000/100 = 30, C's Values/Amounts is 1000/200 = 5, So I want to get 100.
please tell me.

You can do this concisely using values in a subquery:
select
(select
max(val)
from (values (Values_A/Amounts_A),
(Values_B/Amounts_B),
(Values_C/Amounts_C)
) t (val)
) max_val
from your_table;

SQL Server's MAX() function was designed to return the greatest number in a column from a collection of records, not columns. I think the biggest challenge in your problem is that you have your data spread out across columns. One approach is to unpivot this data using UNION, and then just select the maximum ratio.
WITH cte AS (
SELECT Values_A / Amounts_A AS ratio
FROM yourTable
UNION ALL
SELECT Values_B / Amounts_B
FROM yourTable
UNION ALL
SELECT Values_C / Amounts_C
FROM yourTable
)
SELECT MAX(ratio)
FROM cte
This approach would work fine for a single record. If you have multiple records in your table, then a more ugly approach is this:
WITH cte AS (
SELECT Values_A / Amounts_A AS A_ratio,
Values_B / Amounts_B AS B_ratio,
Values_C / Amounts_C AS C_ratio
FROM yourTable
)
SELECT CASE WHEN t.A_ratio > t.B_ratio
THEN CASE WHEN t.A_ratio > t.C_ratio THEN t.A_ratio ELSE t.C_ratio END
ELSE CASE WHEN t.B_ratio > t.C_ratio THEN t.B_ratio ELSE t.C_ratio END
END AS max_ratio
FROM cte t

Related

How to select random rows whose column sum meets the condition in Sql server

Is it possible to select random rows from a table whose particular column total (sum) should be less than my condition value ?
My table structure is like -
id | question | answerInSec
1 | Quest1 | 15
2 | Quest2 | 20
3 | Quest3 | 10
4 | Quest4 | 15
5 | Quest5 | 10
6 | Quest6 | 15
7 | Quest7 | 20
I want to get those random questions whose total sum of 'answerInSec' column is less than (nearest total) or equal to 60.
So random combination can be [1,2,3,4] OR [2,3,5,7] OR [4,5,6,7] etc.
I tried as follows but no luck
select id,question,answerinsec
from (select Question.*, sum(answerinsec) over (order by id) as CumTicketCount
from Question
) t
where cumTicketCount <= 60
ORDER BY NEWID();
I hope this one help
DECLARE #MaxAnswerInSec INT = 60
DECLARE #SumAnswerInSec INT = 0
DECLARE #RadomQuestionTable TABLE(Id INT, Question NVARCHAR(100), AnswerInSec INT)
DECLARE #tempId INT,
#tempQuestion NVARCHAR(100),
#tempAnswerInSec INT
WHILE #SumAnswerInSec <= #MaxAnswerInSec
BEGIN
SELECT TOP(1) #tempId = Id, #tempQuestion = Question, #tempAnswerInSec = AnswerInSec
FROM Question
WHERE Id NOT IN (SELECT Id FROM #RadomQuestionTable)
AND AnswerInSec + #SumAnswerInSec <= #MaxAnswerInSec
ORDER BY NEWID()
IF #tempId IS NOT NULL
BEGIN
INSERT INTO #RadomQuestionTable VALUES(#tempId, #tempQuestion, #tempAnswerInSec)
END
ELSE
BEGIN
BREAK
END
SELECT #tempId = NULL
SELECT #SumAnswerInSec = SUM(AnswerInSec) FROM #RadomQuestionTable
END
SELECT * FROM #RadomQuestionTable
OK. Try this. This might not be the fastest, but is easier to understand and implement. Moreover this is a SQL-only solution:
SELECT t1.id, t2.id, t3.id, t4.id FROM Question t1 CROSS JOIN Question t2
CROSS JOIN Question t3 CROSS JOIN Question t4
WHERE t2.id > t1.id AND t3.id > t2.id AND t4.id > t3.id
AND t1.answerInSec + t2.answerInSec + t3.answerInSec + t4.answerInSec = 60
What this basically does is to create a cross product of your Questions table with itself and then repeats this process two more times, thus creating N ^ 4 rows where N is the number of rows in your table. It then filters out duplicate rows by only those selecting the permutations where t1.id < t2.id < t3.id < t4.id. It then filters remaining rows by looking for the rows where the sum of all score fields is equal to your target value (60).
Note that this result set can become HUGE for even moderately sized tables. For example, a table with just 200 rows will generate a cross product of 200 ^ 4 = 1,600,000,000 rows (though a lot of them will be discarded by the WHERE clause). You should have your indexes in place if your table is large.
Also note that this query does not account for the permutations where less than 4 rows may add up to 60. You can easily modify it to do that by including a NULL row in your table (a row whose score field is zero).
SELECT *
FROM question
WHERE answerInSec<50
ORDER BY CHECKSUM(NEWID())

SQL Select Records ONLY When a Column Value Is In More Than Once

I have a stored procedure in SQL Server, I am trying to select only the records where a column's value is in there more than once, This may seem a bit of an odd request but I can't seem to figure it out, I have tried using HAVING clauses but had no luck..
I want to be able to only select records that have the ACCOUNT in there more than once, So for example:
ACCOUNT | PAYDATE
-------------------
B066 | 15
B066 | OUTSTAND
B027 | OUTSTAND <--- **SHOULD NOT BE IN THE SELECT**
B039 | 09
B039 | OUTSTAND
B052 | 09
B052 | 15
B052 | OUTSTAND
BO27 should NOT show in my select, and the rest of the ACCOUNTS should.
here is my start and end of the Stored Procedure:
Select * from (
*** SELECTS ARE HERE ***
) X where O_STAND <> 0.0000
group by X.ACCOUNT, X.ACCT_NAME , X.DAYS_CR, X.PAYDATE, X.O_STAND
order by X.ACCOUNT
I have been struggling with this for a while, any help or advice would be appreciated. Thank you in advance.
you could replace the first string with
Select *, COUNT(*) OVER (PARTITION BY ACCOUNT) cnt FROM (
and then wrap your query as subquery once more
SELECT cols FROM ( query ) q WHERE cnt>1
Yes, the having clause is for solving exactly this kind of tasks. Basically, it's like where, but allows to filter not only by column values, but also by aggregate functions' results:
declare #t table (
Id int identity(1,1) primary key,
AccountId varchar(20)
);
insert into #t (AccountId)
values
('B001'),
('B002'),
('B015'),
('B015'),
('B002');
-- Get all rows for which AccountId value is encountered more than once in the table
select *
from #t t
where exists (
select 0
from #t h
where h.AccountId = t.AccountId
group by h.AccountId
having count(h.AccountId) > 1
);

Join tables by column names, convert string to column name

I have a table which store 1 row per 1 survey.
Each survey got about 70 questions, each column present 1 question
SurveyID Q1, Q2 Q3 .....
1 Yes Good Bad ......
I want to pivot this so it reads
SurveyID Question Answer
1 Q1 Yes
1 Q2 Good
1 Q3 Bad
... ... .....
I use {cross apply} to acheive this
SELECT t.[SurveyID]
, x.question
, x.Answer
FROM tbl t
CROSS APPLY
(
select 1 as QuestionNumber, 'Q1' as Question , t.Q1 As Answer union all
select 2 as QuestionNumber, 'Q2' as Question , t.Q2 As Answer union all
select 3 as QuestionNumber, 'Q3' as Question , t.Q3 As Answer) x
This works but I dont want to do this 70 times so I have this select statement
select ORDINAL_POSITION
, COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = mytable
This gives me the list of column and position of column in the table.
So I hope I can somehow join 2nd statement with the 1st statement where by column name. However I am comparing content within a column and a column header here. Is it doable? Is there other way of achieving this?
Hope you can guide me please?
Thank you
Instead of Cross Apply you should use UNPIVOT for this query....
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE Test_Table(SurveyID INT, Q1 VARCHAR(10)
, Q2 VARCHAR(10), Q3 VARCHAR(10), Q4 VARCHAR(10))
INSERT INTO Test_Table VALUES
(1 , 'Yes', 'Good' , 'Bad', 'Bad')
,(2 , 'Bad', 'Bad' , 'Yes' , 'Good')
Query 1:
SELECT SurveyID
,Questions
,Answers
FROM Test_Table t
UNPIVOT ( Answers FOR Questions IN (Q1,Q2,Q3,Q4))up
Results:
| SurveyID | Questions | Answers |
|----------|-----------|---------|
| 1 | Q1 | Yes |
| 1 | Q2 | Good |
| 1 | Q3 | Bad |
| 1 | Q4 | Bad |
| 2 | Q1 | Bad |
| 2 | Q2 | Bad |
| 2 | Q3 | Yes |
| 2 | Q4 | Good |
If you need to perform this kind of operation to lots of similar tables that have differing numbers of columns, an UNPIVOT approach alone can be tiresome because you have to manually change the list of columns (Q1,Q2,Q3,etc) each time.
The CROSS APPLY based query in the question also suffers from similar drawbacks.
The solution to this, as you've guessed, involves using meta-information maintained by the server to tell you the list of columns you need to operate on. However, rather than requiring some kind of join as you suspect, what is needed is Dynamic SQL, that is, a SQL query that creates another SQL query on-the-fly.
This is done essentially by concatenating string (varchar) information in the SELECT part of the query, including values from columns which are available in your FROM (and join) clauses.
With Dynamic SQL (DSQL) approaches, you often use system metatables as your starting point. INFORMATION_SCHEMA exists in some SQL Server versions, but you're better off using the Object Catalog Views for this.
A prototype DSQL solution to generate the code for your CROSS APPLY approach would look something like this:
-- Create a variable to hold the created SQL code
-- First, add the static code at the start:
declare #SQL varchar(max) =
' SELECT t.[SurveyID]
, x.question
, x.Answer
FROM tbl t
CROSS APPLY
(
'
-- This syntax will add to the variable for every row in the query results; it's a little like looping over all the rows.
select #SQL +=
'select ' + cast(C.column_id as varchar)
+ ' as QuestionNumber, ''' + C.name
+ ''' as Question , t.' + C.name
+ ' As Answer union all
'
from sys.columns C
inner join sys.tables T on C.object_id=T.object_id
where T.name = 'MySurveyTable'
-- Remove final "union all", add closing bracket and alias
set #SQL = left(#SQL,len(#SQL)-10) + ') x'
print #SQL
-- To also execute (run) the dynamically-generated SQL
-- and get your desired row-based output all at the same time,
-- use the EXECUTE keyword (EXEC for short)
exec #SQL
A similar approach could be used to dynamically write SQL for the UNPIVOT approach.

More efficient alternative to subquery

I've got many "actual" and "history companion tables" (so to speak) with structure of last like this:
values| date_deal | type_deal | num (autoinc)
value1| 01.01.2012 | i | 1
value1| 02.01.2012 | u | 2
value2| 02.01.2012 | i | 3
value2| 03.01.2012 | u | 4
value1| 04.01.2012 | d | 5
value2| 05.01.2012 | u | 6
value2| 08.01.2012 | u | 7
If I insert (or update or delete) record in "actual" table, trigger puts affected record into "history table" with date_deal = Geddate(), type_deal = i|u|d (for insert, update and delete triggers respectivly) and num as autoinc unique value
So the question is how to get last record for each distinct value valid on certain date and excluding from final result records which type_deal = 'd' (since that record was deleted from actual table by that time and we don't want to have anything assosiated with it)
The way I do it most of the time:
SELECT *
FROM t_table1 t1
WHERE t1.num = ( SELECT MAX(num)
FROM t_table1 t2
WHERE t2.[values] = t1.[values]
AND t2.[date_deal] < #dt)
AND t1.[type_deal] <> 'D'
But that works very slow sometimes. I'm looking for more efficient alternative. Please, help
So, an update.
Thanks for replies, friends.
I've made some testing on both actual and testing servers.
In order to put these different approaches into same league I've decided that we should take all fields from source table.
Testing server has bellow 200K records and I also had a luxury of using DBCC FreeProcCache
and DBCC DropCleanbuffers directives. Actual working server has over 2.3M records and also no option for droping buffs or cache since.. well.. it is in use by real users. So it was droped only once and i've got results right after that.
Here is actual queries and time it took on both servers:
Original:
DECLARE #dt datetime = CONVERT(datetime, '01.08.2013', 104)
SELECT *
FROM [CLIENTS_HISTORY].[dbo].[Clients_all_h] c
WHERE c.num = ( SELECT MAX(num)
FROM [CLIENTS_HISTORY].[dbo].[Clients_all_h] c2
WHERE c2.[AccountSys] = c.[AccountSys]
AND date_deal <= #dt)
AND c.type_deal <> 'D'
61sec # 2'316'890rec on real one, 4sec # 191'533 on test
Rahul's:
SELECT *
FROM [CLIENTS_HISTORY].[dbo].[Clients_all_h] c
GROUP BY [all_fields]
HAVING c.num = ( SELECT MAX(num)
FROM [CLIENTS_HISTORY].[dbo].[Clients_all_h] c2
WHERE c2.[AccountSys] = c.[AccountSys]
AND date_deal <= #dt)
AND c.type_deal <> 'D'
62sec # 2'316'890rec on real one, 4sec # 191'533 on test
Almost equal
George's (with some major changes):
SELECT * FROM
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY accountsys ORDER BY num desc) AS aa
FROM [CLIENTS_HISTORY].[dbo].[Clients_all_h] c
WHERE c.date_deal < #dt) as a
WHERE aa=1
AND type_deal <> 'D'
76sec # 2'316'890rec on real one, 5sec # 191'533 on test
So far original and Rahul's are fastest and George's is not so fast.
Try using GROUP BY..HAVING CLAUSE
SELECT *
FROM t_table1 t1
GROUP BY [column_names]
HAVING t1.num = ( SELECT MAX(num)
FROM t_table1 t2
WHERE t2.[values] = t1.[values]
AND t2.[date_deal] < #dt)
AND t1.[type_deal] <> 'D'
I think row_num() could be usefull for you as follows:
select
*
from
(
select
*,
row_number() over( partition by date_deal order by num) as aa
from
t_table1 t1
where
t1.[type_deal] <> 'D'
) as a
where
aa=1

How to perform statistical computations in a query?

I have a table which is filled with float values. I need to calculate the number of results grouped by their distribution around the mean value (Gaussian Distribution). Basically, it is calculated like this:
SELECT COUNT(*), FloatColumn - AVG(FloatColumn) - STDEV(FloatColumn)
FROM Data
GROUP BY FloatColumn - AVG(FloatColumn) - STDEV(FloatColumn)
But for obvious reasons, SQL Server gives this error: Cannot use an aggregate or a subquery in an expression used for the group by list of a GROUP BY clause.
My question is, can I somehow leave this computation to SQL Server? Or do I have to do it the old fashioned way? Retrieve all the data, and do the calculation myself?
To get the aggregate of the whole set you can use an empty OVER clause
WITH T(Result)
AS (SELECT FloatColumn - Avg(FloatColumn) OVER() - Stdev(FloatColumn) OVER ()
FROM Data)
SELECT Count(*),
Result
FROM T
GROUP BY Result
SQL Fiddle
You can perform a pre-aggregation of the data, and join back to the table.
Schema Setup:
create table data(floatcolumn float);
insert data values
(1234.56),
(134.56),
(134.56),
(234.56),
(1349),
(900);
Query 1:
SELECT COUNT(*) C, D.FloatColumn - A
FROM
(
SELECT AVG(FloatColumn) + STDEV(FloatColumn) A
FROM Data
) preagg
CROSS JOIN Data D
GROUP BY FloatColumn - A;
Results:
| C | COLUMN_1 |
--------------------------
| 2 | -1196.876067819572 |
| 1 | -1096.876067819572 |
| 1 | -431.436067819572 |
| 1 | -96.876067819572 |
| 1 | 17.563932180428 |

Resources