How to find the mode of 3 columns in SQL? - sql

In SQL I have a table with 3 columns:
Month1
Month2
Month3
0
1
0
1
1
1
0
1
1
...and so on.
I need another column where it gives the mode of Month1, Month2 and Month3.
My expected output is:
Month1
Month2
Month3
Mode
0
1
0
0
1
1
1
1
0
1
1
1
So far I have only calculated mode for a single column. Not sure how we can do it horizontally by combining 3 columns.

This should work, can easily be expanded for n columns:
select month1, month2, month3, ca.val
from t
cross apply (
select top 1 val
from (values
(month1),
(month2),
(month3)
) as v(val)
group by val
order by count(*) desc
) as ca
For RDBMS other than SQL server, replace values(...) with appropriate table value constructor, cross apply with lateral join/sub query inside select and top 1 with limit/offset...fetch.

You could use a CASE expression:
SELECT *, CASE WHEN Month1 + Month2 + Month3 <= 1 THEN 0 ELSE 1 END AS mode
FROM yourTable;

Related

Average across rows in SQL Server 2008

I have the following table
Data Data1 Data2 YTD
-------------------------
1 2 3
2 3 4
3 3 6
In the YTD column I have to average the rows data. I can use average in columns but not sure how to average across rows.
Looking for the below results and using SQL Server 2008
Data Data1 Data2 YTD
---------------------------------
1 2 3 2 (Average)
2 3 4 3
3 null 6 4.5
I think cross apply is the simplest method:
select t.*, v.avg_data
from t cross apply
(select avg(v.data) as avg_data
from (values (t.data), (t.data1), (t.data2)) v(data)
) v;
Use case expressions, you can also express this as:
select t.*,
( (coalesce(t.data, 0) + (t.data1, 0) + coalesce(t.data2, 0)) /
nullif( (case when t.data is not null then 1 else 0 end) +
(case when t.dat1 is not null then 1 else 0 end) +
(case when t.dat2 is not null then 1 else 0 end), 0
)
) as avg_data;
However, this formulation is messy and prone to typing errors.

SQL Server (2012): Pivot (or Group by?) on Multiple Columns

