PIVOT in SQL Server with no aggregate function - sql-server

I have a table like this (lets call it T1):
Date | Category | Data
-------------------------------
2014-01-01 | 1 | 1.0
2014-01-01 | 2 | 2.0
2014-01-01 | 3 | 3.0
2014-01-02 | 1 | 4.0
2014-01-02 | 2 | 5.0
2014-01-02 | 3 | 6.0
Note that Data are floating point numbers, not sequential, I just made them that way so it's easier to see where they land up in the result. There is also a table translating the Category numbers to names (lets say (T2):
Category | Name
----------------
1 | A
2 | B
3 | C
And I would like a query that could return:
Date | B | C
---------------------------
2014-01-01 | 2.0 | 3.0
2014-01-02 | 5.0 | 6.0
I thought about trying with PIVOT which I haven't used before, however, I can't find a way to do it without using a aggregation function. But then I though that since I only actually have a single row per Date / Category combo using an aggregation like AVG should work. This is my try based on these docs:
SELECT [Date],
[2] as B,
[3] as C
FROM (SELECT * FROM T1 WHERE Category >= 2) AS SourceTable
PIVOT
(
AVG(Data)
FOR Category IN ([2], [3])
) AS PivotTable;
Which gets kind of close:
Date | B | C
---------------------------
2014-01-01 | 2.0 | NULL
2014-01-01 | NULL | 3.0
2014-01-02 | 5.0 | NULL
2014-01-02 | NULL | 6.0
But how do I get rid of the nulls and get all the same dates to be on the same row?

If you only have one value for each result, you can use any of the aggregate functions - eg: MAX
select * from
(select t.date, t.data,c.name from t1 t inner join category c on t.category = c.category) s
pivot (max(data) for name in ([b],[c])) p
The first part is the source data
select t.date, t.data,c.name from t1 t inner join category c on t.category = c.category
Then the pivot rearranges it, creating columns for the in values from the name column, and using the aggregate (max(data)) for the value in that column, which should give the desired results.
If you're getting nulls, it's probably from using select * in your source query, rather than selecting the required fields - ie: replace
SELECT * FROM T1 WHERE Category >= 2
with
select category, data FROM T1 WHERE Category >= 2

Related

SQL Server find sum of values based on criteria within another table

I have a table consisting of ID, Year, Value
---------------------------------------
| ID | Year | Value |
---------------------------------------
| 1 | 2006 | 100 |
| 1 | 2007 | 200 |
| 1 | 2008 | 150 |
| 1 | 2009 | 250 |
| 2 | 2005 | 50 |
| 2 | 2006 | 75 |
| 2 | 2007 | 65 |
---------------------------------------
I then create a derived, aggregated table consisting of an ID, MinYear, and MaxYear
---------------------------------------
| ID | MinYear | MaxYear |
---------------------------------------
| 1 | 2006 | 2009 |
| 2 | 2005 | 2007 |
---------------------------------------
I then want to find the sum of Values between the MinYear and MaxYear foreach ID in the aggregated table, but I am having trouble determining a proper query.
The final table should look something like this
----------------------------------------------------
| ID | MinYear | MaxYear | SumVal |
----------------------------------------------------
| 1 | 2006 | 2009 | 700 |
| 2 | 2005 | 2007 | 190 |
----------------------------------------------------
Right now I can perform all the joins to create the second table. But then I use a fast forward cursor to iterate through each record of the second table with the code inside the for loop looking like the following
DECLARE #curMin int
DECLARE #curMax int
DECLARE #curID int
FETCH Next FROM fastCursor INTo #curISIN, #curMin , #curMax
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT Sum(Value) FROM ValTable WHERE Year >= #curMin and Year <= #curMax and ID = #curID
Group By ID
FETCH Next FROM fastCursor INTo #curISIN, #curMin , #curMax
Having found the sum of values between specified years, I can connect it back to the second table and I wind up the desired result (the third table).
However, the second table in reality is roughly 4 million rows, so this iteration is extremely time consuming (~generating 300 results a minute) and presumably not the best solution.
My question is, is there a way to generate the third table's results without having to use a cursor/for loop?
During a group by the sum will only be for the ID in question -- since the min year and max year is for the ID itself then you don't need to double query. The query below should give you exactly what you need. If you have a different requirement let me know.
SELECT ID, MIN(YEAR) as MinYear, MAX(YEAR) as MaxYear, SUM(VALUE) as SUMVALUE
FROM tablenameyoudidnotsay
GROUP BY ID
You could use query as bellow
TableA is your first table, and TableB is the second one
SELECT *,
(select SUM(Value) FROM TableA where tablea.ID=TableB.ID AND tableA.Year BETWEEN
TableB.MinYear AND TableB.MaxYear) AS SumValue
from TableB
You can put your criteria into a join and obtain the result all as one set which should be faster:
SELECT b.Id, b.MinYear, b.MaxYear, sum(a.Value)
FROM Table2 b
JOIN Table1 a ON a.Id=b.Id AND b.MinYear <= a.Year AND b.MaxYear >= a.Year
GROUP BY b.Id, b.MinYear, b.MaxYear

