firebird sql order by - sql

i got a dataset which looks like this:
customernumber year value
1 2011 500
2 2011 100
1 2010 400
3 2010 600
3 2011 300
2 2010 700
i want it to be ordered by highest value of year 2011, but the rows of each customer need to stay together.
it should look like this:
customernumber year value
1 2011 500
1 2010 400
3 2011 300
3 2010 600
2 2011 100
2 2010 700
is this even possible?
thanks in advance!

Use join to bring that value in, and then you can use it for the oder by:
select d.customernumber, d.year, d.value
from dataset d join
(select d.*
from dataset d
where d.year = 2011
) d2011
on d.customernumber = d2011.customernumber
order by d2011.value, d.customernumber, d.year desc;
In databases that support window functions, this can more easily be done as:
select d.*
from dataset d
order by max(case when year = 2011 then value end) over (partition by customernumber),
customernumber, year desc;

select customernumber, year, value from mytable
group by customernumber, year, value
order by year desc

Related

Detect and Set Column values between rows

I have a table relating to products:
PRD_SLD table
ID DATE SALE_IND
3 2012 0
3 2013 0
3 2014 1
3 2014 1
3 2015 1
3 2016 0
3 2017 1
I would like my final results to look like this:
PRD_SLD table
ID DATE SALE_IND STRT END
3 2012 0 2012 2014
3 2013 0 2012 2014
3 2014 1 2014 2016
3 2014 1 2014 2016
3 2015 1 2014 2016
3 2016 0 2016 2017
3 2017 1 2017 2017
I currently have a working CTE for retrieving the rows in which the values change. this CTE returns this:
PRD_SLD table
ID DATE SALE_IND
3 2012 0
3 2014 1
3 2016 0
3 2017 1
So it returns the first instance of the value in the table, and returns every time the SALE_IND changes.
Is there a way to create a start and end date based off of the date column? I am still very new to this and was enrolled in an advanced course. I'm sure there is a better way to complete this but is there a way to do it with the CTE results i have created? I know there is a between function but i don't know how to implement it into this query
One method is to define groups of adjacent records. You don't have a solid ordering of the rows, but you do have just enough information for this to work -- assuming the indicator is constant in each year.
select t.*,
min(year) over (partition by id, sale_ind, seqnum - seqnum_s) as min_year,
max(year) over (partition by id, sale_ind, seqnum - seqnum_s) as max_year
from (select t.*,
dense_rank() over (partition by id order by date) as seqnum,
dense_rank() over (partition by id, sale_ind order by date) as seqnum_s
from t
) t;

Aggregate payments per year per customer per type

