SQL to Mimic Excel - sql

I have an finance issue that we are trying to put into a SQLServer 2005 database (default installation). We are doing quarterly comparison from this year to the same quarter last year i.e. (2013Q1 - 2012Q1) / 20132Q1. Can someone write a query to return an ordered list from 1 - 9 with the quarter over quarter as described above?
In data set
QUART REV
2011Q1 6,175,352
2011Q2 6,591,067
2011Q3 6,219,978
2011Q4 6,189,939
2012Q1 7,178,652
2012Q2 6,731,467
2012Q3 6,949,978
2012Q4 6,679,939
2013Q1 6,242,802
2013Q2 6,421,902
2013Q3 6,667,007
2013Q4 6,575,004
Expected output
QUART COMP
1 0.1625
2 0.0213
3 0.1174
4 0.0792
5 -0.1304
6 -0.0460
7 -0.0407
8 -0.0157
Thanks in advance ;-)

I agree with the above comment, it is much easier if you split quart:
create table t
( yr int not null
, qt int not null
, salary int not null
, primary key (yr,qt) )
insert into t (yr,qt,salary)
values (2011,1,6175352)
, (2011,2,6591067)
, (2011,3,6219978)
, (2011,4,6189939)
, (2012,1,7178652)
, (2012,2,6731467)
, (2012,3,6949978)
, (2012,4,6679939)
, (2013,1,6242802)
, (2013,2,6421902)
, (2013,3,6667007)
, (2013,4,6575004)
select row_number() over (order by yr, qt) as quart, comp
from (
select t1.yr, t1.qt
, (1.0*t1.salary - (select salary
from t t2
where t2.yr = t1.yr - 1
and t2.qt = t1.qt)
) / t1.salary comp
from t t1
where t1.yr >= 2012
)
my numbers deviates from yours, I have not investigate why but it should give you a start.

Lot of formatting in a SQL query, but the exercise was fun. Normally, you should elevate formatting (such as rounding) to the application level, but since you're trying to emulate Excel...
/*sample data*/
DECLARE #T TABLE ( Quart CHAR(6), Rev INT )
INSERT INTO #T
( Quart, Rev )
VALUES ( '2011Q1', 6175352 ),
( '2011Q2', 6591067 ),
( '2011Q3', 6219978 ),
( '2011Q4', 6189939 ),
( '2012Q1', 7178652 ),
( '2012Q2', 6731467 ),
( '2012Q3', 6949978 ),
( '2012Q4', 6679939 ),
( '2013Q1', 6242802 ),
( '2013Q2', 6421902 ),
( '2013Q3', 6667007 ),
( '2013Q4', 6575004 );
/*query begins here
cte is used to parse quart column into years & quarters */
WITH cte
AS ( SELECT Yr = CONVERT(SMALLINT, LEFT(Quart, 4))
, Qt = RIGHT(Quart, 1)
, Rev
FROM #T
)
/*join cte to itself to compare last year same quarter
ROW_NUMBER used to get sequential ordering
CONVERT to numeric and rounding to get formatting
*/
SELECT QUART = ROW_NUMBER() OVER (ORDER BY b.Yr
, b.Qt) ,
COMP = CONVERT(NUMERIC(5,4), ROUND((a.Rev-b.Rev*1.0)/ b.Rev, 4))
FROM cte a
JOIN cte b ON b.Qt = a.Qt
AND b.Yr = a.Yr - 1

Related

sql count new id that did not exists before for each month