Using Where clause in Case to compare

I am new to T-sql .
I am using the following Query:
SELECT e.Id,e.cAvg,
CASE
WHEN e.cAvg<=0.8 and cAvg>=0 THEN t.Model when t.Cr='0.8' then t.Model
WHEN e.cAvg>0.8 and cAvg<=5.4 THEN t.Model WHEN t.Cr='5.4' then t.Model
WHEN e.cAvg>5.4 and cg<=8 THEN t.Model WHEN t.Cr='8' then t.Model
ELSE 'No Change Required'
END
from A e, B t;
What I am trying to do is:
Select id and cAvg columns in Table A.
Compare cAvg in Table A with Cr in Table B.
Use the comparison in CASE to select the particular row which satisfies the condition.
Use the selected row to give query results.
t.Model is a column of table B. I want to select t.Model value of the selected row in the case statement.
I feel the way is to somehow include a equivalent of the where clause inside When of CASE.
Need Direction!!
The table schema:
Table A:
+----+------+
| id | cAvg |
+----+------+
| 1 | .8 |
| 2 | 5.4 |
| 3 | 6.0 |
+----+------+
Table B:
+-----+-------+
| Cr | Model |
+-----+-------+
| 2 | M1 |
| 5.5 | M2 |
| 8 | M3 |
+-----+-------+
I want to the following:
Compare the values of cAvg with a condition => (cAvg<=8 And cAvg>=5.5 => the model selected must be M3.)
The result I want to get is:
+----+------+-------+
| id | cAvg | Model |
+----+------+-------+
| 1 | .8 | M1 |
| 2 | 5.4 | M2 |
| 3 | 6.0 | M3 |
+----+------+-------+
I tired Join as suggested in the comments, A great thanks , I learnt a lot because of it!!.
My problem is that there are no common columns to join.
Also I need to compare the column in one table with that of another table and then give a result based on the comparison.
I referred to many answers in stack overflow but all the answers are for the premise where there is a common column.
I tried the following:
Inner Join
Cases
I need a direction as to which direction I need to go into.
Thank you!!
1st of all, you're selecting from 2 tables but without any link restrictions, so all rows are compared
If there is a matching key between the tables, so only relevant pairs of rows would be compared, it should be used, in a JOIN statement:
A e JOIN B t ON e.id = t.id
2nd of all, in order to select relevant lines, you should decide what these are..
you can inside a WHERE statement define whatvare the relent cases
WHERE
e.cAvg > 12
You can use the case statement inside WHERE but then the result should be conditioned are returned TRUE
SELECT e.Id,e.cAvg, t.Model
A e JOIN B t ON e.id = t.id
WHERE
CASE WHEN e.cAvg<=6 THEN t.Model when t.Cr=6 then t.Model
WHEN e.cAvg>6 and e.cAvg<=12 THEN t.Model
WHEN t.Cr='12' then t.Model
WHEN e.cAvg>12 and cg<=24 THEN t.Model
WHEN t.Cr='24' then t.Model
ELSE -1 END ! = -1
EDIT
Following you question edit, I think that what you need is a JOIN with a condition
Basically, instead of joining the tables with an equal key, join them with an unequal key.
Since you're looking for cAvg BETWEEN to t. Cr rows, 2 JOINs are needed
SELECT e.Id,e.cAvg, t.Model
FROM
A e JOIN B t ON
e.cAvg >= t.Cr
JOIN B t2 ON
e.cAvg < t2.Cr
WHERE
t.Cr IS NOT NULL
AND t2.Cr IS NOT NULL
The idea is that only where the 2 conditions are met, you would get the results of e
Hope that helps
I found a possible work around for the problem.
Problem Statement:
Compare two tables with no common column.
Use the comparison in CASE to select a particular row.
A WHERE Clause inside CASE is not accepted in T-SQL.
My Work Around :
Add a new column in the second table.
Assign An id from Table B to the column in Table A.
Use the assigned id to select the required row in Table B.
Tables:
Table A:
+----+------+
| id | cAvg |
+----+------+
| 1 | .8 |
| 2 | 5.4 |
| 3 | 6.0 |
+----+------+
Table B
+-----+-----+-------+
| Bid | Cr | Model |
+-----+-----+-------+
| 1 | 2 | M1 |
| 2 | 5.5 | M2 |
| 3 | 8 | M3 |
+-----+-----+-------+
Query to assign id's:
CREATE VIEW [AssignIDView] AS
SELECT DISTINCT e.id,
e.cAvg,
(CASE
WHEN e.cAvg>=0 and e.cAvg<=2 THEN 1
WHEN e.cAvg>2 and e.cAvg<=5.5 THEN 2
WHEN e.cAvg>3 and e.cAvg<=8 THEN 3
ELSE 'Invalid'
END) As BId
FROM A e, B t;
The result of the above Query will be a view as follows:
+----+------+-----+
| id | cAvg | Bid |
+----+------+-----+
| 1 | .8 | 1 |
| 2 | 5.4 | 2 |
| 3 | 6.0 | 3 |
+----+------+-----+
Now use Bid to select rows from table B to assign Model from B:
Query:
CREATE VIEW [ModelAssignView] AS
select e.id,
e.cAvg,
t.Model as [Model]
FROM A e, B t where e.TierID = t.id;
The result of the Query will be as follows:
+----+------+-------+
| id | cAvg | Model |
+----+------+-------+
| 1 | .8 | M1 |
| 2 | 5.4 | M2 |
| 3 | 6.0 | M3 |
+----+------+-------+
The intention of my question was to do the above.
For that I wanted to find an Equivalent of A WHERE Clause inside CASE.
But the above method achieved the solution for me.
Hope it helps:)!