Please consider the following payment data:
customerID paymentID pamentType paymentDate paymentAmount
---------------------------------------------------------------------
1 1 A 2015-11-28 500
1 2 A 2015-11-29 -150
1 3 B 2016-03-07 300
2 4 A 2015-03-03 200
2 5 B 2016-05-25 -100
2 6 C 2016-06-24 700
1 7 B 2015-09-22 110
2 8 B 2016-01-03 400
I need to tally per year, per customer, the sum of the diverse payment types (A = invoice, B = credit note, etc), as follows:
year customerID paymentType paymentSum
-----------------------------------------------
2015 1 A 350 : paymentID 1 + 2
2015 1 B 110 : paymentID 7
2015 1 C 0
2015 2 A 200 : paymentID 4
2015 2 B 0
2015 2 C 0
2016 1 A 0
2016 1 B 300 : paymentID 3
2016 1 C 0
2016 2 A 0
2016 2 B 300 : paymentID 5 + 8
2016 2 C 700 : paymentId 6
It is important that there are values for every category (so for 2015, customer 1 has 0 payment value for type C, but still it is good to see this).
In reality, there are over 10 payment types and about 30 customers. The total date range is 10 years.
Is this possible to do in only SQL, and if so could somebody show me how? If possible by using relatively easy queries so that I can learn from it, for instance by storing intermediary result into a #temptable.
Any help is greatly appreciated!
a simple GROUP BY with SUM() on the paymentAmount will gives you what you wanted
select year = datepart(year, paymentDate),
customerID,
paymentType,
paymentSum = sum(paymentAmount)
from payment_data
group by datepart(year, paymentDate), customerID, paymentType
This is a simple query that generates the required 0s. Note that it may not be the most efficient way to generate this result set. If you already have lookup tables for customers or payment types, it would be preferable to use those rather than the CTEs1 I use here:
declare #t table (customerID int,paymentID int,paymentType char(1),paymentDate date,
paymentAmount int)
insert into #t(customerID,paymentID,paymentType,paymentDate,paymentAmount) values
(1,1,'A','20151128', 500),
(1,2,'A','20151129',-150),
(1,3,'B','20160307', 300),
(2,4,'A','20150303', 200),
(2,5,'B','20160525',-100),
(2,6,'C','20160624', 700),
(1,7,'B','20150922', 110),
(2,8,'B','20160103', 400)
;With Customers as (
select DISTINCT customerID from #t
), PaymentTypes as (
select DISTINCT paymentType from #t
), Years as (
select DISTINCT DATEPART(year,paymentDate) as Yr from #t
), Matrix as (
select
customerID,
paymentType,
Yr
from
Customers
cross join
PaymentTypes
cross join
Years
)
select
m.customerID,
m.paymentType,
m.Yr,
COALESCE(SUM(paymentAmount),0) as Total
from
Matrix m
left join
#t t
on
m.customerID = t.customerID and
m.paymentType = t.paymentType and
m.Yr = DATEPART(year,t.paymentDate)
group by
m.customerID,
m.paymentType,
m.Yr
Result:
customerID paymentType Yr Total
----------- ----------- ----------- -----------
1 A 2015 350
1 A 2016 0
1 B 2015 110
1 B 2016 300
1 C 2015 0
1 C 2016 0
2 A 2015 200
2 A 2016 0
2 B 2015 0
2 B 2016 300
2 C 2015 0
2 C 2016 700
(We may also want to play games with a numbers table and/or generate actual start and end dates for years if the date processing above needs to be able to use an index)
Note also how similar the top of my script is to the sample data in your question - except it's actual code that generates the sample data. You may wish to consider presenting sample code in such a way in the future since it simplifies the process of actually being able to test scripts in answers.
1CTEs - Common Table Expressions. They may be thought of as conceptually similar to temp tables - except we don't actually (necessarily) materialize the results. They also are incorporated into the single query that follows them and the whole query is optimized as a whole.
Your suggestion to use temp tables means that you'd be breaking this into multiple separate queries that then necessarily force SQL to perform the task in an order that we have selected rather than letting the optimizer choose the best approach for the above single query.

Getting the max from two combined columns

I'm trying to get the value from all accounts in use, using the combination of two columns (Year and month) to get the right period.
The data table looks like this:
Account
Year
Month
Value
1000
2015
1
11501
1000
2016
1
11111
1000
2016
10
11610
1000
2017
1
11701
2000
2014
12
22222
2000
2017
1
21701
3000
2015
1
33333
4000
2016
1
44444
Table: AcBal
I've tried to make an query, but somehow, I cant quite get there...
Select Account,
Year,
Month,
MAX(((Year*100)+Month)) AS YearPeriod,
Value
from AcBal
where YearPeriod <= 201601
group by Account, Year, Month, Value
order by Account, Year
If I use "where year <= 2017", then I get a result, but with multiple hits for each accout. I only want one result for each account.
Wanted result:
Account
Year
Month
Value
1000
2016
1
11111
2000
2014
12
22222
3000
2015
1
33333
4000
2016
1
44444
How can I achieve that?
You could use TOP (1) WITH TIES and ROW_NUMBER() OVER() like the following query
Select TOP (1) WITH TIES
Account, [Year], [Month], [Year]*100+[Month] AS YearPeriod , Value
from AcBal
where [Year]*100+[Month] <=201601
ORDER BY ROW_NUMBER() OVER(PARTITION BY Account ORDER BY [Year]*100 +[Month] DESC)
Demo link: http://rextester.com/DUPJ25770
Use HAVING:
Select Account, Year, Month, MAX(((Year*100)+Month)) AS YearPeriod , Value
from AcBal
group by Account, Year, Month, Value
Having MAX(((Year*100)+Month)) <=201601
order by Account, Year

How to Fetch Data based on columns data match using SQL