I have the follow set of data
enter image description here
how can I write the sql to gives the result on right side?
that is the counting of unique id that did appeared previously for each month.
After long time of reading and reading his question, Ssiu wanted to ask the following:
So here is the test data in MS SQL: at that time he didn't clarify on postgresql
create table tmp1 (
ddate datetime
, iid int
)
insert into tmp1 values
('2017-11-01',1)
,('2017-11-02',2)
,('2017-11-03',3)
,('2017-11-04',4)
,('2017-11-05',5)
,('2017-11-06',5)
,('2017-11-07',5)
,('2017-12-01',1)
,('2017-12-02',2)
,('2017-12-03',3)
,('2017-12-04',6)
,('2017-12-05',7)
,('2018-01-01',1)
,('2018-01-02',2)
,('2018-01-03',3)
,('2018-01-04',4)
,('2018-01-05',8)
Disclaimer: The following is not the best approach for this problem. It is not applicable for more months, however it can give Ssiu a clue.
with cte(mmonth, iid) as (
select distinct convert(varchar(7), ddate, 120) mmonth
, iid
from tmp1
)
, cte_201711 as (
select * from cte where mmonth = '2017-11'
)
, cte_201712 as (
select * from cte where mmonth = '2017-12'
)
, cte_201801 as (
select * from cte where mmonth = '2018-01'
)
, cte_cnt201712 as(
select cte_201711.mmonth as mm201711
, cte_201711.iid as id201711
, cte_201712.mmonth as mm201712
, cte_201712.iid as id201712
from cte_201711
full outer join cte_201712
on cte_201712.iid = cte_201711.iid
)
, cte_cnt201801 as (
select cte_201711.mmonth as mm201711
, cte_201711.iid as id201711
, cte_201712.mmonth as mm201712
, cte_201712.iid as id201712
, cte_201801.mmonth as mm201801
, cte_201801.iid as id201801
from cte_201711
full outer join cte_201712
on cte_201712.iid = cte_201711.iid
full outer join cte_201801
on cte_201801.iid = cte_201712.iid
or cte_201801.iid = cte_201711.iid
)
--select * from cte_cnt201801 order by isnull(mm201711,'z'), isnull(mm201712,'z')
select '2017-12' mmonth, count(*) Ssiu
from cte_cnt201712
where mm201711 is null
union all
select '2018-01' mmonth, count(*) Ssiu
from cte_cnt201801
where mm201711 is null
and mm201712 is null
Note the data for the cte_cnt201801 CTE:
select * from cte_cnt201801 order by isnull(mm201711,'z'), isnull(mm201712,'z')
So the result for the above query is:

SQL reporting query

