sql query different column based on input - sql

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.

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.

Concetating results from Oracle table with several criterias

This is a tough one. I've read about concatating values from multible rows in a table, but can't find anything on how to go about the task set before me.
I'm not an oracle-man, and untill now have only made simple select queries, so I'm at a loss here.
In a huge oracle database table (severel hundred millions of rows) containing laboratory results, I need to select information on specific requisitions, that meet a specific criteria.
Criteria: For the same ReqNo, Analysis A B and C must be present with an answer, if they are, any instance of the answer to analysis X, Y or Z should be selected
Table contents:
ReqNo Ana Answer
1 A 7
1 B 14
1 C 18
1 X 250
2 A 8
2 X 35
2 Y 125
3 A 8
3 B 16
3 C 20
3 Z 100
4 X 115
4 Y 355
5 A 6
5 B 15
5 C 22
5 X 300
5 Y 108
5 C 88
Desired result:
ReqNo A B C X Y Z
1 7 14 18 250
3 8 16 20 100
5 6 15 22 300 108 88
leaving out ReqNo 2 and 4, since they don't meet the A/B/C criteria.
Is that even possible?
You may first filter the records that have all 3 (A,B and C) and then use PIVOT to convert them to columns for those which satisfy the criteria.
with req
AS
(
select reqno from t where ana IN ('A','B','C')
GROUP BY reqno HAVING
count(DISTINCT ana) = 3
)
select * FROM
(
select * from t where
exists ( select 1 from req r where t.reqno = r.reqno )
)
PIVOT(
min(answer) for ana in ('A' as A, 'B' as B, 'C' as C,
'X' as X, 'Y' as Y, 'Z' as Z)
) ORDER BY reqno;
Demo
I would just use conditional aggregation:
select reqno,
max(case when Ana = 'A' then Answer end) as a,
max(case when Ana = 'B' then Answer end) as b,
max(case when Ana = 'C' then Answer end) as c,
max(case when Ana = 'X' then Answer end) as x,
max(case when Ana = 'Y' then Answer end) as y,
max(case when Ana = 'Z' then Answer end) as z
from t
group by reqno
having sum(case when Ana = 'A' then 1 else 0 end) > 0 and
sum(case when Ana = 'B' then 1 else 0 end) > 0 and
sum(case when Ana = 'C' then 1 else 0 end) > 0 ;
Given that you don't seem to have duplicates, you can simplify the having to:
having sum(case when Ana in ('A', 'B', 'C') then 1 else 0 end) = 3

Showing rows with greatest count of a specific value of a column

I have this query:
select
d.sdealer_number
,c.icontract_term
,case when (c.icontract_term / 12) = 0 THEN cast(c.icontract_term as varchar) + ' M' ELSE cast((c.icontract_term / 12) as varchar) + ' Y' END as Term
,count(c.icontract_term) as [Count]
from dealers d
inner join contracts c on c.sdealer_number = d.sdealer_number
where d.sdealer_number not like '%demo%'
group by c.icontract_term, d.sdealer_number
order by d.sdealer_number
Which returns this result set:
sdealer_number icontract_term Term Count
DL00001 84 7 Y 3
DL00001 12 1 Y 12
DL00001 48 4 Y 15
DL00001 60 5 Y 2
DL00001 24 2 Y 2
DL00001 3 3 M 1
DL00001 6 6 M 5
DL00001 36 3 Y 1
DL00002 84 7 Y 4
DL00002 48 4 Y 2
DL00002 6 6 M 35
DL00002 3 3 M 8
DL00002 12 1 Y 8
DL00002 36 3 Y 2
DL00007 36 3 Y 1
DL00007 12 1 Y 1
DL00007 60 5 Y 4
DL00007 24 2 Y 2
DL00007 48 4 Y 9
DL00007 84 7 Y 1
I need to filter the result set and only show rows where 4 Y (48 month term) and 5 Y (60 month term) are the majority of contracts sold.
So, in the above example DL00001 should not show up nor should DL00002, but DL00007 should show up because they have more 4-5 Y terms contracts sold than any other contract type they sell.
EDIT:
Here is solution used with credit going to #MWillemse:
; with t as (select d.sdealer_number, sum(case when c.icontract_term in (48,60) then 1 else 0 end) as '4-5 Yeam Term', sum(case when c.icontract_term not in (48,60) then 1 else 0 end) as 'Non 4-5 Yeam Term'
from dealers d
inner join contracts c on c.sdealer_number = d.sdealer_number
where d.sdealer_number not like '%demo%'
group by d.sdealer_number)
select * from t
where t.[4-5 Yeam Term] > t.[Non 4-5 Yeam Term]
order by sdealer_number
Group by you dealer_number and conditionally sum the counts using a construct like this: SUM(CASE WHEN Term IN ( '4 Y', '5 Y' ) THEN [Count] ELSE 0 END) and use a having clause to filter which groups to keep.
EDIT: After rereading your query I realize you need to filter the results, not group them altogether. The query below will probably better suit your needs.
WITH
YourOriginalQuery
AS (SELECT d.sdealer_number
, c.icontract_term
, CASE WHEN (c.icontract_term / 12) = 0 THEN CAST(c.icontract_term AS VARCHAR) + ' M'
ELSE CAST((c.icontract_term / 12) AS VARCHAR) + ' Y'
END AS Term
, COUNT(c.icontract_term) AS [Count]
FROM dealers d
INNER JOIN contracts c ON c.sdealer_number = d.sdealer_number
WHERE d.sdealer_number NOT LIKE '%demo%'
GROUP BY c.icontract_term
, d.sdealer_number
) ,
Totals
AS (SELECT YOQ.*
, Y45Total = SUM(CASE WHEN Term IN ('4 Y', '5 Y') THEN 1
ELSE 0
END) OVER (PARTITION BY dealer_number)
, NY45Total = SUM(CASE WHEN Term NOT IN ('4 Y', '5 Y') THEN 1
ELSE 0
END) OVER (PARTITION BY dealer_number)
FROM YourOriginalQuery AS YOQ
)
SELECT *
FROM Totals
WHERE Totals.Y45Total > Totals.NY45Total
ORDER BY d.sdealer_number