Extract number from delimited string, with varying formats

I'm trying to join two tables, OrderLines and Production, from our ERP system, running SQL Server 2014. The way it works is, if a product is added to an order and there is nothing in stock for that product, a "Production Entry" (Bill of Materials, essentially) is generated automatically for Production.
The GeneratedEntries field in the OrderLines table captures that data, recording which Production Entry numbers were generated for that order line. The field usually is in the following format: It starts with PD~, then is followed by an entry number, with subsequent entry numbers delimited by another tilde ~. So a standard value for this field, for an order line that caused 2 entry numbers to be generated, might look like this:
PD~12345~67891
The issue is, the formatting for that field is sometimes not consistent, in that there are sometimes trailing tildes, sometimes the PD~ is repeated in middle of the string, other extra garbage characters, etc.
I don't know how to extract the entry numbers from GeneratedEntries to join the two tables, in a way that would work for all formatting possibilities. Is there any way to do this?
Sample tables below with relevant columns (OrderNumber and Product aren't really relevant, just there for context...)
OrderLines Table
+-------------+---------+----------------------+
| OrderNumber | Product | GeneratedEntries |
+-------------+---------+----------------------+
| 1 | A | PD~10005 |
| 1 | B | PD~10006~ |
| 1 | C | PD~10007~10008~10009 |
| 2 | R | PD~10010~~10011 |
| 2 | L | ~PD~10012~~ |
| 2 | Z | PD~10013 PD~10014 |
+-------------+---------+----------------------+
Production Table
+-----------------+
| ProductionEntry |
+-----------------+
| 10005 |
| 10006 |
| 10007 |
| 10008 |
| 10009 |
| 10010 |
| 10011 |
| 10012 |
| 10013 |
| 10014 |
+-----------------+
Virtually any parse/split function will do. Here is an inline approach.
The only trick is that we replace any SPACE with a ~ and filter with a try_convert()
Example
Declare #YourTable Table ([OrderNumber] varchar(50),[Product] varchar(50),[GeneratedEntries] varchar(50))
Insert Into #YourTable Values
(1,'A','PD~10005')
,(1,'B','PD~10006~')
,(1,'C','PD~10007~10008~10009')
,(2,'R','PD~10010~~10011')
,(2,'L','~PD~10012~~')
,(2,'Z','PD~10013 PD~10014')
Select A.OrderNumber
,A.Product
,B.*
From #YourTable A
Cross Apply (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace(replace([GeneratedEntries],' ','~'),'~','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) B
Where try_convert(int,B.RetVal) is not null
I kept the sequence RetSeq just in case it was important.
Returns
OrderNumber Product RetSeq RetVal
1 A 2 10005
1 B 2 10006
1 C 2 10007
1 C 3 10008
1 C 4 10009
2 R 2 10010
2 R 4 10011
2 L 3 10012
2 Z 2 10013
2 Z 4 10014

How to define a field & group by within Select Query in SQL

At present have a column with status types, and a separate column with dates. What I would like to do is create new columns for each status type. Have created column names by using case when statements, but cannot then group by those.
At present, the table kicks out the following:
Reference | Status | Date
----- | ----------- | -----
1 | Approve | 1/1/2017
1 | In Progress | 1/2/2017
2 | Approve | 1/1/2017
2 | In Progress | 1/2/2017
2 | Close | 1/3/2017
Would like to take this and make:
Reference | Approve | In Progress | Close
--------- | -------- | ----------- | -----
1 | 1/1/2017 | 1/2/2017 |
2 | 1/1/2017 | 1/2/2017 | 1/3/2017
Have a lot of other selects, and intention is to export to excel/run automatically, so trying to avoid temp tables.
I don't know that case when is appropriate, but am struggling to find a better solution.
You can use pivot in sql server
select * from #yourStatus
pivot( max(date) for [Status] in ([Approve], [In Progress],[Close])) p
Your table:
create table #yourStatus (Reference int, Status varchar(20), Date date)
insert into #yourstatus (
Reference , Status , Date ) values
( 1 ,'Approve','1/1/2017')
,( 1 ,'In Progress','1/2/2017')
,( 2 ,'Approve','1/1/2017')
,( 2 ,'In Progress','1/2/2017')
,( 2 ,'Close','1/3/2017')
Something like this should work based on the table you've provided (I've also handled null values to get you the output you need):-
select
Reference,
isnull(convert(varchar(10),[Approve],103),'') [Approve],
isnull(convert(varchar(10),[In Progress],103),'') [In Progress],
isnull(convert(varchar(10),[Close],103),'') [Close]
from
(
select
Reference,
Status,
Date
from YourTableName
) d
pivot (min([Date]) for Status in ([Approve],[In Progress],[Close])) p

Query to find closest number and return the associated discount in SQL server

Here is the exact situation:
Monthly_Budget | Extra_Bonus
---------------------------
300,000,000 | 0.40
420,000,000 | 0.60
580,000,000 | 0.90
1,000,000,000 | 1.20
1,600,000,000 | 1.45
2,900,000,000 | 1.55
4,160,000,000 | 1.65
6,600,000,000 | 1.80
10,000,000,000 | 2.10
14,160,000,000 | 2.25
20,000,000,000 | 2.60
26,000,000,000 | 3.00
33,000,000,000 | 3.40
50,000,000,000 | 4.00
73,000,000,000 | 4.50
The first Column shows the budget and second one indicates the relevant bonus.
The rule says for example if the budget is 300,000,000 or 350,000,000 or 410,000,000 (anything less than next record) the bonus will be 0.40 (the previous Record).
my question is how i can query this exact given example?
SELECT Extra Bonus
FROM Monthly_Budget_TBL
WHERE Monthly_Budget .....?
Thanks
declare #actualBudget integer = 350000000
-- --------------------------------
Select Extra_Bonus from table
Where Monthly_Budget =
(Select Max(Monthly_Budget) from table
Where Monthly_Budget <= #actualBudget)
So you have some budget as input and want to get the closest bonus that has a budget lower or equal your input?
in that case this should work:
SELECT Extra_Bonus, {exampleBudgetVariable} - Monthly_Budget as diff FROM Monthly_Budget_TBL
WHERE {exampleBudgetVariable} - Monthly_Budget >= 0
ORDER BY diff ASC
LIMIT 1
{exampleBudgetVariable} should be replaced with your example budget in whatever language you are coding
In order to demonstrate this properly, I'm going to make up a couple additional tables. Let's pretend you have a Employees table with an EmployeeID column and a Name column, and MonthlySalesSummary table with an EmployeeID column and SalesAmount column. It's this SalesAmount value that you want to match up against the budget for each salesperson. Here's what the query might look like:
SELECT e.Name, b.Extra_Bonus
FROM Employee e
INNER JOIN MonthlySalesSummary s ON s.EmployeeID = e.EmployeeID
CROSS APPLY (
SELECT TOP 1 Extra_Bonus FROM Monthly_Budget_TBL WHERE Monthly_Budget > SalesAmount ORDER BY Monthly_Budget
) b

Resources