I have a database with following structure.
CREATE TABLE Party
(
PartyID INT IDENTITY
PRIMARY KEY ,
StatusID INT ,
Weigth INT ,
OldWeigth INT
);
GO
CREATE TABLE PartyLocation
(
PartyLocationID INT IDENTITY
PRIMARY KEY ,
PartyID INT FOREIGN KEY REFERENCES dbo.Party ( PartyID ) ,
LocationID INT ,
Distance INT
);
GO
CREATE TABLE PartyRole
(
PartyRoleID INT IDENTITY
PRIMARY KEY ,
PartyID INT FOREIGN KEY REFERENCES dbo.Party ( PartyID ) ,
RoleID INT
);
with some simple data.
INSERT INTO dbo.Party
( StatusID, Weigth, OldWeigth )
VALUES ( 1, -- StatusID - int
10, -- Age - int
20 -- OldAge - int
),
( 1, 15, 25 ),
( 2, 20, 30 );
INSERT INTO dbo.PartyLocation
( PartyID, LocationID, Distance )
VALUES ( 1, -- PartyID - int
1, -- LocationID - int
100 -- Distance - int
),
( 1, 2, 200 ),
( 1, 3, 300 ),
( 2, 1, 1000 ),
( 2, 2, 2000 ),
( 3, 1, 10000 );
INSERT INTO dbo.PartyRole
( PartyID, RoleID )
VALUES ( 1, -- PartyID - int
1 -- RoleID - int
),
( 1, 2 ),
( 1, 3 ),
( 2, 1 ),
( 2, 2 ),
( 3, 1 );
I want to query the following information
Return sum of Weigth of all parties that has roleID = 1 in PartyRole table
Return sum of OldWeigth of all parties that has statusID = 2
Return sum of distances of all parties that has locationID = 3
Return sum of distances of all parties that has roleID = 2
So the expected results are
FilteredWeigth FilteredOldWeigth FilteredDistance AnotherFilteredDistance
-------------- ----------------- ---------------- -----------------------
45 30 600 3600
Can we write a query that will query each table just once? If no what will be the most optimal way to query the data?
You can try this.
SELECT
FilteredWeigth = SUM(CASE WHEN RoleID = 1 AND RN_P = 1 THEN Weigth END) ,
FilteredOldWeigth = SUM(CASE WHEN StatusID = 2 AND RN_P = 1 THEN OldWeigth END),
FilteredDistance = SUM(CASE WHEN LocationID = 3 AND RN_L = 1 THEN Distance END),
AnotherFilteredDistance = SUM(CASE WHEN RoleID = 2 THEN Distance END)
FROM (
SELECT P.Weigth, P.StatusID, P.OldWeigth, PL.LocationID, PL.Distance, PR.RoleID,
RN_P = ROW_NUMBER() OVER (PARTITION BY P.PartyID ORDER BY PL.PartyLocationID),
RN_L = ROW_NUMBER() OVER (PARTITION BY PL.LocationID ORDER BY PR.PartyRoleID)
FROM Party P
INNER JOIN PartyLocation PL ON P.PartyID = PL.PartyID
INNER JOIN PartyRole PR ON P.PartyID = PR.PartyID
) AS T
the below gives
45 20 300 3600
the third column gives 300 which does not correspond to your expected result.
with q1
as
(
select sum(weigth) FilteredWeigth
from party join partyrole on party.partyid = partyrole.partyid
where partyrole.RoleID = '1'
),
q2 as
(
select sum(weigth) OldWeigth from party where StatusID = '2'
),
q3 as (
select sum(Distance) FilteredDistance
from party join PartyLocation on party.partyid = PartyLocation.partyid
where PartyLocation.locationID = '3'
),
q4 as
(
select sum(Distance) AnotherFilteredDistance
from party join partyrole on party.partyid = partyrole.partyid
join PartyLocation on party.partyid = PartyLocation.partyid
where partyrole.RoleID = '2'
)
select FilteredWeigth,OldWeigth,FilteredDistance,AnotherFilteredDistance
from q1,q2,q3,q4
When Using Individual Queries, you can achieve this using the following
Return sum of Weight of all parties that has roleID = 1 in PartyRole table
SELECT
SUM(Weight) FilteredWeigth
FROM dbo.Party P
WHERE EXISTS
(
SELECT
1
FROM dbo.PartyRole PR
WHERE PR. PartyID = P.PartyID
AND PR.RoleId = 1
)
Return sum of OldWeigth of all parties that has statusID = 2
SELECT
SUM(OldWeigth) FilteredOldWeigth
FROM dbo.Party P
WHERE EXISTS
(
SELECT
1
FROM dbo.PartyRole PR
WHERE PR. PartyID = P.PartyID
AND PR.RoleId = 2
)
Return sum of distances of all parties that has locationID = 3
SELECT
SUM(Distance) FilteredDistance
FROM dbo.PartyLocation
WHERE LocationID = 3
Return sum of distances of all parties that has roleID = 2
SELECT SUM(Distance) FROM PartyLocation PL
WHERE EXISTS
(
SELECT 1 FROM PartyRole PR
WHERE PR.PartyID = PL.PartyID
AND PR.Roleid = 2
)
If you want to get the result of all these in a single result set. then maybe you can try a pivot query. Like this
WITH CTE
AS
(
SELECT
'FilteredWeigth' ColNm,
SUM(Weigth) Val
FROM dbo.Party P
WHERE EXISTS
(
SELECT
1
FROM dbo.PartyRole PR
WHERE PR. PartyID = P.PartyID
AND PR.RoleId = 1
)
UNION
SELECT
'FilteredOldWeigth' ColNm,
SUM(OldWeigth) Val
FROM dbo.Party P
WHERE EXISTS
(
SELECT
1
FROM dbo.PartyRole PR
WHERE PR. PartyID = P.PartyID
AND PR.RoleId = 2
)
UNION
SELECT
'FilteredDistance' ColNm,
SUM(Distance) Val
FROM dbo.PartyLocation
WHERE LocationID = 3
UNION
SELECT
'AnotherFilteredDistance' ColNm,
SUM(Distance) Val FROM PartyLocation PL
WHERE EXISTS
(
SELECT 1 FROM PartyRole PR
WHERE PR.PartyID = PL.PartyID
AND PR.Roleid = 2
)
)
SELECT
*
FROM CTE
PIVOT
(
SUM(Val)
FOR ColNm IN
(
[FilteredWeigth],[FilteredOldWeigth],[FilteredDistance],[AnotherFilteredDistance]
)
)Pvt
The Result Will be
I could think of only three possible options:
Union query with four different select statements as answered by #ab-bennett
Join all tables then use select statements as answered by sarslan
Mix of 1 and 2, based on experiments
Coming to the question you asked:
Can we write a query that will query each table just once?
Assuming best performance is the goal, following could happen in each of the above cases:
All select statements would have their own where clause. This would perform best when where produces few rows compared to the count(*). Note that Joins are terrible for very large tables.
A join is made once, and the desired output is obtained from the same Joined table. This would perform optimal when where produces significant number of rows and the table is not too big to join.
You can mix JOIN / IN / EXISTS / WHERE to optimize your queries based on number of rows you are having in table. This approach could be used when your dataset cardinality might not vary a lot.