I have the following table:
month seating_id dept_id
Jan 1 5
Jan 8 9
Jan 5 3
Jan 7 2
Jan 1 5
Feb 1 9
Feb 8 9
Feb 5 3
Feb 7 2
Feb 7 1
I want to count each type of seating_id and dept_id for each month, so the result would look something like this:
month seating_id_1 seating_id_5 seating_id_7 seating_id_8 dept_id_1 dept_id_2 dept_id_3 dept_id_5 dept_id_9
Jan 2 1 1 1 0 1 1 2 1
Feb 0 1 2 1 1 1 1 0 2
I've experimented with unpivot/pivot and GROUP BY but haven't been able to achieve the desired results. Note, I would like to perform this as a SELECT statement, and not PROCEDURE call, if possible.
Let me know if you need any other info.
In case you need to go Dynamic
Example
Declare #SQL varchar(max) = '
Select *
From (
Select month,B.*
From YourTable A
Cross Apply (values (concat(''seating_id_'',seating_id),seating_id)
,(concat(''dept_id_'',dept_id),dept_id)
) b(item,value)
) A
Pivot (count([Value]) For [Item] in (' + Stuff((Select Distinct concat(',[seating_id_',seating_id,']') from YourTable For XML Path('')),1,1,'')
+','+
Stuff((Select Distinct concat(',[dept_id_',dept_id,']') from YourTable For XML Path('')),1,1,'')
+ ') ) p'
Exec(#SQL);
Returns
The Generated SQL Looks like this
Select *
From (
Select month,B.*
From YourTable A
Cross Apply (values (concat('seating_id_',seating_id),seating_id)
,(concat('dept_id_',dept_id),dept_id)
) b(item,value)
) A
Pivot (count([Value]) For [Item] in ([seating_id_1],[seating_id_5],[seating_id_7],[seating_id_8],[dept_id_1],[dept_id_2],[dept_id_3],[dept_id_5],[dept_id_9]) ) p
i have an answer you might not like but it does it:
select month
, sum(case seating_id when 1 then 1 else 0 end) as seating_id_1
, sum(case seating_id when 2 then 1 else 0 end) as seating_id_2
...
, sum(case dept_id when 1 then 1 else 0 end) as dept_id_1
, sum(case dept_id when 2 then 1 else 0 end) as dept_id_2
...
from YourTable
group by month

SQL: How to split a string at each character to display in a separate row?

I want to split a string at each character and display each of them in a separate row. I also need an extra column (Col2) which should display whether the character is a number or not (if number, then 1 else 0).
Example:
If the data is 'October 11, 2017', I should get
Col1 Col2
O 0
c 0
t 0
o 0
b 0
e 0
r 0
0
1 1
1 1
, 0
0
2 1
0 1
1 1
7 1
You can use this.
DECLARE #data VARCHAR(100) = 'October 11, 2017'
;WITH CTE AS
(
SELECT STUFF(#data,1,1,'') TXT, LEFT(#data,1) Col1
UNION ALL
SELECT STUFF(TXT,1,1,'') TXT, LEFT(TXT,1) Col1 FROM CTE
WHERE LEN(TXT) > 0
)
select Col1, ISNUMERIC(Col1) from CTE
Result:
Col1 Col2
---- -----------
O 0
c 0
t 0
o 0
b 0
e 0
r 0
0
1 1
1 1
, 1
0
2 1
0 1
1 1
7 1
Try this:
CREATE TABLE tbSeperate (Data NVARCHAR(100))
INSERT INTO tbSeperate SELECT 'October 11, 2017'
SELECT SUBSTRING(Data,Number,1) rt , CASE WHEN TRY_CAST(SUBSTRING(Data,Number,1) AS INT) IS NULL THEN 0
WHEN SUBSTRING(Data,Number,1) = ' ' THEN 0 ELSE 1 END c FROM tbSeperate
CROSS APPLY (SELECT DISTINCT number FROM master..spt_values WHERE number > 0 AND number <= LEN(Data))V
One method uses a recursive CTE:
with cte as (
select cast('October 11, 2017' as varchar(max)) as str,
cast(NULL as varchar(max)) as letter, 0 as lev
union all
select substring(str, 2, len(str)), left(str, 1), lev + 1
from cte
where str <> ''
)
select letter,
(case when letter between '0' and '9' then 1 else 0 end) as is_digit
from cte
where lev > 0;
If the string can have more than 99 characters, then you would want to use the maximum recursion option.
Here is a Rextester.
Using recursive CTE in MySQL we can achieve this.
following query will print the character of each string in different rows.
replace your table name and column name as per requirement.
WITH RECURSIVE cte
as
(
select 1 as n,1 as f,first_name ,'Temporary Variable' as name1 from customer
union
select (n+1) as n,f,first_name,substr(first_name,n,f) as name1 from cte
where substr(first_name,n,n)<>""
)
select n,name1,first_name from cte where n<>1 and first_name='KAREN'
Screenshot

Multiple counts for each ID

I'm trying to get my head around this, but unfortunately neither of my approaches works:
I need a table with 3 columns:
ItemID
Number cases where ItemID has CostcentreID x
Number cases where ItemID has CostcentreID y
SELECT ItemID, Count1, Count2
FROM Table
Output should be like:
--ItemID--Count1--Count2
1 12 5
2 3 2
What i get when using
SELECT ItemdID, SUM(case when costc...),...
FROM Table
is:
--ItemID--Count1--Count2
1 12 0
2 3 0
due to the GROUP BY statement.
Anyway to solve this without a Cursor?
Also, a JOIN of 5 tables is needed.
Thanks in advance!
I'm not sure what you need with the joins, but here is the first part.
DECLARE #table TABLE(ItemID INT, CostCentreID CHAR(1));
INSERT INTO #table
VALUES (1,'X'),
(1,'X'),
(1,'Y'),
(2,'X'),
(2,'Y'),
(2,'Y'),
(2,'Y');
SELECT ItemID,
SUM(
CASE
WHEN CostCentreID = 'X' THEN 1 ELSE 0
END
) AS CostCentreX,
SUM(
CASE
WHEN CostCentreID = 'Y' THEN 1 ELSE 0
END
) AS CostCentreY
FROM #table
GROUP BY ItemID
Results:
ItemID CostCentreX CostCentreY
----------- ----------- -----------
1 2 1
2 1 3

sql query different column based on input

I am using MS-SQL 2008. I have a table with different columns based on locations in it that will have a 'Y' or Null value. The table also has other data other than location from survey results. I have set up a temptable #TempLocation to hold the location based on the one or all. I need to select rows from the table based on 'Y' from one or more location rows within a date range.
TableID Northwest Northeast Southwest Southeast Batchno first_choice date_completed
1 Y Y Y 1 A 2012-11-10
2 Y Y 1 SA 2012-19-10
3 Y Y 1 N 2012-07-10
4 Y Y Y 2 A 2012-10-10
5 Y 2 A 2012-16-10
6 Y Y 2 D 2012-21-10
7 Y NULL A 2012-19-10
8 Y Y Y Y 3 SA 2012-11-10
9 Y 3 A 2012-10-10
10 Y Y 3 A 2012-07-10
I have created a Dynamic SQL statement to pull one location successfully but is it possible to pull all of them?
select ''' + (SELECT * FROM #TempLocation) + ''',
count(batchno),
count(case when first_choice is not null then batchno end),
count(case when t.First_choice =''SD'' then 1 end) ,
count(case when t.First_choice=''D'' then 1 end) ,
count(case when t.First_choice=''N'' then 1 end) ,
count(case when t.First_choice=''A'' then 1 end) ,
count(case when t.First_choice=''SA'' then 1 end)
from customer_satisfaction_survey t
where t.date_completed>= ''' + CAST(#beg_date AS VARCHAR) + '''
and t.date_completed < ''' + CAST(dateadd(day,1,#end_date) AS Varchar) + '''
and t.' + (SELECT * FROM #TempLocation) + ' = ''Y'''
An All result would look like this.
Number Location Total Total2 SA A N D SD
1 Northwest 6 6 1 3 1 1 0
2 Northeast 5 4 2 2 1 0 0
3 Southwest 4 4 1 3 0 0 0
4 Southeast 6 6 2 3 0 1 0
I have to think that you are approaching this in the wrong way, because your data is not normalized. The first thing you should do is to normalize the data using UNPIVOT. I'm assuming that you are using SQL Server, since your syntax suggests that. It is a good idea to tag all questions with the database, though.
You can unpivot your data with a statement such as:
select BatchNo, FirstChoice, DateCompleted, Location
from d
unpivot (val for location in (Northwest, Northeast, Southwest, Southeast)) as unpvt
Next, set up your temporary table to have a separate row for each location. Then, you can do the join with no dynamic SQL. Something like:
with dnorm as (
THE NORMALIZATION QUERY HERE
)
select dnorm.location, count(*) as total,
sum(case when dnorm.first_choice is not null then 1 else 0 end) as total2,
sum(case when dnorm.first_choice = 'SA' then 1 else 0 end) as SA,
. . .
from dnorm join
#TempLocation tl
on dnorm.location = tl.location
where ALL YOUR WHERE CONDITIONS HERE
The final query looks something like:
with dnorm as (
select BatchNo, FirstChoice, DateCompleted, Location
from d
unpivot (val for location in (Northwest, Northeast, Southwest, Southeast)) as unpvt
)
select dnorm.location, count(*) as total,
sum(case when dnorm.first_choice is not null then 1 else 0 end) as total2,
sum(case when dnorm.first_choice = 'SA' then 1 else 0 end) as SA,
. . .
from dnorm join
#TempLocation tl
on dnorm.location = tl.location
where ALL YOUR WHERE CONDITIONS HERE
The dynamic SQL approach is quite clever, but I don't think it is the simplest way to approach this.