Looking for small help, it i need to fetch data from one Table which will have data for all years like 2009,2010,2011 etc.
I need to create a SQL query which will pull data based on whether it has matching rows for particular year
This query will help users in fetching product prices in 2011 fo products enterd in 2010
Sample data
ProductID price year
1020 2000 USD 2009
1030 100 USD 2009
1030 150 USD 2010
1020 300 USD 2011
1020 310 USD 2012
1030 160 USD 2012
1040 400 USD 2012
Below Query is what i wrote i have little doubt so please correct me
select *
from productstbl a ,productstbl b
where Year = '2012' and
ProductID in(select b.ProductID from productstbl where b.Year = '2011') and
a.ProductID=b.ProductID
Try this
select * from productstbl
where ( year = '2012' or year = '2011' )
and ProductID in (select ProductID from productstbl where year='2011' and year='2012' )
Subquery version
SELECT
ProductID,
price as ThisYear,
(SELECT b.price FROM productstbl b
WHERE b.ProductID=a.ProductID
AND b.year=2011) as LastYear
FROM productstbl a
WHERE year=2012
ORDER BY ProductID
If it's MS SQL I'd add TOP 1 in the subquery, just in case the data has more than 1 row and breaks things. Or GROUP BY ProductID and use the min() or max() price as required.
And maybe a Coalesce around the subquery to default to a sane default value if it returns nothing and ends up Null

Generate year to date by month report in SQL [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Running total by grouped records in table
I am trying to put together an SQL statement that returns the SUM of a value by month, but on a year to date basis. In other words, for the month of March, I am looking to get the sum of a value for the months of January, February, and March.
I can easily do a group by to get a total for each month by itself, and potentially calculate the year to date value I need in my application from this data by looping through the results set. However, I was hoping to have some of this work handled with my SQL statement.
Has anyone ever tackled this type of problem with an SQL statement, and if so, what is the trick that I am missing?
My current sql statement for monthly data is similar to the following:
Select month, year, sum(value) from mytable group by month, year
If I include a where clause on the month, and only group by the year, I can get the result for a single month that I am looking for:
select year, sum(value) from mytable where month <= selectedMonth group by year
However, this requires me to have a particular month pre-selected or to utilize 12 different SQL statements to generate one clean result set.
Any guidance that can be provided would be greatly appreciated!
Update: The data is stored on an IBM iSeries.
declare #Q as table
(
mmonth INT,
value int
)
insert into #Q
values
(1,10),
(1,12),
(2,45),
(3,23)
select sum(January) as UpToJanuary,
sum(February)as UpToFebruary,
sum(March) as UpToMarch from (
select
case when mmonth<=1 then sum(value) end as [January] ,
case when mmonth<=2 then sum(value) end as [February],
case when mmonth<=3 then sum(value) end as [March]
from #Q
group by mmonth
) t
Produces:
UpToJanuary UpToFebruary UpToMarch
22 67 90
You get the idea, right?
NOTE: This could be done easier with PIVOT tables but I don't know if you are using SQL Server or not.
As far as I know DB2 does support windowing functions although I don't know if this is also supported on the iSeries version.
If windowing functions are supported (I believe IBM calls them OLAP functions) then the following should return what you want (provided I understood your question correctly)
select month,
year,
value,
sum(value) over (partition by year order by month asc) as sum_to_date
from mytable
order by year, month
create table mon
(
[y] int not null,
[m] int not null,
[value] int not null,
primary key (y,m))
select a.y, a.m, a.value, sum(b.value)
from mon a, mon b
where a.y = b.y and a.m >= b.m
group by a.y, a.m, a.value
2011 1 120 120
2011 2 130 250
2011 3 500 750
2011 4 10 760
2011 5 140 900
2011 6 100 1000
2011 7 110 1110
2011 8 90 1200
2011 9 70 1270
2011 10 150 1420
2011 11 170 1590
2011 12 600 2190
You should try to join the table to itself by month-behind-a-month condition and generate a synthetic month-group code to group by as follows:
select
sum(value),
year,
up_to_month
from (
select a.value,
a.year,
b.month as up_to_month
from table as a join table as b on a.year = b.year and b.month => a.month
)
group by up_to_month, year
gives that:
db2 => select * from my.rep
VALUE YEAR MONTH
----------- ----------- -----------
100 2011 1
200 2011 2
300 2011 3
400 2011 4
db2 -t -f rep.sql
1 YEAR UP_TO_MONTH
----------- ----------- -----------
100 2011 1
300 2011 2
600 2011 3
1000 2011 4