Can I create a SQL view to show "fake" data

My boss wants me to create a view in SQL to show data that isn't there. Is that possible? Here's what I need:
I need to show our internal currency conversion rate for any sourceCur that = targetCur. We only have this data for the year 2011, period 1. I need it to show all 12 periods for every year from 2011 to 2017. The value will always be the same (1). Here are the columns yearNum, preiodNum, sourceCur, targetCur, convRate.
A simple SELECT statement:
SELECT *
FROM dbo.CurrencyConversionRates
WHERE (sourceCur = targetCur)
The results are:
The value of convRate will always be 1 in this case because the currency is the same. I just need to project the data we have (2011 period 1) to all 12 periods for each year from 2011 to 2017. My boss does not want to add the data into the table, he wants a view.
Is this possible?
EDIT FROM COMMENTS
WITH future( yearNum , periodNum) AS (
SELECT DISTINCT
CalendarYear ,
fiscalPeriodNum
FROM
CALENDAR
WHERE CalendarYear BETWEEN 2011 AND YEAR(GETDATE())),
currencies( sourceCur , targetCur) AS (
SELECT DISTINCT
sourceCur ,
targetCur
FROM
CurrencyConversionRates
WHERE targetCur = sourceCur)
SELECT
f.yearNum ,
f.periodNum ,
c.sourceCur ,
c.targetCur ,
'1' AS conversionRate
FROM
future AS f
CROSS JOIN currencies AS c
ORDER BY
f.yearNum ,
f.periodNum;
Use a numbers CTE:
with CTE as
(
select 1 as NN
union all
select NN +1
where NN < 12
)
, CTE2 as
(
select 2011 + NN as N_Year
from CTE
where NN + 2011 < 2017
)
, CTE3 as
(
select C2.N_Year, C1.NN as N_Month
from CTE2 C2
cross join CTE C1
)
select C3.*, [OTHER STUFF]
from CTE3 C3
left join [Other table] OT
on OT.TheYear = C3.N_Year
and OT.TheMonth = C3.N_Month

Get Max and Min of 2 Columns SQL Server 2008 R2