How to use a Pivot Function to show data for a yr's span

I have report that was coded I was told some time ago, and it shows data for a 'Ratings' and 'Result' column across the span of a year.
Currently the script is using Case statements to acheive this, however I believe this is what the Pivot Function is for and could save some lines of code. Can anyone assist me in changing the logic to use a pivot function?Current Logic:
Select
EmployeeID,
Employee,
--Datetype,
EmpGroupID,
PA,
EmpLevel,
ObjectiveName as Objective,
Objectiveid AS Objectiveid,
Weighting,
ReportingYear,
max(Case When r.DateType = 'M'
and Month(r.StartDate) = 1
and r.result IS NOT NULL
Then r.Result
When r.DateType = 'M'
and Month(r.StartDate) = 1
and r.result IS NULL
and r.rating is NOT NULL THEN COALESCE(rtovr.ratingShortDesc,rt.ratingShortDesc)
Else null
END) as Value_January_1,
MAX(Case When r.DateType = 'M'
and Month(r.StartDate) = 1
Then dbo.udfGetRating(case when r.RatingOverride is null
then r.Rating
else r.RatingOverride
end) + '|' + CAST(UserOverride AS CHAR(1)) + '|' + rt.ratingShortDesc
Else null
END) as Rating_January_1,
From #tblResults R
WHERE r.datetype IN ('M','Y')
... follows that case logic in the select all the way to Dec.
Example Data on table #tblResult:
EmployeeID Employee Datetype EmpGroupID PA EmpLevel ObjectiveName Objectiveid Weighting RPTYear ObjSortOrder ObjGrpSortOrder Result Rating RatingOverride startdate UserOverride
552940 test Q 664 Sr Rep 1 Overall Rating -1 1 2012 0 0 2 2.00 NULL 2012-01-01 0
552940 test Q 664 Sr Rep 1 Overall Rating -1 1 2012 0 0 2.39 2.00 NULL 2012-04-01 0

T-SQL Query a matrix table used as LIFO

Following my [question]: T-SQL Query a matrix table for free position
I've now trying to handle my matrix table as a LIFO. Each couple of (X,Z) represent a channel in which I can store an element. When I generate a location I'm now using the query provided in the above question and here below.
SELECT x, z, MAX(CASE WHEN disabled = 0 AND occupiedId IS NULL THEN Y ELSE 0 END) firstFreeY
FROM matrix
GROUP BY x, z
ORDER BY x, z;
This is working but it doesn't handle "holes". In fact It's possible that a Disabled flag is removed from the table or an element is manually deleted.
In case my Matrix table will look like this:
X Z Y Disabled OccupiedId
--------------------------------------------------
1 1 1 0 591
1 1 2 0 NULL
1 1 3 1 NULL
1 1 4 0 524
1 1 5 0 523
1 1 6 0 522
1 1 7 0 484
1 2 1 0 NULL
1 2 2 0 NULL
1 2 3 0 NULL
1 2 4 0 NULL
1 2 5 0 NULL
1 2 6 0 589
1 2 7 0 592
the result of the above query is:
X Z firstFreeY
------------------------
1 1 2
1 2 5
instead of:
X Y firstFreeY
------------------------
1 1 0
1 2 5
Any suggestions on how to achieve this?
This query looks for the largest Y that is smaller than all other occupied Y's:
select m1.X
, m1.Z
, max(
case
when m2.MinOccupiedY is null or m1.Y < m2.MinOccupiedY then m1.Y
else 0
end
) as FirstFreeY
from matrix m1
join (
select X
, Z
, min(
case
when disabled <> 0 or occupiedId is not null then Y
end
) as MinOccupiedY
from matrix
group by
X
, Z
) m2
on m1.X = m2.X
and m1.Z = m2.Z
group by
m1.X
, m1.Z
Live example at SQL Fiddle.
just to know if i understood what you were asking, is this working too?
select distinct
m1.x,m1.z, o.y
from
matrix m1
cross apply
(
select top 1 (case when m2.Disabled = 0 then m2.y else 0 end)
from matrix m2
where
m1.x = m2.x
and m1.z = m2.z
and m2.OccupiedId is null
order by m2.y desc
) o (y);