This query is the final part of a store procedure that manages to show me the results in this way:
;with final_comparativo as (
select s.CeEmplazamiento, avg(s.Totales) TotalGeneral , s2.TotGral, #Mes as Mes
from tmpSemanas s
cross join ( select avg(Totales) TotGral from tmpSemanas s2 where Totales >0) s2
group by s.CeEmplazamiento , s2.TotGral
union all
select ss.CeEmplazamiento, avg(ss.Totales) TotalGeneral , ss2.TotGral, #Mes_comparar as Mes
from tmpSemanas_comparar ss
cross join ( select avg(Totales) TotGral from tmpSemanas_comparar ss2 where Totales >0) ss2
group by ss.CeEmplazamiento , ss2.TotGral
)
--insert #tmpDatos
, maximo_minimo as (
select CeEmplazamiento,
max(case when Mes = #Mes then TotalGeneral else 0 end) as Mes_actual,
max(case when Mes = #Mes_comparar then TotalGeneral else 0 end) as Mes_comparar
from final_comparativo
where TotalGeneral <> 0
group by CeEmplazamiento
)
select * from maximo_minimo
drop table #tmpDatos
This is the current result table
CeEmplazamiento Month_current Month_compare
Celaya 76.500000 75.600000
Coecillo 79.000000 79.800000
Irapuato 77.500000 75.400000
León Sur 85.750000 87.600000
Oriente León 86.250000 85.200000
The store procedure asks you for 2 arguments corresponding to month_current (month of origin) month_compare (against which month you want to compare the month_current) so it is already a final calculation. But my problem lies in the following, I want this table to take the maximum and minimum of the current month and likewise for the column "Month_compare", I hope I have explained. More briefly, I would like it to remain this way.
CeEmplazamiento Month_current Month_compare
Leon Sur 85.75 87.60
Celaya 76.50 null
Irapuato null 75.40
As far as you can tell, it is not the same amount for all CeEsmplazamiento, those that are not in the current Month, are not shown but are taken into account for a graphic that is being done. I hope you can help me
Smth like this:
declare #t table(CeEmplazamiento varchar(100), Month_current money, Month_compare money);
insert into #t values
('Celaya', 76.500000, 75.600000),
('Coecillo', 79.000000, 79.800000),
('Irapuato', 77.500000, 75.400000),
('León Sur', 85.750000, 87.600000),
('Oriente León', 86.250000, 85.200000);
with cte1 as
(
select top 1 CeEmplazamiento, Month_current, null as Month_compare
from #t
order by Month_current asc
)
,cte2 as
(
select top 1 CeEmplazamiento, Month_current, null as Month_compare
from #t
order by Month_current desc
)
,cte3 as
(
select top 1 CeEmplazamiento, null as Month_current, Month_compare
from #t
order by Month_compare asc
)
,cte4 as
(
select top 1 CeEmplazamiento, null as Month_current, Month_compare
from #t
order by Month_compare desc
)
select *
from cte1
union all
select *
from cte2
union all
select *
from cte3
union all
select *
from cte4

Trying to Get SELECT TOP to work with Parameter in ACCESS

This is building on some code I got the other day (thanks to peterm). I am now trying to select the TOP X number of results after calculations on the query. The X can range from 1 to 8 depending on the number of results per player.
This is the code I have but I get a syntax error when I try to run it.
SELECT
PlayerID
, RoundID
, PlayedTo
, (SELECT Count(PlayerID) FROM PlayedToCalcs) AS C
, iif(
C <= 6
, 1
, iif(
C <= 8
, 2
, (
iif(
C <= 10
, 3
, (
iif(
C <= 12
, 4
, (
iif(
C <= 14
, 5
, (
iif(
C <= 16
, 6
, (
iif(
C <= 18
, 7
, (iif(C <= 20, 8, 999))
)
)
)
)
)
)
)
)
)
)
)
) AS X
FROM PlayedToCalcs AS s
WHERE PlayedTo IN (
SELECT TOP (X) PlayedTo
FROM PlayedToCalcs
WHERE PlayerID = s.PlayerID
ORDER BY PlayedTo DESC, RoundID DESC
)
ORDER BY PlayerID, PlayedTo DESC, RoundID DESC;
Here is a link http://sqlfiddle.com/#!3/a726c/4 with a small sample of the data I'm trying to use it on.
The Access db engine does not allow you to use a parameter for SELECT TOP. You must include a literal value in the SQL statement.
For example this query works correctly.
SELECT TOP 2 *
FROM tblFoo
ORDER BY id DESC;
But attempting to substitute a parameter, how_many, triggers error 3141, "The SELECT statement includes a reserved word or an argument name that is misspelled or missing, or the punctuation is incorrect."
SELECT TOP how_many *
FROM tblFoo
ORDER BY id DESC;
The reason being in SQL Server (the simulator you used in SQL Fiddle), you cannot use IIF. Try using CASE.
And there is a limitation of using 7 nested IIF in Access.